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

DeclarationType
deprecatedboolean | string | { message?, date?, link? }
sunsetstring | { 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:

src/routes/users.ts
export const users = k.routes('users', {
    deleteUser: k
        .route({
            method: 'DELETE',
            path: '/users/:id',
            deprecated: true,
            responses: {
                200: z.object({
                    ok: z.boolean(),
                }),
            },
        })
        .handler(/* ... */),
});
src/routes/users.ts
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(/* ... */),
});

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:

src/routes/users.ts
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(/* ... */),
});

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

DeclarationType
.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 the Deprecation, Sunset, and Link headers on every response of a route that sends them.
  • The TypeScript client emits @deprecated use `email_address` instead on 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.

On this page