Zod Schema Validation: Parse and Validate JSON in TypeScript
Zod is a TypeScript-first schema validation library with 40M+ weekly npm downloads. It validates JSON data at runtime and simultaneously infers TypeScript types — meaning you write the schema once and get both runtime validation and compile-time type safety. Install with npm install zod. This guide covers defining schemas, using safeParse() vs parse(), validating API responses, common Zod types, converting between Zod and JSON Schema, and when to choose Zod over JSON Schema. For the JSON Schema approach, see our guide to validate JSON Schema in JavaScript with Ajv and our JSON Schema tutorial.
Validating JSON Schema (not Zod)? Use Jsonic's JSON Schema Validator to test schemas against data instantly.
Open JSON Schema ValidatorDefine a Zod schema
A Zod schema is a description of the expected shape and constraints of your data. You build schemas by composing Zod's primitive types — z.string(), z.number(), z.boolean() — with structural types like z.object() and z.array(). Validators such as .email(), .min(), and .datetime() add additional constraints on top of the base type. The most powerful feature of Zod is z.infer<typeof Schema>: it extracts a TypeScript type directly from the schema definition, so you never need to write a separate interface or type alias that could drift out of sync with your validation logic.
import { z } from 'zod'
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
age: z.number().min(0).max(120).optional(),
role: z.enum(['admin', 'user', 'guest']),
createdAt: z.string().datetime(),
})
// Infer TypeScript type from schema — no separate interface needed
type User = z.infer<typeof UserSchema>
// Equivalent to:
// {
// id: number;
// name: string;
// email: string;
// age?: number | undefined;
// role: 'admin' | 'user' | 'guest';
// createdAt: string;
// }The z.infer<typeof UserSchema> pattern eliminates the need to write a separate TypeScript interface. When you update the schema — adding a field, tightening a constraint — the TypeScript type updates automatically. This single source of truth is the core reason Zod has become the default validation library for TypeScript projects. For generating an initial schema from a sample JSON payload, you can use our JSON to TypeScript type generator to get the interface shape, then translate it to Zod types.
parse() vs safeParse()
Zod provides two methods for running validation against a schema. parse() throws a ZodError when validation fails — it's a hard stop, useful when invalid data represents a programming error or a startup-time misconfiguration that should crash the process immediately. safeParse() never throws; instead it returns a discriminated union object with a success boolean. If success is true, the data field holds the validated and typed value. If success is false, the error field holds a ZodError with a detailed errors array. For API routes and request handlers, always prefer safeParse() — a thrown exception in a request handler will either crash the server or result in an unhandled 500 error, whereas safeParse() lets you return a proper 400 response with field-level error details.
import { z } from 'zod'
const UserSchema = z.object({ name: z.string(), age: z.number() })
// parse() throws ZodError on invalid input
try {
const user = UserSchema.parse({ name: 'Alice', age: 'thirty' }) // throws
} catch (err) {
if (err instanceof z.ZodError) {
console.log(err.errors)
// [{ path: ['age'], message: 'Expected number, received string', code: 'invalid_type' }]
}
}
// safeParse() returns { success: true, data } or { success: false, error }
const result = UserSchema.safeParse({ name: 'Alice', age: 30 })
if (result.success) {
console.log(result.data) // typed as { name: string; age: number }
} else {
console.log(result.error.errors) // ZodIssue[]
}Use safeParse() for external data sources: API responses, user form input, webhook payloads, and database query results where unexpected shapes are a normal operational condition. Use parse() for internal data you fully control — for example, loading a configuration file at application startup where an invalid config should halt the process. See our guide on parse JSON in TypeScript for how Zod compares to other type-safe parsing patterns.
Validate JSON API responses with Zod
The most common production use case for Zod is validating API responses. When you call a third-party API or your own backend, response.json() returns unknown (or any in older TypeScript setups) — the type system has no idea what the payload contains. Wrapping the response in a Zod safeParse() call validates the shape at the network boundary and gives you a fully typed value inside the success branch. This catches API contract breaks (a newly required field, a type change from number to string) the moment they happen, rather than silently propagating wrong values deep into your application logic where they cause confusing runtime crashes.
import { z } from 'zod'
const ProductSchema = z.object({
id: z.number(),
name: z.string(),
price: z.number().positive(),
inStock: z.boolean(),
tags: z.array(z.string()),
})
const ProductListSchema = z.array(ProductSchema)
type Product = z.infer<typeof ProductSchema>
async function fetchProducts(url: string): Promise<Product[]> {
const response = await fetch(url)
const json = await response.json() // unknown at this point
const result = ProductListSchema.safeParse(json)
if (!result.success) {
throw new Error(`API response invalid: ${result.error.message}`)
}
return result.data // typed as Product[]
}Notice that z.array(ProductSchema) wraps the item schema to validate a JSON array response — the inferred type is Product[]. The result.error.message in the throw includes a JSON summary of every failing field and its expected vs received type, making it straightforward to diagnose which part of the API response changed. For more context on safe API data fetching in TypeScript, see fetch JSON in JavaScript.
Common Zod types and validators
Zod covers all JSON-representable data types and adds TypeScript-specific variants like optional and nullable. The table below shows the most frequently used types alongside their inferred TypeScript equivalents and available chained validators. Chained validators narrow the constraint further without changing the inferred type — for example, z.string().email() still infers as string in TypeScript but enforces email format at runtime. You can also compose schemas:z.union([SchemaA, SchemaB]) accepts data that matches either schema, and z.intersection(SchemaA, SchemaB) requires both to match — equivalent to TypeScript's A | B and A & B respectively.
| Zod type | TypeScript type | Example validators |
|---|---|---|
z.string() | string | .min(1), .max(100), .email(), .url(), .regex() |
z.number() | number | .min(0), .max(100), .int(), .positive() |
z.boolean() | boolean | — |
z.array(z.string()) | string[] | .min(1), .max(10), .nonempty() |
z.object() | { ... } | .pick(), .omit(), .partial() |
z.enum(['a', 'b']) | 'a' | 'b' | — |
z.union([A, B]) | A | B | — |
z.optional() | T | undefined | — |
z.nullable() | T | null | — |
Beyond the primitives, Zod provides z.literal('value') for exact string or number matches, z.record(z.string(), z.number()) for dictionaries with dynamic keys (infers as Record<string, number>), and z.tuple([z.string(), z.number()]) for fixed-length arrays. For recursive data structures like trees, use z.lazy(() => Schema).
Convert Zod schema to JSON Schema (and back)
Zod and JSON Schema solve similar problems but serve different ecosystems. Zod lives entirely in TypeScript/JavaScript; JSON Schema is language-agnostic and used by API documentation tools (OpenAPI), Python validators (jsonschema), and multi-language systems. Two community packages bridge the gap: zod-to-json-schema converts a Zod schema to a standard JSON Schema object (compatible with Ajv and other validators), and json-schema-to-zod converts a JSON Schema object to a string of Zod code you can paste into your project. This makes it practical to maintain Zod as the source of truth internally while also generating JSON Schema for API documentation or cross-language consumers.
// zod → JSON Schema (using zod-to-json-schema)
// npm install zod-to-json-schema
import { z } from 'zod'
import { zodToJsonSchema } from 'zod-to-json-schema'
const UserSchema = z.object({
name: z.string().min(1),
age: z.number().int().min(0),
})
const jsonSchema = zodToJsonSchema(UserSchema)
console.log(JSON.stringify(jsonSchema, null, 2))
// Outputs a standard JSON Schema object compatible with Ajv and other validators
// JSON Schema → Zod (using json-schema-to-zod)
// npm install json-schema-to-zod
import { jsonSchemaToZod } from 'json-schema-to-zod'
const zodCode = jsonSchemaToZod({ type: 'object', properties: { name: { type: 'string' } } })
// Returns a string of Zod code you can copy into your projectThe generated JSON Schema from zodToJsonSchema includes standard keywords like type, properties, required, minLength, and minimum — all valid JSON Schema draft-07. This output can be pasted directly into an OpenAPI spec or used with Jsonic's JSON Schema Validator to test against sample data. For more on JSON Schema itself, see our Ajv validation guide.
Zod vs JSON Schema — when to use each
Zod and JSON Schema are complementary rather than competing. Zod is optimized for TypeScript developer experience: schemas are written in code, type inference is automatic, IDE autocompletion is excellent, and the learning curve is minimal if you already know TypeScript. JSON Schema is a language-agnostic specification written in JSON, making it the standard for cross-language API contracts, OpenAPI documentation, and validation in Python, Java, Go, and other ecosystems. The decision often comes down to audience: if every consumer of your schema is a TypeScript project, Zod wins on ergonomics. If the schema needs to be readable and usable by non-TypeScript systems, JSON Schema is the right format.
| Zod | JSON Schema | |
|---|---|---|
| TypeScript types | Inferred automatically | Requires separate type definition |
| Runtime validation | Yes (primary use) | Yes (via Ajv, etc.) |
| Language | TypeScript/JavaScript only | Language-agnostic |
| IDE autocomplete | Excellent | Good |
| Cross-language APIs | No | Yes |
| Learning curve | Low | Medium |
Use Zod when: your codebase is TypeScript and you want types and validation in one place. Use JSON Schema when: you need a language-agnostic schema (API docs, Python validation, multi-language teams). Both can coexist in the same project — use Zod internally and generate JSON Schema from it using zod-to-json-schema for API documentation.
Frequently asked questions
What is Zod and how does it relate to JSON Schema?
Zod is a TypeScript-first schema declaration and validation library with 40M+ weekly npm downloads. Like JSON Schema, it defines the expected structure of data and validates input against that structure at runtime. Unlike JSON Schema (which is language-agnostic), Zod is TypeScript-specific and automatically infers TypeScript types from schemas using z.infer<typeof Schema>. The two can be converted using the zod-to-json-schema and json-schema-to-zod npm packages.
How do I validate JSON data with Zod?
Define a schema with z.object(), z.array(), or other Zod types, then call schema.safeParse(data). safeParse() returns { success: true, data } if validation passes, or { success: false, error } with detailed error messages if it fails. The data in a successful result is typed exactly as defined by the schema — no type assertion needed. Use parse() instead of safeParse() if you want Zod to throw a ZodError on failure.
How do I convert a JSON Schema to Zod?
Use the json-schema-to-zod npm package: install it with npm install json-schema-to-zod, then call jsonSchemaToZod(schema) with your JSON Schema object. The function returns a string of Zod code you can paste into your TypeScript project. For the reverse direction (Zod to JSON Schema), use zod-to-json-schema: install with npm install zod-to-json-schema, then call zodToJsonSchema(ZodSchema) to get a standard JSON Schema object compatible with Ajv and Jsonic's JSON Schema Validator.
Should I use Zod or JSON Schema for TypeScript projects?
Use Zod for TypeScript projects where you want runtime validation and compile-time types from a single source of truth. Zod schemas produce TypeScript types via z.infer<typeof Schema>, eliminating duplicate interface definitions. Use JSON Schema when your schema needs to work across languages (Python, Java, Go) or when you're documenting an API contract (OpenAPI specs use JSON Schema). Both can coexist: generate JSON Schema from Zod using zod-to-json-schema for API docs while using Zod internally.
How do I use Zod to validate API responses in TypeScript?
Define a Zod schema for the expected response shape, then call schema.safeParse(await response.json()). If the parse fails, the error contains field-level messages: result.error.errors is an array of ZodIssue objects, each with a path (the failing field) and message. This pattern catches API changes that break your assumptions at the boundary rather than silently propagating wrong types through your application. Use z.array(ItemSchema) for JSON arrays and z.object() for JSON objects.
How do I validate optional and nullable fields with Zod?
z.optional() means the field can be missing (undefined): z.string().optional() accepts string | undefined. z.nullable() means the field can be null: z.string().nullable() accepts string | null. Combine them with z.string().nullish() to accept string | null | undefined. For default values, use .default('value') — this returns the default when the input is undefined. For API responses that sometimes omit a field, .optional() is usually the right choice; for API responses that return null explicitly, use .nullable().
Validate your JSON Schema online
Paste any JSON and schema into Jsonic's validator to check compliance instantly — no install required.
Open JSON Schema Validator