Deprecations
Declare deprecations and sunset dates on a route and have them reach your editor, OpenAPI, Swift, Kotlin, and the Deprecation and Sunset response headers.
Routes
| Declaration | Type |
|---|---|
deprecated | boolean | string | { message?, date?, link? } |
sunset | string | { date, link? } |
Dates are ISO 8601, a date alone meaning midnight UTC. On the wire, Deprecation is Unix seconds and Sunset an HTTP date.
Set deprecated on the route, either true, a migration message, or the object form with a date and a documentation link:
export const users = k.routes('users', {
deleteUser: k
.route({
method: 'DELETE',
path: '/users/:id',
deprecated: true,
responses: {
200: z.object({
ok: z.boolean(),
}),
},
})
.handler(/* ... */),
});export const users = k.routes('users', {
deleteUser: k
.route({
method: 'DELETE',
path: '/users/:id',
deprecated: 'use `archiveUser` instead',
responses: {
200: z.object({
ok: z.boolean(),
}),
},
})
.handler(/* ... */),
});export const users = k.routes('users', {
deleteUser: k
.route({
method: 'DELETE',
path: '/users/:id',
deprecated: {
message: 'use `archiveUser` instead',
date: '2026-03-01', // or '2026-03-01T12:00:00Z'
link: 'https://example.com/changelog/delete-user',
},
responses: {
200: z.object({
ok: z.boolean(),
}),
},
})
.handler(/* ... */),
});With a date, every response from the route announces it:
Deprecation: @1772323200
Link: <https://example.com/changelog/delete-user>; rel="deprecation"true or a message alone sends no header. The date may sit in the future, announcing a deprecation before it takes effect.
Sunset
A route scheduled for removal declares when. The object form adds a link to the retirement policy:
export const users = k.routes('users', {
deleteUser: k
.route({
method: 'DELETE',
path: '/users/:id',
deprecated: 'use `archiveUser` instead',
sunset: '2027-01-01', // or '2027-01-01T12:00:00Z'
responses: {
200: z.object({
ok: z.boolean(),
}),
},
})
.handler(/* ... */),
});export const users = k.routes('users', {
deleteUser: k
.route({
method: 'DELETE',
path: '/users/:id',
deprecated: 'use `archiveUser` instead',
sunset: {
date: '2027-01-01', // or '2027-01-01T12:00:00Z'
link: 'https://example.com/retirement-policy',
},
responses: {
200: z.object({
ok: z.boolean(),
}),
},
})
.handler(/* ... */),
});Every response from the route then announces it:
Sunset: Fri, 01 Jan 2027 00:00:00 GMT
Link: <https://example.com/retirement-policy>; rel="sunset"sunset stands on its own, a route can sunset without being deprecated. defineConfig rejects a sunset or deprecated.date that does not parse as ISO 8601.
Fields
| Declaration | Type |
|---|---|
.meta({ deprecated: ... }) | boolean | string |
Fields declare it in their Zod metadata:
const UserSchema = z.object({
id: z.string(),
email: z.string().meta({
deprecated: 'use `email_address` instead',
}),
email_address: z.string(),
});What each output emits
- OpenAPI marks the operation and the field's schema with
deprecated: true(the spec has no message field), and documents theDeprecation,Sunset, andLinkheaders on every response of a route that sends them. - The TypeScript client emits
@deprecated use `email_address` insteadon the method and on the field, which strikes them through in your editor whether or not the extension is installed. - Swift emits
@available(*, deprecated, message: "use `email_address` instead"), so the message reaches autocomplete and compiler warnings. - Kotlin emits
@Deprecated("use `email_address` instead"), reaching the same places. - The headers themselves are sent by the server, so every client sees them at runtime.