DEV Community

Tushar Jagtap
Tushar Jagtap

Posted on Originally published at jsonglow.com AI-assisted

How to Auto-Generate Zod Schemas & TypeScript Types from JSON APIs

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!
Enter fullscreen mode Exit fullscreen mode

To prevent runtime crashes, we use Zod to validate the payload:

// ✅ Safe: Runtime validation guarantees type safety

const user = UserSchema.parse(await response.json());
Enter fullscreen mode Exit fullscreen mode

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"
  ]
}
Enter fullscreen mode Exit fullscreen mode

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 id to z.string().uuid()
  • Emails: Maps email to z.string().email()
  • ISO Dates: Maps created_at to z.string().datetime()
  • Nested Objects: Extracts profile into a standalone, reusable ProfileSchema
  • 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>;
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode
  • If an API explicitly returns null, use .nullable():
z.string().nullable()
Enter fullscreen mode Exit fullscreen mode
  • If it can be missing or null, use .nullish():
z.string().nullish()
Enter fullscreen mode Exit fullscreen mode

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 .ts files 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)