JSON Schema vs TypeScript types

TypeScript checks your code at compile time. JSON Schema checks values at runtime. Here's how to decide which you need — and when you need both.

6 min read

JSON Schema and TypeScript both describe the shape of data, but they answer different questions. TypeScript checks your code at compile time. JSON Schema checks values at runtime. If you've ever had a valid-looking API response crash your app, you already understand why picking one over the other is usually the wrong call — most production systems need both.

The one-sentence difference

TypeScript types describe the values your code believes exist. JSON Schema describes the values that actually arrive. Types are erased at build time; schemas run in production.

When TypeScript is enough

  • Data you generate yourself and never send over a network.
  • Fully typed RPC layers (tRPC, gRPC with codegen).
  • Internal utilities where the caller and callee live in the same repo.

In these cases the compiler already guarantees the shape. Adding a runtime schema is dead weight.

When you need JSON Schema (or a validator)

  • Any HTTP request body — REST, webhooks, form submissions.
  • External API responses. "It worked in staging" is not a type guarantee.
  • Config files loaded from disk or environment variables.
  • Message-queue payloads produced by another service or team.

At every one of these boundaries, TypeScript has already been stripped. A runtime check is the only thing standing between bad data and a production incident.

Generating one from the other

You almost never want to hand-write both. Pick a source of truth:

  • Schema first — write JSON Schema, generate TS types with json-schema-to-typescript. Best when a schema already exists (OpenAPI, AsyncAPI, an industry spec).
  • Types first with a runtime library — Zod, Valibot, or ArkType let you declare a schema in TypeScript and get both the type and the validator from one definition. Best for greenfield apps.
  • Sample first — paste an example response into our JSON to JSON Schema or JSON to TypeScript tool to bootstrap either shape in a few seconds, then refine by hand.

Where each format wins

ConcernTypeScriptJSON Schema
Compile-time safetyYesOnly via codegen
Runtime validationNoYes (with Ajv, etc.)
Language-agnostic contractNoYes
Regex, min/max, enum constraintsLimited (template literals)First-class
Discriminated unionsExcellentGood (oneOf + discriminator)
IDE autocompletionExcellentOnly through generated types

A practical recipe

  1. Define a Zod (or Valibot) schema next to every public endpoint.
  2. Derive the TypeScript type with z.infer<typeof schema>so the compiler and validator stay in sync.
  3. For third-party APIs you don't control, capture a real response and generate a schema from it, then tighten required and types by hand.
  4. Validate at the edge — request handlers, queue consumers, config loaders.
  5. Trust the type inside your app. Once data has crossed a validator, your business logic gets to assume it's well-formed.

Common mistakes

  • Casting instead of parsing. data as User is a lie the compiler can't check. Parse.
  • Trusting JSON.parse. It returns any — the shape is entirely unchecked.
  • Duplicating schemas by hand. Every drift between the runtime and compile-time definition is a latent bug.
  • Skipping validation because "we control the client". Anyone can craft an HTTP request. The client is always adversarial.

Bottom line

Use TypeScript for what your code believes. Use a schema (JSON Schema or a runtime library) for what your code receives. Generate one from the other so they can't drift. That's the whole strategy.

Try the tools

Related reading