colinhacks/zod-deno(opens in a new tab)
Language: TypeScript
TypeScript-first schema validation for Deno
- stars
- 17
- watchers
- 1
- forks
- 0
- open issues
- 0
- Last updated
- 05/05/2025
Language: TypeScript
TypeScript-first schema validation for Deno
zod.dev
TypeScript-first schema validation with static type inference
|
Zod 4 is now in beta!
|
Zod is a TypeScript-first schema declaration and validation library. I'm using the term "schema" to broadly refer to any data type, from a simple string to a complex nested object.
Zod is designed to be as developer-friendly as possible. The goal is to eliminate duplicative type declarations. With Zod, you declare a validator once and Zod will automatically infer the static TypeScript type. It's easy to compose simpler types into complex data structures.
Some other great aspects:
.optional()) return a new instanceSponsorship at any level is appreciated and encouraged. If you built a paid product using Zod, consider one of the corporate tiers.
|
|
|
|
|
|
|
|
|
|
|
|
Nitric |
PropelAuth |
Cerbos |
Scalar |
|
Trigger.dev |
Transloadit |
Infisical |
Whop |
|
CryptoJobsList |
Plain. |
Inngest |
Storyblok |
|
Mux |
Cybozu |
|
|
|
|
|
|
|
|
|
|
|
|
|
There are a growing number of tools that are built atop or support Zod natively! If you've built a tool or library on top of Zod, tell me about it on Twitter or start a Discussion. I'll add it below and tweet it out.
tRPC: Build end-to-end typesafe APIs without GraphQL.@anatine/zod-nestjs: Helper methods for using Zod in a NestJS project.zod-endpoints: Contract-first strictly typed endpoints with Zod. OpenAPI compatible.zhttp: An OpenAPI compatible, strictly typed http library with Zod input and response validation.domain-functions: Decouple your business logic from your framework using composable functions. With first-class type inference from end to end powered by Zod schemas.@zodios/core: A typescript API client with runtime and compile time validation backed by axios and zod.express-zod-api: Build Express-based APIs with I/O schema validation and custom middlewares.tapiduck: End-to-end typesafe JSON APIs with Zod and Express; a bit like tRPC, but simpler.koa-zod-router: Create typesafe routes in Koa with I/O validation using Zod.zod-sockets: Zod-powered Socket.IO microframework with I/O validation and built-in AsyncAPI specsoas-tszod-gen: Client SDK code generator to convert OpenApi v3 specifications into TS endpoint caller functions with Zod types.GQLoom: Weave GraphQL schema and resolvers using Zod.oRPC: Typesafe APIs Made Simplereact-hook-form: A first-party Zod resolver for React Hook Form.TanStack Form: Headless, performant, and type-safe form state management for TS/JS, React, Vue, Angular, Solid, and Litzod-validation-error: Generate user-friendly error messages from ZodErrors.zod-formik-adapter: A community-maintained Formik adapter for Zod.react-zorm: Standalone <form> generation and validation for React using Zod.zodix: Zod utilities for FormData and URLSearchParams in Remix loaders and actions.conform: A typesafe form validation library for progressive enhancement of HTML forms. Works with Remix and Next.js.remix-params-helper: Simplify integration of Zod with standard URLSearchParams and FormData for Remix apps.formik-validator-zod: Formik-compliant validator library that simplifies using Zod with Formik.zod-i18n-map: Useful for translating Zod error messages.@modular-forms/solid: Modular form library for SolidJS that supports Zod for validation.houseform: A React form library that uses Zod for validation.sveltekit-superforms: Supercharged form library for SvelteKit with Zod validation.mobx-zod-form: Data-first form builder based on MobX & Zod.@vee-validate/zod: Form library for Vue.js with Zod schema validation.zod-form-renderer: Auto-infer form fields from zod schema and render them with react-hook-form with E2E type safety.antd-zod: Zod adapter for Ant Design form fields validation.frrm: Tiny 0.5kb Zod-based, HTML form abstraction that goes brr.zod-to-ts: Generate TypeScript definitions from Zod schemas.zod-to-json-schema: Convert your Zod schemas into JSON Schemas.@anatine/zod-openapi: Converts a Zod schema to an OpenAPI v3.x SchemaObject.zod-fast-check: Generate fast-check arbitraries from Zod schemas.zod-dto: Generate Nest.js DTOs from a Zod schema.fastify-type-provider-zod: Create Fastify type providers from Zod schemas.zod-to-openapi: Generate full OpenAPI (Swagger) docs from Zod, including schemas, endpoints & parameters.nestjs-graphql-zod: Generates NestJS GraphQL model classes from Zod schemas. Provides GraphQL method decorators working with Zod schemas.zod-openapi: Create full OpenAPI v3.x documentation from Zod schemas.fastify-zod-openapi: Fastify type provider, validation, serialization and @fastify/swagger support for Zod schemas.typeschema: Universal adapter for schema validation.zodex: (De)serialization for zod schemasts-to-zod: Convert TypeScript definitions into Zod schemas.@runtyping/zod: Generate Zod from static types & JSON schema.json-schema-to-zod: Convert your JSON Schemas into Zod schemas. Live demo.json-to-zod: Convert JSON objects into Zod schemas. Live demo.graphql-codegen-typescript-validation-schema: GraphQL Code Generator plugin to generate form validation schema from your GraphQL schema.zod-prisma: Generate Zod schemas from your Prisma schema.Supervillain: Generate Zod schemas from your Go structs.prisma-zod-generator: Emit Zod schemas from your Prisma schema.drizzle-zod: Emit Zod schemas from your Drizzle schema.prisma-trpc-generator: Emit fully implemented tRPC routers and their validation schemas using Zod.zod-prisma-types Create Zod types from your Prisma models.quicktype: Convert JSON objects and JSON schemas into Zod schemas.@sanity-typed/zod: Generate Zod Schemas from Sanity Schemas.java-to-zod: Convert POJOs to Zod schemasOrval: Generate Zod schemas from OpenAPI schemasKubb: Generate SDKs and Zod schemas from your OpenAPI schemas@anatine/zod-mock: Generate mock data from a Zod schema. Powered by faker.js.zod-mocking: Generate mock data from your Zod schemas.zod-fixture: Use your zod schemas to automate the generation of non-relevant test fixtures in a deterministic way.zocker: Generate plausible mock-data from your schemas.zodock Generate mock data based on Zod schemas.zod-schema-faker Generates mock data from Zod schemas. Powered by @faker-js/faker and randexp.jsfreerstore: Firestore cost optimizer.slonik: Node.js Postgres client with strong Zod integration.schemql: Enhances your SQL workflow by combining raw SQL with targeted type safety and schema validation.soly: Create CLI applications with zod.pastel: Create CLI applications with react, zod, and ink.zod-xlsx: A xlsx based resource validator using Zod schemas.znv: Type-safe environment parsing and validation for Node.js with Zod schemas.zod-config: Load configurations across multiple sources with flexible adapters, ensuring type safety with Zod.unplugin-environment: A plugin for loading enviroment variables safely with schema validation, simple with virtual module, type-safe with intellisense, and better DX 🔥 🚀 👷. Powered by Zod.zod-struct: Create runtime-checked structs with Zod.zod-csv: Validation helpers for zod for parsing CSV data.fullproduct.dev: Universal Expo + Next.js App Starter that uses Zod schemas as the single source of truth to keep generated MDX docs, GraphQL, database models, forms, and fetcher functions in sync.zod_utilz: Framework agnostic utilities for Zod.zod-playground: A tool for learning and testing Zod schema validation functionalities. Link.zod-sandbox: Controlled environment for testing zod schemas. Live demo.zod-dev: Conditionally disables Zod runtime parsing in production.zod-accelerator: Accelerates Zod's throughput up to ~100x.TypeScript 4.5+!
You must enable strict mode in your tsconfig.json. This is a best practice for all TypeScript projects.
// tsconfig.json { // ... "compilerOptions": { // ... "strict": true } }
npm install zod # npm deno add npm:zod # deno yarn add zod # yarn bun add zod # bun pnpm add zod # pnpm
Zod also publishes a canary version on every commit. To install the canary:
npm install zod@canary # npm deno add npm:zod@canary # deno yarn add zod@canary # yarn bun add zod@canary # bun pnpm add zod@canary # pnpm
The rest of this README assumes you are using npm and importing directly from the
"zod"package.
Creating a simple string schema
import { z } from "zod"; // creating a schema for strings const mySchema = z.string(); // parsing mySchema.parse("tuna"); // => "tuna" mySchema.parse(12); // => throws ZodError // "safe" parsing (doesn't throw error if validation fails) mySchema.safeParse("tuna"); // => { success: true; data: "tuna" } mySchema.safeParse(12); // => { success: false; error: ZodError }
Creating an object schema
import { z } from "zod"; const User = z.object({ username: z.string(), }); User.parse({ username: "Ludwig" }); // extract the inferred type type User = z.infer<typeof User>; // { username: string }
import { z } from "zod"; // primitive values z.string(); z.number(); z.bigint(); z.boolean(); z.date(); z.symbol(); // empty types z.undefined(); z.null(); z.void(); // accepts undefined // catch-all types // allows any value z.any(); z.unknown(); // never type // allows no values z.never();
Zod now provides a more convenient way to coerce primitive values.
const schema = z.coerce.string(); schema.parse("tuna"); // => "tuna" schema.parse(12); // => "12"
During the parsing step, the input is passed through the String() function, which is a JavaScript built-in for coercing data into strings.
schema.parse(12); // => "12" schema.parse(true); // => "true" schema.parse(undefined); // => "undefined" schema.parse(null); // => "null"
The returned schema is a normal ZodString instance so you can use all string methods.
z.coerce.string().email().min(5);
How coercion works
All primitive types support coercion. Zod coerces all inputs using the built-in constructors: String(input), Number(input), new Date(input), etc.
z.coerce.string(); // String(input) z.coerce.number(); // Number(input) z.coerce.boolean(); // Boolean(input) z.coerce.bigint(); // BigInt(input) z.coerce.date(); // new Date(input)
Note — Boolean coercion with z.coerce.boolean() may not work how you expect. Any truthy value is coerced to true, and any falsy value is coerced to false.
const schema = z.coerce.boolean(); // Boolean(input) schema.parse("tuna"); // => true schema.parse("true"); // => true schema.parse("false"); // => true schema.parse(1); // => true schema.parse([]); // => true schema.parse(0); // => false schema.parse(""); // => false schema.parse(undefined); // => false schema.parse(null); // => false
For more control over coercion logic, consider using z.preprocess or z.pipe().
Literal schemas represent a literal type, like "hello world" or 5.
const tuna = z.literal("tuna"); const twelve = z.literal(12); const twobig = z.literal(2n); // bigint literal const tru = z.literal(true); const terrificSymbol = Symbol("terrific"); const terrific = z.literal(terrificSymbol); // retrieve literal value tuna.value; // "tuna"
Currently there is no support for Date literals in Zod. If you have a use case for this feature, please file an issue.
Zod includes a handful of string-specific validations.
// validations z.string().max(5); z.string().min(5); z.string().length(5); z.string().email(); z.string().url(); z.string().emoji(); z.string().uuid(); z.string().nanoid(); z.string().cuid(); z.string().cuid2(); z.string().ulid(); z.string().regex(regex); z.string().includes(string); z.string().startsWith(string); z.string().endsWith(string); z.string().datetime(); // ISO 8601; by default only `Z` timezone allowed z.string().ip(); // defaults to allow both IPv4 and IPv6 z.string().cidr(); // defaults to allow both IPv4 and IPv6 // transforms z.string().trim(); // trim whitespace z.string().toLowerCase(); // toLowerCase z.string().toUpperCase(); // toUpperCase // added in Zod 3.23 z.string().date(); // ISO date format (YYYY-MM-DD) z.string().time(); // ISO time format (HH:mm:ss[.SSSSSS] or HH:mm) z.string().duration(); // ISO 8601 duration z.string().base64();
Check out validator.js for a bunch of other useful string validation functions that can be used in conjunction with Refinements.
You can customize some common error messages when creating a string schema.
const name = z.string({ required_error: "Name is required", invalid_type_error: "Name must be a string", });
When using validation methods, you can pass in an additional argument to provide a custom error message.
z.string().min(5, { message: "Must be 5 or more characters long" }); z.string().max(5, { message: "Must be 5 or fewer characters long" }); z.string().length(5, { message: "Must be exactly 5 characters long" }); z.string().email({ message: "Invalid email address" }); z.string().url({ message: "Invalid url" }); z.string().emoji({ message: "Contains non-emoji characters" }); z.string().uuid({ message: "Invalid UUID" }); z.string().includes("tuna", { message: "Must include tuna" }); z.string().startsWith("https://", { message: "Must provide secure URL" }); z.string().endsWith(".com", { message: "Only .com domains allowed" }); z.string().datetime({ message: "Invalid datetime string! Must be UTC." }); z.string().date({ message: "Invalid date string!" }); z.string().time({ message: "Invalid time string!" }); z.string().ip({ message: "Invalid IP address" }); z.string().cidr({ message: "Invalid CIDR" });
As you may have noticed, Zod string includes a few date/time related validations. These validations are regular expression based, so they are not as strict as a full date/time library. However, they are very convenient for validating user input.
The z.string().datetime() method enforces ISO 8601; default is no timezone offsets and arbitrary sub-second decimal precision. Seconds may be omitted if precision is not set.
const datetime = z.string().datetime(); datetime.parse("2020-01-01T00:00:00Z"); // pass datetime.parse("2020-01-01T00:00:00.123Z"); // pass datetime.parse("2020-01-01T00:00:00.123456Z"); // pass (arbitrary precision) datetime.parse("2020-01-01T00:00Z"); // pass (hours and minutes only) datetime.parse("2020-01-01T00:00:00+02:00"); // fail (no offsets allowed)
Timezone offsets can be allowed by setting the offset option to true.
const datetime = z.string().datetime({ offset: true }); datetime.parse("2020-01-01T00:00:00+02:00"); // pass datetime.parse("2020-01-01T00:00+02:00"); // pass datetime.parse("2020-01-01T00:00:00.123+02:00"); // pass (millis optional) datetime.parse("2020-01-01T00:00:00.123+0200"); // pass (millis optional) datetime.parse("2020-01-01T00:00:00.123+02"); // pass (only offset hours) datetime.parse("2020-01-01T00:00:00Z"); // pass (Z still supported)
Allow unqualified (timezone-less) datetimes with the local flag.
const schema = z.string().datetime({ local: true }); schema.parse("2020-01-01T00:00:00"); // pass schema.parse("2020-01-01T00:00"); // pass
You can additionally constrain the allowable precision. By default, arbitrary sub-second precision is supported (but optional).
const datetime = z.string().datetime({ precision: 3 }); datetime.parse("2020-01-01T00:00:00.123Z"); // pass datetime.parse("2020-01-01T00:00:00Z"); // fail datetime.parse("2020-01-01T00:00Z"); // fail datetime.parse("2020-01-01T00:00:00.123456Z"); // fail
Added in Zod 3.23
The z.string().date() method validates strings in the format YYYY-MM-DD.
const date = z.string().date(); date.parse("2020-01-01"); // pass date.parse("2020-1-1"); // fail date.parse("2020-01-32"); // fail
Added in Zod 3.23
The z.string().time() method validates strings in the format HH:MM or HH:MM:SS[.s+]. The second can include arbitrary decimal precision. It does not allow timezone offsets of any kind.
const time = z.string().time(); time.parse("00:00:00"); // pass time.parse("09:52:31"); // pass time.parse("09:52"); // pass time.parse("23:59:59.9999999"); // pass (arbitrary precision) time.parse("00:00:00.123Z"); // fail (no `Z` allowed) time.parse("00:00:00.123+02:00"); // fail (no offsets allowed)
You can set the precision option to constrain the allowable decimal precision.
const time = z.string().time({ precision: 3 }); time.parse("00:00:00.123"); // pass time.parse("00:00:00.123456"); // fail time.parse("00:00:00"); // fail time.parse("00:00"); // fail
By default .ip() allows both IPv4 and IPv6.
const ip = z.string().ip(); ip.parse("192.168.1.1"); // pass ip.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // pass ip.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:192.168.1.1"); // pass ip.parse("256.1.1.1"); // fail ip.parse("84d5:51a0:9114:gggg:4cfa:f2d7:1f12:7003"); // fail
You can additionally set the IP version.
const ipv4 = z.string().ip({ version: "v4" }); ipv4.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // fail const ipv6 = z.string().ip({ version: "v6" }); ipv6.parse("192.168.1.1"); // fail
Validate IP address ranges specified with CIDR notation. By default, .cidr() allows both IPv4 and IPv6.
const cidr = z.string().cidr(); cidr.parse("192.168.0.0/24"); // pass cidr.parse("2001:db8::/32"); // pass
You can specify a version with the version parameter.
const ipv4Cidr = z.string().cidr({ version: "v4" }); ipv4Cidr.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // fail const ipv6Cidr = z.string().cidr({ version: "v6" }); ipv6Cidr.parse("192.168.1.1"); // fail
You can customize certain error messages when creating a number schema.
const age = z.number({ required_error: "Age is required", invalid_type_error: "Age must be a number", });
Zod includes a handful of number-specific validations.
z.number().gt(5); z.number().gte(5); // alias .min(5) z.number().lt(5); z.number().lte(5); // alias .max(5) z.number().int(); // value must be an integer z.number().positive(); // > 0 z.number().nonnegative(); // >= 0 z.number().negative(); // < 0 z.number().nonpositive(); // <= 0 z.number().multipleOf(5); // Evenly divisible by 5. Alias .step(5) z.number().finite(); // value must be finite, not Infinity or -Infinity z.number().safe(); // value must be between Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER
Optionally, you can pass in a second argument to provide a custom error message.
z.number().lte(5, { message: "this👏is👏too👏big" });
Zod includes a handful of bigint-specific validations.
z.bigint().gt(5n); z.bigint().gte(5n); // alias `.min(5n)` z.bigint().lt(5n); z.bigint().lte(5n); // alias `.max(5n)` z.bigint().positive(); // > 0n z.bigint().nonnegative(); // >= 0n z.bigint().negative(); // < 0n z.bigint().nonpositive(); // <= 0n z.bigint().multipleOf(5n); // Evenly divisible by 5n.
You can customize certain error messages when creating a nan schema.
const isNaN = z.nan({ required_error: "isNaN is required", invalid_type_error: "isNaN must be 'not a number'", });
You can customize certain error messages when creating a boolean schema.
const isActive = z.boolean({ required_error: "isActive is required", invalid_type_error: "isActive must be a boolean", });
Use z.date() to validate Date instances.
z.date().safeParse(new Date()); // success: true z.date().safeParse("2022-01-12T00:00:00.000Z"); // success: false
You can customize certain error messages when creating a date schema.
const myDateSchema = z.date({ required_error: "Please select a date and time", invalid_type_error: "That's not a date!", });
Zod provides a handful of date-specific validations.
z.date().min(new Date("1900-01-01"), { message: "Too old" }); z.date().max(new Date(), { message: "Too young!" });
Coercion to Date
Since zod 3.20, use z.coerce.date() to pass the input through new Date(input).
const dateSchema = z.coerce.date(); type DateSchema = z.infer<typeof dateSchema>; // type DateSchema = Date /* valid dates */ console.log(dateSchema.safeParse("2023-01-10T00:00:00.000Z").success); // true console.log(dateSchema.safeParse("2023-01-10").success); // true console.log(dateSchema.safeParse("1/10/23").success); // true console.log(dateSchema.safeParse(new Date("1/10/23")).success); // true /* invalid dates */ console.log(dateSchema.safeParse("2023-13-10").success); // false console.log(dateSchema.safeParse("0000-00-00").success); // false
For older zod versions, use z.preprocess like described in this thread.
const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]); type FishEnum = z.infer<typeof FishEnum>; // 'Salmon' | 'Tuna' | 'Trout'
z.enum is a Zod-native way to declare a schema with a fixed set of allowable string values. Pass the array of values directly into z.enum(). Alternatively, use as const to define your enum values as a tuple of strings. See the const assertion docs for details.
const VALUES = ["Salmon", "Tuna", "Trout"] as const; const FishEnum = z.enum(VALUES);
This is not allowed, since Zod isn't able to infer the exact values of each element.
const fish = ["Salmon", "Tuna", "Trout"]; const FishEnum = z.enum(fish);
.enum
To get autocompletion with a Zod enum, use the .enum property of your schema:
FishEnum.enum.Salmon; // => autocompletes FishEnum.enum; /* => { Salmon: "Salmon", Tuna: "Tuna", Trout: "Trout", } */
You can also retrieve the list of options as a tuple with the .options property:
FishEnum.options; // ["Salmon", "Tuna", "Trout"];
.exclude/.extract()
You can create subsets of a Zod enum with the .exclude and .extract methods.
const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]); const SalmonAndTrout = FishEnum.extract(["Salmon", "Trout"]); const TunaOnly = FishEnum.exclude(["Salmon", "Trout"]);
Zod enums are the recommended approach to defining and validating enums. But if you need to validate against an enum from a third-party library (or you don't want to rewrite your existing enums) you can use z.nativeEnum().
Numeric enums
enum Fruits { Apple, Banana, } const FruitEnum = z.nativeEnum(Fruits); type FruitEnum = z.infer<typeof FruitEnum>; // Fruits FruitEnum.parse(Fruits.Apple); // passes FruitEnum.parse(Fruits.Banana); // passes FruitEnum.parse(0); // passes FruitEnum.parse(1); // passes FruitEnum.parse(3); // fails
String enums
enum Fruits { Apple = "apple", Banana = "banana", Cantaloupe, // you can mix numerical and string enums } const FruitEnum = z.nativeEnum(Fruits); type FruitEnum = z.infer<typeof FruitEnum>; // Fruits FruitEnum.parse(Fruits.Apple); // passes FruitEnum.parse(Fruits.Cantaloupe); // passes FruitEnum.parse("apple"); // passes FruitEnum.parse("banana"); // passes FruitEnum.parse(0); // passes FruitEnum.parse("Cantaloupe"); // fails
Const enums
The .nativeEnum() function works for as const objects as well. ⚠️ as const requires TypeScript 3.4+!
const Fruits = { Apple: "apple", Banana: "banana", Cantaloupe: 3, } as const; const FruitEnum = z.nativeEnum(Fruits); type FruitEnum = z.infer<typeof FruitEnum>; // "apple" | "banana" | 3 FruitEnum.parse("apple"); // passes FruitEnum.parse("banana"); // passes FruitEnum.parse(3); // passes FruitEnum.parse("Cantaloupe"); // fails
You can access the underlying object with the .enum property:
FruitEnum.enum.Apple; // "apple"
You can make any schema optional with z.optional(). This wraps the schema in a ZodOptional instance and returns the result.
const schema = z.optional(z.string()); schema.parse(undefined); // => returns undefined type A = z.infer<typeof schema>; // string | undefined
For convenience, you can also call the .optional() method on an existing schema.
const user = z.object({ username: z.string().optional(), }); type C = z.infer<typeof user>; // { username?: string | undefined };
You can extract the wrapped schema from a ZodOptional instance with .unwrap().
const stringSchema = z.string(); const optionalString = stringSchema.optional(); optionalString.unwrap() === stringSchema; // true
Similarly, you can create nullable types with z.nullable().
const nullableString = z.nullable(z.string()); nullableString.parse("asdf"); // => "asdf" nullableString.parse(null); // => null
Or use the .nullable() method.
const E = z.string().nullable(); // equivalent to nullableString type E = z.infer<typeof E>; // string | null
Extract the inner schema with .unwrap().
const stringSchema = z.string(); const nullableString = stringSchema.nullable(); nullableString.unwrap() === stringSchema; // true
// all properties are required by default const Dog = z.object({ name: z.string(), age: z.number(), }); // extract the inferred type like this type Dog = z.infer<typeof Dog>; // equivalent to: type Dog = { name: string; age: number; };
Use .shape to access the schemas for a particular key.
Dog.shape.name; // => string schema Dog.shape.age; // => number schema
Use .keyof to create a ZodEnum schema from the keys of an object schema.
const keySchema = Dog.keyof(); keySchema; // ZodEnum<["name", "age"]>
You can add additional fields to an object schema with the .extend method.
const DogWithBreed = Dog.extend({ breed: z.string(), });
You can use .extend to overwrite fields! Be careful with this power!
Equivalent to A.extend(B.shape).
const BaseTeacher = z.object({ students: z.array(z.string()) }); const HasID = z.object({ id: z.string() }); const Teacher = BaseTeacher.merge(HasID); type Teacher = z.infer<typeof Teacher>; // => { students: string[], i