Skip to content

extend() Function

The extend() function augments a Standard Schema with additional JSON Schema properties. It returns a new Standard Schema whose input and output JSON Schema representations have the overlay deep-merged into them. The original schema is not modified.

This is useful for adding OpenAPI-specific metadata -- such as description, example, format, minimum, maximum, or any other JSON Schema keyword -- to schemas from libraries like Zod or Valibot that may not expose those options natively.

Import

ts
import { extend } from 'to-openapi'

Signature

ts
function extend(
  schema: StandardJSONSchemaV1,
  overlay: Record<string, unknown>,
): StandardJSONSchemaV1

Parameters

  • schema -- the original Standard JSON Schema V1 object to extend. This can be a Zod schema, Valibot schema, or any schema implementing the @standard-schema/spec StandardJSONSchemaV1 interface.
  • overlay -- a plain object whose properties will be deep-merged into both the input and output JSON Schema representations of the original schema.

Return Value

Returns a new StandardJSONSchemaV1 object. The new schema:

  • Preserves the original schema's version, vendor, and types metadata.
  • Wraps the original schema's jsonSchema.input() and jsonSchema.output() methods so that each call returns the base JSON Schema with the overlay deep-merged on top.
  • Does not mutate the original schema.

How It Works

Under the hood, extend() creates a new Standard Schema with wrapped input() and output() methods. When these methods are called during document generation, they:

  1. Call the original schema's input() or output() to get the base JSON Schema.
  2. Deep-merge the overlay into the base JSON Schema.
  3. Return the merged result.

This means the overlay is applied lazily at resolution time, not eagerly at the point of calling extend().

Examples

Adding a Description

ts
import { extend } from 'to-openapi'
import { z } from 'zod'

const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
  email: z.string().email(),
})

const ExtendedUserSchema = extend(UserSchema, {
  description: 'A registered user in the system',
  title: 'User',
})

The resulting JSON Schema will include the description and title fields alongside the properties generated by Zod.

Adding an Example

ts
import { extend } from 'to-openapi'
import { z } from 'zod'

const ProductSchema = z.object({
  name: z.string(),
  price: z.number(),
})

const ExtendedProductSchema = extend(ProductSchema, {
  example: {
    name: 'Widget',
    price: 9.99,
  },
})

Adding Format and Constraints

ts
import { extend } from 'to-openapi'
import { z } from 'zod'

const PaginationSchema = z.object({
  page: z.number().int(),
  perPage: z.number().int(),
})

const ExtendedPaginationSchema = extend(PaginationSchema, {
  properties: {
    page: { minimum: 1, description: 'Page number (1-indexed)' },
    perPage: { minimum: 1, maximum: 100, description: 'Items per page' },
  },
})

Extending Nested Properties

Since the overlay is deep-merged, you can target nested properties:

ts
import { extend } from 'to-openapi'
import { z } from 'zod'

const AddressSchema = z.object({
  street: z.string(),
  city: z.string(),
  zip: z.string(),
})

const ExtendedAddressSchema = extend(AddressSchema, {
  description: 'A postal address',
  properties: {
    zip: {
      pattern: '^[0-9]{5}(-[0-9]{4})?$',
      description: 'US ZIP code',
      example: '90210',
    },
  },
})

Using with Route Definitions

extend() is typically used inline within route definitions:

ts
import { openapi, extend } from 'to-openapi'
import { z } from 'zod'

const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
  email: z.string().email(),
})

const doc = openapi({
  info: { title: 'My API', version: '1.0.0' },
  schemas: {
    User: extend(UserSchema, {
      description: 'A user account',
      example: { id: '550e8400-e29b-41d4-a716-446655440000', name: 'Alice', email: 'alice@example.com' },
    }),
  },
  paths: {
    'GET /users': {
      summary: 'List users',
      200: extend(z.array(UserSchema), {
        description: 'A list of users',
      }),
    },
  },
})

Notes

  • The original schema is never modified. extend() always returns a new schema object.
  • The overlay is deep-merged, so nested objects are merged recursively rather than replaced.
  • Multiple extend() calls can be chained: extend(extend(schema, overlay1), overlay2).
  • The overlay can contain any valid JSON Schema keyword, giving you full control over the generated OpenAPI schema output.