# schema zod

Source: https://apigenie.pl/techdocs/reference/cli-generate_schema_zod

Generate [Zod](https://zod.dev) (v4) schemas, with their TypeScript types, from JSON Schemas.

> **(experimental, outside the 1.0 compatibility promise)** The generated schemas aren't yet
> checked against a live API (conformance Level 3), and the options and the output can still
> change. `schema zod` says so in its `--help` and on the first line it prints, on the standard
> error. See the [experimental commands](/techdocs/reference/cli-overview#experimental-commands).

## Usage

```sh
apigenie-generator schema zod [...OPTIONS] INPUT_FILE
```

## Description

The input is a JSON or YAML file:

- a JSON Schema document (drafts 4, 6, 7, 2019-09 and 2020-12, 2020-12 when it has no
  `$schema`): its root schema, named after its `title` or the file, and its `$defs` (or
  `definitions`) are generated. A root that only holds definitions is left out;
- an OpenAPI 3.x document: its `components.schemas`, with the OpenAPI 3.0 `nullable` keyword and
  boolean `exclusiveMinimum`/`exclusiveMaximum`;
- a Swagger 2.0 document: its `definitions`, with the `x-nullable` extension;
- an AsyncAPI document: its `components.schemas`. Schemas in other formats (Avro, ...) are skipped.

Other schemas that a `$ref` points to, e.g. `#/$defs/User/properties/address` or a schema of
another file, are named after their path (`UserAddressSchema`), so that each is written once.

Every schema becomes a constant and a type:

```ts
import * as z from "zod";

export const UserSchema = z.looseObject({
  id: z.uuid(),
  email: z.email(),
  /** Display name */
  name: z.string().min(1).max(80),
  age: z.int().gte(0).lt(150).optional(),
});
export type User = z.infer<typeof UserSchema>;
```

Schemas are declared after the schemas they use. A schema using itself, or a schema declared
later, from an object property gets a getter:

```ts
export const CommentSchema = z.strictObject({
  text: z.string(),
  get replies() {
    return z.array(CommentSchema).optional();
  },
});
```

Elsewhere, e.g. `items` of an array schema using the array itself, the reference is wrapped in
`z.lazy()` and the constant is typed `z.ZodType`, so its inferred type is `unknown`. This is
reported as a warning.

### How keywords are mapped

| JSON Schema | Zod |
| ----------- | --- |
| `type` | `z.string()`, `z.number()`, `z.int()` (`z.int32()` for the `int32` format), `z.boolean()`, `z.null()`, `z.array()`, objects; several types make a `z.union()`, `null` with other types `.nullable()` |
| no `type` | the type implied by the keywords, e.g. `minLength` makes a string; `z.unknown()` otherwise |
| `enum`, `const` | `z.enum()` for strings, `z.literal()` otherwise |
| `format` | `z.email()`, `z.url()` (`uri`, `iri`), `z.uuid()`, `z.iso.datetime({ offset: true })`, `z.iso.date()`, `z.iso.duration()`, `z.ipv4()`, `z.ipv6()`, `z.hostname()`, `z.base64()` (`byte`), `z.base64url()`; other formats are not validated |
| `minLength`, `maxLength`, `pattern` | `.min()`, `.max()`, `.length()`, `.regex()` |
| `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf` | `.gte()`, `.lte()`, `.gt()`, `.lt()`, `.multipleOf()` |
| `items`, `minItems`, `maxItems` | `z.array()`, `.min()`, `.max()` |
| `prefixItems` (or an array of `items`) | `z.tuple()`, with optional items beyond `minItems` and a rest of `items` (`additionalItems`) |
| `uniqueItems`, `contains` | refinements |
| `properties`, `required` | the shape, with `.optional()` for properties that aren't required, or `.default()` with their `default` |
| `additionalProperties` | none or `true`: `z.looseObject()` (unknown properties are kept); `false`: `z.strictObject()`; a schema: `.catchall()`, or `z.record()` without properties |
| `patternProperties` | a refinement validating the matching properties |
| `minProperties`, `maxProperties`, `propertyNames`, `dependentRequired`, `dependentSchemas` (`dependencies`) | refinements |
| `allOf` | `z.intersection()` |
| `anyOf`, `oneOf` | `z.union()`, or `z.discriminatedUnion()` when a required property with constant values tells the objects apart (e.g. the OpenAPI `discriminator`) |
| `not`, `if`/`then`/`else` | refinements |
| `$ref` | the referenced schema's constant |
| `description` (or `title`), `deprecated` | JSDoc comments; `.describe()` with the `describe` option |

Not supported, and reported as warnings: `unevaluatedProperties` and `unevaluatedItems`.
`$dynamicRef` and `$recursiveRef` are resolved to their initial targets.

In very large graphs of recursive schemas, TypeScript may give up inferring a type
(`TS2589: Type instantiation is excessively deep` at `z.infer`); the schemas still validate.

Approximations: `oneOf` is not exclusive, `uniqueItems` compares items by their JSON text, string
lengths count UTF-16 code units, and `uuid` accepts the versions known to Zod. The types of
properties are implied by their keywords also where JSON Schema accepts other types, e.g.
`{ "minLength": 1 }` rejects numbers.

## Extensions

| Extension | Effect |
| --------- | ------ |
| `x-apigenie.codegen.name` | base of the names of the constant and the type of a schema (`AnimalSchema`, `Animal`) |
| `x-nullable` | `true` makes a schema accept `null`, as in Swagger 2.0 documents |

The extensions can live in an overlay for this target instead of in the document, e.g.
`zod.overlay.yaml`:

```yaml
overlay: 1.0.0
info:
  title: Zod names
  version: 1.0.0
actions:
  - target: $.components.schemas.Pet
    update:
      x-apigenie:
        codegen:
          name: Animal
```

```sh
apigenie-generator schema zod openapi.yaml --overlay zod.overlay.yaml -o out
```

## Options

### -c, --config

Path to the configuration file to use. See the "Configuration" section for more information.

### --overlay OVERLAY

Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the
extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those
listed under `commands.generate.schema_zod.overlay` in the configuration file (with paths relative
to it). Warnings and errors about a value an overlay set point to the overlay.

### --strict

Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated)
instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`).

### -o, --output

Directory where the schemas are generated.

### --package-name

Name of the generated package. Defaults to the title of the document or the name of the file.

### --package-version

Version of the generated package. Defaults to the version of the API, or `0.0.0`.

## Configuration

```yaml
version: '1.0'
commands:
  generate:
    schema_zod:
      layout: package
      import_extension: .js
      schema_suffix: Schema
      type_suffix: ''
      types: true
      additional_properties: loose
      describe: false
```

| Option | Description |
| ------ | ----------- |
| `layout` | `package` (default): an npm package with `src/schemas.ts` and `src/index.ts`; `sources`: `schemas.ts` only |
| `import_extension` | Extension of relative imports: `.js` (default, Node.js) or an empty string (bundlers) |
| `schema_suffix` | Appended to the names of the schema constants (default `Schema`) |
| `type_suffix` | Appended to the names of the types (default none) |
| `types` | Whether `export type X = z.infer<typeof XSchema>` is generated (default `true`) |
| `additional_properties` | Objects without `additionalProperties`: `loose` (default) keeps unknown properties, `strip` removes them (`z.object()`) |
| `describe` | Whether descriptions are attached with `.describe()` (default `false`) |
