If you write TypeScript, you almost certainly use Zod for runtime validation.
Whether you're validating incoming webhooks, third-party REST/GraphQL API responses, or form submissions, Zod bridges the gap between compile-time TypeScript types and runtime reality.
The problem? Writing Zod schemas by hand for large, nested JSON payloads is tedious, repetitive, and error-prone.
If an API returns a 40-field payload with nested objects, arrays, UUIDs, and ISO timestamps, hand-crafting z.object({...}) line-by-line takes 20 minutes of boilerplate typing.
In this guide, we'll look at how to automatically generate production-ready Zod schemas and inferred TypeScript types in seconds.
The Problem: Runtime Blind Spots
TypeScript's static types only exist at compile time. Once your code compiles to JavaScript and runs on a server or in a browser, static types vanish:
// ❌ Dangerous: TypeScript believes this is a User,
// but what if the API changes?
const response = await fetch(
"https://api.example.com/v1/user/101"
);
const user = (await response.json()) as User;
console.log(user.profile.address.city);
// 💥 Crashes at runtime if 'profile' is missing!
To prevent runtime crashes, we use Zod to validate the payload:
// ✅ Safe: Runtime validation guarantees type safety
const user = UserSchema.parse(await response.json());
Writing UserSchema manually for deeply nested objects is where developers lose time.
The Solution: Automatic Schema Generation
Instead of writing schemas from scratch, we can convert a sample JSON payload directly into modular Zod schemas.
Here's a real-world API response:
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"username": "alex_dev",
"email": "alex@example.com",
"created_at": "2026-03-21T14:30:00Z",
"is_active": true,
"role": "admin",
"profile": {
"avatar_url": "https://cdn.example.com/avatars/alex.png",
"bio": "Fullstack engineer building distributed systems",
"login_count": 142
},
"permissions": [
"read:users",
"write:users",
"deploy:prod"
]
}
1. Auto-Generating the Schema
You can use JSONGlow's Free JSON to Zod Converter, which runs 100% in your browser with zero server uploads.
When you paste this JSON, it automatically detects:
-
UUIDs: Maps
idtoz.string().uuid() -
Emails: Maps
emailtoz.string().email() -
ISO Dates: Maps
created_attoz.string().datetime() -
Nested Objects: Extracts
profileinto a standalone, reusableProfileSchema -
Arrays: Analyzes array contents and infers
z.array(z.string())
2. The Output Schema (user.schema.ts)
Here is the clean TypeScript code generated:
import { z } from "zod";
export const ProfileSchema = z.object({
avatar_url: z.string().url(),
bio: z.string(),
login_count: z.number(),
});
export const UserSchema = z.object({
id: z.string().uuid(),
username: z.string(),
email: z.string().email(),
created_at: z.string().datetime(),
is_active: z.boolean(),
role: z.string(),
profile: ProfileSchema,
permissions: z.array(z.string()),
});
// Infer TypeScript static types automatically
export type Profile = z.infer<typeof ProfileSchema>;
export type User = z.infer<typeof UserSchema>;
Notice how we get both the runtime validation schema and the compile-time TypeScript types from a single definition using z.infer<typeof ...>.
3 Pro-Tips for Production Zod Schemas
Tip 1: Always Use safeParse() for API Calls
Avoid .parse() when handling external API payloads because it throws an exception if validation fails.
Instead, use .safeParse() for graceful error handling:
import { UserSchema } from "./schemas/user.schema";
async function fetchUser(userId: string) {
const res = await fetch(
`https://api.example.com/v1/users/${userId}`
);
const json = await res.json();
const result = UserSchema.safeParse(json);
if (!result.success) {
console.error(
"API Contract Violation:",
result.error.format()
);
throw new Error(
"Invalid user payload received from upstream API."
);
}
// Fully typed and verified at runtime!
return result.data;
}
This gives you a clear validation boundary between your application and external data.
Tip 2: Use .strict() for Webhook Payloads
By default, Zod allows unknown properties to be stripped from parsed objects.
If you're validating secure incoming webhooks, such as Stripe, GitHub, or Shopify events, and want to reject unexpected keys, enable strict mode:
export const WebhookPayloadSchema = z
.object({
event: z.string(),
timestamp: z.number(),
})
.strict();
// Throws if extra unrecognized fields are present
This can be useful when you want your validation layer to enforce an exact payload contract.
Tip 3: Handling Nullable vs Optional Fields
One common source of bugs when generating schemas is confusing missing properties with explicit null values.
- If an API might omit a key completely, use
.optional():
z.string().optional()
- If an API explicitly returns
null, use.nullable():
z.string().nullable()
- If it can be missing or null, use
.nullish():
z.string().nullish()
Understanding this distinction is especially important when working with third-party APIs where response shapes aren't always consistent.
Try It Online
If you have an API response or database payload you need to validate today:
👉 JSONGlow JSON to Zod Converter
Why use it?
- ⚡ Zero-Telemetry — Runs 100% in your browser using WebAssembly/JavaScript, so your JSON doesn't need to be uploaded to a server.
- 📦 Instant Download — Export ready-to-import
.tsfiles with one click. - 🛡️ Format Detection — Automatically detects UUIDs, emails, URLs, and ISO datetimes.
- 🧩 Reusable Schemas — Nested objects are extracted into separate schemas for cleaner code.
Conclusion
Manually writing Zod schemas for large API responses can quickly become repetitive.
Generating the initial schema from real JSON gives you a much faster starting point while still keeping the benefits of runtime validation and TypeScript inference.
The workflow is simple:
JSON API response → Generated Zod schema → Runtime validation → TypeScript types
You still control and review the generated schema, but you don't have to spend time writing hundreds of lines of repetitive boilerplate.
If you're working with TypeScript APIs regularly, it's worth adding schema generation to your development workflow.
Discussion
How are you currently managing runtime validation in your TypeScript stack?
Do you prefer Zod, Valibot, ArkType, or TypeBox?
Drop your thoughts below! 👇
Top comments (0)