Skip to content
Zod

Zod

Officialv5.4.7

@kubb/plugin-zod

kubb add plugin-zod

Customize generated schemas

Prefix inferred types, change Zod validators, and encode custom request types.

Start with the Zod plugin configuration. Apply these changes to pluginZod() in that configuration.

Prefix inferred types

Enable inferred and override resolver.schema.typeName.

kubb.config.ts
import { pluginZod } from '@kubb/plugin-zod'

pluginZod({
  inferred: true,
  resolver: {
    schema: {
      typeName(name) {
        return 'Api' + name
      },
    },
  },
})

The type becomes ApiPet. The schema constant stays petSchema. The callback receives the cased type name, so no additional casing helper is needed.

Use numbers for int64 fields

Replace the bigint printer handler to validate int64 fields with z.int().

kubb.config.ts
import { pluginZod } from '@kubb/plugin-zod'

pluginZod({
  printer: {
    nodes: {
      bigint() {
        return 'z.int()'
      },
    },
  },
})

The integer handler controls int32 fields separately. To change integer representation across all plugins, use the adapter's integerType.

Remove descriptions

Use the description-removal macro with pluginZod({ macros: [dropDescriptions] }). Generated schemas omit description metadata.

Encode custom types

A direction-aware printer decodes response values and encodes request values. This example converts ISO time strings to Temporal.PlainTime.

kubb.config.ts
import { pluginZod, type PrinterZodNodes } from '@kubb/plugin-zod'

const nodes: PrinterZodNodes = {
  time() {
    return this.options.direction === 'encode'
      ? 'z.instanceof(Temporal.PlainTime).transform((value) => value.toString())'
      : 'z.iso.time().transform((value) => Temporal.PlainTime.from(value))'
  },
}

pluginZod({ inferred: true, printer: { nodes } })

Use a runtime that provides Temporal, or add its polyfill through a generated import or banner. Match the OpenAPI format: time, date, and date-time use time, date, and datetime handlers.

The generator emits separate response and input schemas, including through $ref:

src/gen/zod/slotSchema.ts
export const slotSchema = z.object({
  startsAt: z.iso.time().transform((value) => Temporal.PlainTime.from(value)),
})

export const slotInputSchema = z.object({
  startsAt: z.instanceof(Temporal.PlainTime).transform((value) => value.toString()),
})
Use inferred: true without pluginTs so the client types follow these conversions. Types from pluginTs describe the wire format.

For built-in date conversion, set dateType: 'date' on the adapter. Generated response schemas convert ISO datetimes to Date. Request schemas convert them back with toISOString().

Import a custom codec

Override a printer node and register the package import with this.import. For example, use your package's myCodec.uint64() for int64 fields:

kubb.config.ts
import { ast } from 'kubb/kit'
import { pluginZod } from '@kubb/plugin-zod'

pluginZod({
  printer: {
    nodes: {
      bigint() {
        this.import(ast.factory.createImport({
          name: ['myCodec'],
          path: 'my-codec/zod',
        }))
        return 'myCodec.uint64()'
      },
    },
  },
})

Generated files import myCodec from my-codec/zod. Leave root unset on this import to preserve the package specifier. Setting root rewrites it as a relative file path.

Format and type mappings

@kubb/plugin-zod maps OpenAPI types and formats to Zod v4:

OpenAPI type and formatZodZod MiniNotes
integerz.int()z.int()Coerces to z.coerce.number().int() when coercion.numbers is enabled
integer, format: int32z.int32()z.int32()32-bit signed integer
integer, format: uint32z.uint32()z.uint32()32-bit unsigned integer
integer, format: int64z.bigint()z.bigint()64-bit integer
string, format: byte / base64z.base64()z.base64()Base64 string validation
string, format: base64urlz.base64url()z.base64url()URL-safe base64 string validation
string, format: jwtz.jwt()z.jwt()JSON Web Token format
string, format: ulidz.ulid()z.ulid()ULID format
string, format: ibanz.iban()z.iban()International Bank Account Number
string, format: durationz.iso.duration()z.iso.duration()ISO 8601 duration format
string, format: uuidz.uuid() (or z.guid())z.uuid() (or z.guid())Configured via guidType
string, format: emailz.email()z.email()Email format
string, format: uri / urlz.url()z.url()URL format
string, format: ipv4 / ipv6z.ipv4() / z.ipv6()z.ipv4() / z.ipv6()IP address format
string, format: datez.iso.date()z.iso.date()ISO 8601 date
string, format: date-timez.iso.datetime()z.string()ISO 8601 date-time
string, format: timez.iso.time()z.iso.time()ISO 8601 time
object, additionalProperties: <schema>z.record(z.string(), schema)z.record(z.string(), schema)Dictionary with no fixed properties
object, additionalProperties: truez.looseObject(shape)z.looseObject(shape)Open/passthrough object allowing extra keys
object, propertyNames: <schema>z.record(keySchema, schema)z.record(keySchema, schema)Dynamic map with validated key schema (e.g. pattern, format)
object, propertyNames: <enum>z.partialRecord(enumSchema, schema)z.partialRecord(enumSchema, schema)Enum keys may be missing

Dictionaries, open objects, and key schemas

OpenAPI 3.1 propertyNames validates dictionary keys. Open key schemas use z.record(keySchema, valueSchema), including regex, UUID, and length constraints. Closed keys, such as enums, literals, or unions of enums, use z.partialRecord so only present keys are validated. Keys may be missing, as in OpenAPI, and the inferred type is Partial<Record<Keys, Value>>.

patternProperties combines key regex patterns into an alternation and emits z.record(z.string().regex(...), valueSchema). Zod Mini uses z.string().check(z.regex(...)) for the key schema.

An additionalProperties schema without fixed properties produces a dictionary. With fixed properties, it produces .catchall(valueSchema) to preserve that declared shape. additionalProperties: true emits z.looseObject(shape) to allow undeclared keys.