Capítulo 50 de 61

Chapter 50: Metadata & Registries

Core Idea

Registries associate strongly-typed metadata with schema instances — z.globalRegistry (with .meta() as its convenience method) is the built-in one for common fields like title/description, and custom registries let you define your own metadata shape, optionally constrained to specific schema types.

Key Concepts

  • z.registry<MetaType>(): creates a registry whose metadata must match MetaType; .add(schema, meta), .has(schema), .get(schema), .remove(schema), .clear() manage entries. z.registry() with no type argument acts as a plain schema collection with no metadata requirement.
  • id is special: every registry (including the global one) throws if two schemas are registered with the same id.
  • .register(registry, meta): the one Zod method that returns the original schema instance rather than a new one — every other method (including .meta(), .describe()) is immutable and returns a new instance.
  • z.globalRegistry: the built-in registry for { id?, title?, description?, deprecated?, [k: string]: unknown }; extend its type via TypeScript declaration merging (declare module "zod" { interface GlobalMeta { ... } }).
  • .meta({...}): convenience shortcut for .register(z.globalRegistry, {...}); calling it with no argument retrieves the schema's metadata. Metadata is tied to the specific instance — since Zod methods are immutable, a schema derived via .refine() etc. does not inherit the original's metadata.
  • .describe(text): shorthand for .meta({ description: text }); still available but .meta() is now the recommended general-purpose API.
  • z.$output / z.$input: special type references usable inside a custom registry's metadata type to reference the associated schema's inferred output/input type (e.g. an examples: z.$output[] field).
  • Constraining registry schema types: z.registry<MetaType, z.ZodString>() restricts what schema types can be added to the registry at all — attempting .add() with a non-matching schema type is a compile error.

Code Examples

const myRegistry = z.registry<{ description: string }>();
const mySchema = z.string();
myRegistry.add(mySchema, { description: "A cool schema!" });
myRegistry.get(mySchema); // => { description: "A cool schema!" }

// .register() returns the ORIGINAL schema, unlike every other Zod method
const named = z.object({
  name: z.string().register(myRegistry, { description: "The user's name" }),
});

// z.globalRegistry + .meta()
const emailSchema = z.email().meta({
  id: "email_address", title: "Email address", description: "Your email address",
});
emailSchema.meta(); // => { id: "email_address", title: "Email address", ... }

// metadata does NOT carry over to derived schemas
const A = z.string().meta({ description: "A cool string" });
const B = A.refine(_ => true);
B.meta(); // => undefined

// metadata type referencing the schema's inferred output
type MyMeta = { examples: z.$output[] };
const examplesRegistry = z.registry<MyMeta>();
examplesRegistry.add(z.string(), { examples: ["hello", "world"] });

// constraining which schema types a registry accepts
const stringOnlyRegistry = z.registry<{ description: string }, z.ZodString>();
stringOnlyRegistry.add(z.number(), { description: "nope" }); // compile error
  • What it demonstrates: creating and using a custom registry, the special .register()/immutability interaction, and type-level constraints on registry contents.

Anti-patterns

  • Expecting metadata to survive .refine()/.extend()/other chained methods: it doesn't — metadata is keyed to the exact schema instance, and immutable methods always return a new instance without it.

Key Takeaways

  1. .register() is the one exception to Zod's "everything returns a new instance" rule — use it specifically when you want to attach metadata without creating a fresh schema reference.
  2. Attach .meta() as the last step in a schema's construction, after all .refine()/.extend()/etc. calls, or the metadata won't be reachable from the final schema.
  3. Custom registries with z.$output/z.$input in their metadata type are the pattern for type-safe example values, default-generators, or anything else that needs to reference "the type this schema produces."

Connects To

  • JSON Schema — Conversion Functions: .meta() fields flow directly into z.toJSONSchema() output.
  • JSON Schema — Registries & Multi-Schema Output: z.globalRegistry used for multi-file JSON Schema generation.