Registries overview
Metadata in Zod is handled via registries. Registries are collections of schemas, each associated with strongly-typed metadata. Create a registry using z.registry<{ /* metadata type */ }>(). Registries support four operations: add() to register a schema with metadata, has() to check if a schema is registered, get() to retrieve metadata for a schema, and remove() to unregister a schema. The clear() method wipes the entire registry.
Registry type safety
TypeScript enforces that metadata for each schema matches the registry's metadata type. Attempting to register a schema with incorrect metadata types will result in a TypeScript error.
Registry special handling for id
Zod registries treat the id property specially. An Error will be thrown if multiple schemas are registered with the same id value. This is true for all registries, including the global registry.
Schema .register() method
Schemas provide a .register() method that adds the schema to a registry and returns the original schema unchanged. This is unique among Zod methods because it does not return a new schema instance; all other Zod methods return new instances. This allows defining metadata inline in schema chains.
Registry without metadata type
A registry can be created without a metadata type using z.registry() with no generic argument. This creates a generic collection where schemas can be added without requiring metadata.
GlobalMeta interface
Zod provides z.globalRegistry that accepts metadata matching the GlobalMeta interface. The interface includes: id (optional, string), title (optional, string), description (optional, string), deprecated (optional, boolean), and [k: string]: unknown for any additional properties.
Augment GlobalMeta with declaration merging
To add new fields to the GlobalMeta interface, use TypeScript declaration merging. Add a module declaration for 'zod' with an interface GlobalMeta containing your new fields. A common convention is to create a zod.d.ts file in the project root and add the declaration there.
.meta() method
The .meta() method registers a schema in z.globalRegistry with metadata. In Zod, call .meta() with an object containing metadata fields. In Zod Mini, wrap the metadata in z.meta() and pass it to .check(). Calling .meta() without arguments retrieves the metadata for a schema.
Metadata is instance-specific
Metadata is associated with a specific schema instance. Since Zod methods are immutable and return new instances, metadata does not propagate to schemas derived from refinements or other operations. For example, if A is a string with metadata and B is A.refine(...), B.meta() will return undefined.
.describe() method
The .describe() method is a shorthand for registering a schema in z.globalRegistry with only a description field. In Zod, call .describe("text"). In Zod Mini, wrap the description in z.describe() and pass it to .check(). The .describe() method remains available but .meta() is the recommended approach.
Custom registry metadata types with z.$output
When defining custom registry metadata types, use the special symbol z.$output to reference the inferred output type of a schema, equivalent to z.infer<typeof schema>. Similarly, use z.$input to reference the input type. This allows metadata types to be generic over the schema's inferred types.
Constrain registry to specific schema types
Pass a second generic argument to z.registry() to constrain which schema types can be added to the registry. For example, z.registry<{ description: string }, z.ZodString>() only accepts string schemas. TypeScript will error if an incompatible schema type is added.
Example: basic registry usage
const myRegistry = z.registry<{ description: string }>();
const mySchema = z.string();
myRegistry.add(mySchema, { description: "A cool schema!" });
myRegistry.has(mySchema); // => true
myRegistry.get(mySchema); // => { description: "A cool schema!" }
myRegistry.remove(mySchema);
myRegistry.clear();
Example: inline schema registration with .register()
const mySchema = z.object({
name: z.string().register(myRegistry, { description: "The user's name" }),
age: z.number().register(myRegistry, { description: "The user's age" }),
});
Example: globalRegistry with .register()
const emailSchema = z.email().register(z.globalRegistry, {
id: "email_address",
title: "Email address",
description: "Your email address",
examples: ["first.last@example.com"]
});
Example: .meta() in Zod Mini
const emailSchema = z.email().check(
z.meta({
id: "email_address",
title: "Email address",
description: "Please enter a valid email address",
})
);
Example: custom registry with z.$output
type MyMeta = { examples: z.$output[] };
const myRegistry = z.registry<MyMeta>();
myRegistry.add(z.string(), { examples: ["hello", "world"] });
myRegistry.add(z.number(), { examples: [1, 2, 3] });
Example: constrained registry
const myRegistry = z.registry<{ description: string }, z.ZodString>();
myRegistry.add(z.string(), { description: "A string" }); // ✅
myRegistry.add(z.number(), { description: "A number" }); // ❌