Capítulo 35 de 61

Chapter 35: Matching an Existing Type

Core Idea

z.toZod<T>() checks, at compile time, that a schema's output type is exactly T — stricter than the common satisfies z.ZodType<T> pattern, which only checks assignability and lets extra keys or a bare z.any() slip through.

Key Concepts

  • z.toZod<T>()(schema): returns schema unchanged at runtime; at compile time, requires the schema's output type to be exactly T — no missing keys, no extra keys, no type drift.
  • satisfies z.ZodType<T> is weaker: it only checks that the schema is assignable to T, so an object schema with an extra key, or even z.any(), silently passes.
  • Exactness is structural on the type as written: an intersection type (A & B) matches a schema built with .and(), not one built with .safeExtend() — because TypeScript doesn't consider those two constructions the same type even though their resulting shapes look identical; flattening the target type ({ [K in keyof T]: T[K] } & {}) makes .safeExtend() match instead.

Code Examples

type Player = { username: string; xp: number };

const PlayerSchema = z.toZod<Player>()(
  z.object({ username: z.string(), xp: z.number() })
);
// compiles fine — output type matches exactly

z.toZod<Player>()(
  z.object({ username: z.string(), xp: z.number(), admin: z.boolean() })
); // compile error — extra key "admin"

// satisfies is weaker: extra keys and z.any() both slip through
z.object({ username: z.string(), xp: z.number(), admin: z.boolean() })
  satisfies z.ZodType<Player>; // no error!

z.any() satisfies z.ZodType<Player>; // also no error!
  • What it demonstrates: z.toZod<T>() catches drift that satisfies z.ZodType<T> does not.

Anti-patterns

  • Relying on satisfies z.ZodType<T> to guarantee an exact match: it only checks assignability, so it won't catch an accidental extra field or an overly permissive z.any().

Key Takeaways

  1. When you already own the TypeScript type (a DB model, generated client type, external interface) and need a schema that matches it exactly, z.toZod<T>() is stricter and safer than satisfies.
  2. z.toZod<T>() is a compile-time-only check — the schema itself is returned unmodified at runtime.
  3. Intersection types built via .and() and object extension built via .safeExtend() are not interchangeable for exactness checking — flatten the target type if you need .safeExtend() to match.

Connects To

  • Basic Usage — Define, Parse, Handle Errors, Infer: the more common direction (schema first, type derived via z.infer) that this chapter's approach inverts.
  • Objects — Extending & Deriving: .and() vs .safeExtend(), relevant to how exactness checking treats each.