ts-to-zod

repository·main·Indexed 23 days ago

https://github.com/fabien0102/ts-to-zod

A tool to automatically generate Zod schemas from TypeScript types and interfaces. It ensures runtime validation matches static types and supports Zod v4. Features include JSDoc tag validators for OpenAPI-inspired constraints (e.g., @minimum, @maxLength, @format), support for discriminated unions via @discriminator, and a programmatic API for advanced use cases. It supports primitive types and TypeScript helpers like Record, Pick, Omit, Partial, Required, Array, and Promise, though it does not support type generics.

Tokens
5.8K
Snippets
10
Records
38
Agent score
82%

What's inside ts-to-zod

  1. How ts-to-zod handles type references and imports

    main

    Single File References

    ts-to-zod works on one file at a time. It can resolve references to other types within the same file.

    Non-Zod Imports

    If your TypeScript interface references a type imported from an external module (e.g., @3rdparty/person), ts-to-zod cannot know its schema. It will use z.any() as a placeholder for that type.

    Zod Imports (Automatic Resolution)

    If an imported type is also defined as an input in your ts-to-zod.config.mjs, the utility will automatically replace the external import with the generated Zod schema from that file, resolving the relative paths correctly.

  2. Validate array elements using `@element` tags

    main

    When working with arrays of string or number, you can apply validation rules to the individual elements using @element prefixed JSDoc tags.

    Supported Element Tags

    • @elementDescription {value}
    • @elementMinimum {number} [err_msg]
    • @elementMaximum {number} [err_msg]
    • @elementMinLength {number} [err_msg]
    • @elementMaxLength {number} [err_msg]
    • @elementFormat {FormatType} [err_msg]
    • @elementPattern {regex}
    export interface EnemyContact {
      /**
       * @elementMinLength 5
       * @elementMaxLength 10
       * @minLength 2
       * @maxLength 50
       */
      names: string[];
    }
    
    // Generates:
    // names: z.array(z.string().min(5).max(10)).min(2).max(50)
  3. Use embedded validation to ensure type compatibility

    main

    By default, ts-to-zod performs an internal validation check to ensure that z.infer<generatedSchema> matches your original TypeScript type. This check only applies to exported types/interfaces.

    To bypass this validation step, use the --skipValidation flag (use at your own risk).

  4. Understand ts-to-zod limitations and supported types

    main

    Because ts-to-zod generates Zod schemas, it is subject to Zod's limitations. Specifically:

    • No type generics are supported.

    However, you can use all primitive types and the following TypeScript helpers:

    • Record<...>
    • Pick<...>
    • Omit<...>
    • Partial<...>
    • Required<...>
    • Array<...>
    • Promise<...>
  5. Install and Quick Start with ts-to-zod (Zod v4)

    main

    To generate Zod v4 schemas from TypeScript types or interfaces, install ts-to-zod as a development dependency and run the CLI against your source files.

    Generated schemas follow the naming pattern ${originalType}Schema for every exported interface and type.

  6. Migrate from Zod v3 to Zod v4

    main

    If you are upgrading an existing project from Zod v3 to v4:

    1. Update your zod dependency to ^4.
    2. Regenerate your schemas using ts-to-zod to ensure compatibility with updated string validation methods and improved function type support.
    npm install zod@^4
    npx ts-to-zod
  7. Explore the ts-to-zod example

    main

    The example directory demonstrates how ts-to-zod transforms TypeScript files into Zod schemas and type definitions.

    In this example:

    • The source file is hero.ts.
    • The generated Zod schemas are in hero.zod.ts.
    • The generated types are in hero.types.ts.

    To experiment, you can modify heros.ts and run the generation command to see how the output files change. Note that the example is designed to stay in sync; if heros.ts and heros.zod.ts diverge, heros.types.ts will report TypeScript errors.

    pnpm gen:example
  8. Prettify generated files with pretty-quick

    main
    Since ts-to-zod v5, Prettier is no longer embedded in the generation process. To automatically format your generated files, you can chain the command with pretty-quick in your package.json scripts.
  9. How multi-config mode works

    main

    If your ts-to-zod.config.mjs exports an array of configuration objects instead of a single object, ts-to-zod enters multi-config mode.

    In this mode:

    1. You cannot use the input and output positional arguments directly; you must use the --all flag or the --config <name> flag.
    2. Using --all will execute every configuration object in the array.
    3. Using --config <name> will execute only the configuration object where the name property matches the provided string.
    4. If you run the command without --all or --config while multiple configs exist, the CLI will prompt you to choose an execution mode.
  10. Configure ts-to-zod via ts-to-zod.config.js

    main

    You can customize schema names or restrict which types are exported by creating a ts-to-zod.config.js file at your project root. You can initialize a type-safe configuration file by running pnpm ts-to-zod --init.

    To restrict the scope of generation, use one of the following filters:

    • nameFilter: Filters by the interface or type name.
    • jsDocTagFilter: Filters based on specific JSDoc tags.

    Note: If an exported interface/type references a non-exported interface/type, ts-to-zod will fail to generate the schema and report missing dependencies.

    // ts-to-zod.config.js
    /**
     * ts-to-zod configuration.
     *
     * @type {import("./src/config").TsToZodConfig}
     */
    module.exports = [
      {
        name: "example",
        input: "example/heros.ts",
        output: "example/heros.zod.ts",
        jsDocTagFilter: (tags) => tags.map((tag) => tag.name).includes("toExtract"),
      },
    ];
  11. Define Custom JSDoc Format Types

    main

    While ts-to-zod supports standard OpenAPI formats like email and ip, you can define custom @format behaviors using the customJSDocFormatTypes property in your configuration.

    A custom format can be defined as either a simple string (representing a regex) or an object containing a regex and an errorMessage.

    When applied to a TypeScript property via JSDoc, ts-to-zod will transform it into a Zod .regex() validation call.

    {
      "customJSDocFormatTypes": {
        "phone-number": "^\\d{3}-\\d{3}-\\d{4}$",
        "date": {
          "regex": "^\\d{4}-\\d{2}-\\d{2}$",
          "errorMessage": "Must be in YYYY-MM-DD format."
        }
      }
    }
  12. Understand Zod v4 Promise type handling in ts-to-zod

    main

    When generating types for Zod v4, ts-to-zod applies special logic for Promise types and functions that return promises.

    Why special handling is required

    In Zod v4, standard inference can lead to type mismatches for asynchronous operations:

    1. Asynchronous Parsing: Promises require parseAsync().
    2. Type Unwrapping: z.output<ZodPromise<T>> returns the unwrapped type T rather than Promise<T>.
    3. Inference Issues: While z.infer<ZodPromise<T>> technically returns Promise<T>, it can create type compatibility issues in certain TypeScript environments.

    To ensure the generated TypeScript type is fully compatible with the original source type, ts-to-zod uses the pattern Promise<z.output<typeof schema>> for promise types and z.output<typeof schema> for promise-returning functions.