Skip to content

Request Parameters

to-openapi supports three kinds of request parameters: query, path, and header. Each is defined by providing an object schema whose properties become individual parameters.

Query Parameters

Provide an object schema to the query field. Each property of the schema becomes a separate query parameter.

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

const doc = openapi({
  info: { title: 'API', version: '1.0.0' },
  paths: {
    'GET /users': {
      query: z.object({
        page: z.number(),
        limit: z.number().optional(),
        search: z.string().optional(),
      }),
      200: UserListSchema,
    },
  },
})

This produces three query parameters: page (required), limit (optional), and search (optional). Required vs optional is determined by the required array in the resolved JSON Schema.

Path Parameters

Path parameters are automatically detected from :param or {param} segments in the route path. By default, they are typed as { type: "string" } and marked required: true.

ts
'GET /users/:id': {
  200: UserSchema,
}

This produces a path parameter id with type: "string", in: "path", required: true.

Custom Path Parameter Types

Provide an object schema to params to override the default typing:

ts
'GET /users/:id': {
  params: z.object({
    id: z.string().uuid(),
  }),
  200: UserSchema,
}

Now the id parameter has the UUID format from your schema instead of a plain string. Path parameters are always required: true regardless of the schema.

Mixed Detection

If your path has parameters not covered by the params schema, they are still included with the default { type: "string" } typing:

ts
'GET /orgs/:orgId/users/:userId': {
  params: z.object({
    userId: z.string().uuid(),
  }),
  200: UserSchema,
}

This produces two path parameters: orgId with default string typing, and userId with UUID typing from the schema.

Header Parameters

Provide an object schema to headers. Each property becomes a header parameter.

ts
'GET /protected': {
  headers: z.object({
    'x-api-key': z.string(),
    'x-request-id': z.string().optional(),
  }),
  200: DataSchema,
}

This produces two header parameters: x-api-key (required) and x-request-id (optional).

Required Fields

Whether a parameter is required depends on its presence in the JSON Schema required array:

  • Query parameters: required if listed in the schema's required array; optional otherwise.
  • Path parameters: always required: true, regardless of the schema.
  • Header parameters: required if listed in the schema's required array; optional otherwise.

In Zod, calling .optional() on a property removes it from the required array. Other schema libraries have equivalent mechanisms.

Complete Example

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

const UpdateUserBody = z.object({
  name: z.string(),
  email: z.string().email(),
})

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

const doc = openapi({
  info: { title: 'User API', version: '1.0.0' },
  paths: {
    'PUT /orgs/:orgId/users/:userId': {
      params: z.object({
        orgId: z.string().uuid(),
        userId: z.string().uuid(),
      }),
      query: z.object({
        dryRun: z.boolean().optional(),
      }),
      headers: z.object({
        'x-idempotency-key': z.string(),
      }),
      body: UpdateUserBody,
      200: UserSchema,
      400: null,
    },
  },
})

This produces:

  • Two path parameters: orgId and userId (both UUID, both required)
  • One query parameter: dryRun (optional boolean)
  • One header parameter: x-idempotency-key (required string)
  • A JSON request body with the UpdateUserBody schema
  • Two responses: 200 with the User schema, 400 with no body