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
import { extend } from 'to-openapi'Signature
function extend(
schema: StandardJSONSchemaV1,
overlay: Record<string, unknown>,
): StandardJSONSchemaV1Parameters
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/specStandardJSONSchemaV1interface.overlay-- a plain object whose properties will be deep-merged into both theinputandoutputJSON Schema representations of the original schema.
Return Value
Returns a new StandardJSONSchemaV1 object. The new schema:
- Preserves the original schema's
version,vendor, andtypesmetadata. - Wraps the original schema's
jsonSchema.input()andjsonSchema.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:
- Call the original schema's
input()oroutput()to get the base JSON Schema. - Deep-merge the overlay into the base JSON Schema.
- 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
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
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
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:
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:
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.