Zod logo

Metadata and registries

It's often useful to associate a schema with some additional metadata for documentation, code generation, AI structured outputs, form validation, and other purposes.

Registries

Metadata in Zod is handled via registries. Registries are collections of schemas, each associated with some strongly-typed metadata. To create a simple registry:

import * as z from "zod";
 
const myRegistry = z.registry<{ description: string }>();

To register, lookup, and remove schemas from this registry:

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);

TypeScript enforces that the metadata for each schema matches the registry's metadata type.

myRegistry.add(mySchema, { description: "A cool schema!" }); // ✅
myRegistry.add(mySchema, { description: 123 }); // ❌

.register()

Note — This method is special in that it does not return a new schema; instead, it returns the original schema. No other Zod method does this! That includes .meta() and .describe() (documented below) which return a new instance.

Schemas provide a .register() method to more conveniently add it to a registry.

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

This lets you define metadata "inline" in your schemas.

const mySchema = z.object({
  name: z.string().register(myRegistry, { description: "The user's name" }),
  age: z.number().register(myRegistry, { description: "The user's age" }),
})

If a registry is defined without a metadata type, you can use it as a generic "collection", no metadata required.

const myRegistry = z.registry();
 
myRegistry.add(z.string());
myRegistry.add(z.number());

Metadata

z.globalRegistry

For convenience, Zod provides a global registry (z.globalRegistry) that can be used to store metadata for JSON Schema generation or other purposes. It accepts the following metadata:

export interface GlobalMeta {
  id?: string;
  title?: string;
  description?: string;
  examples?: T[]; // T is the output type of the schema you are registering
  [k: string]: unknown; // accepts other properties too
}

To register some metadata in z.globalRegistry for a schema:

import * as z from "zod";
 
const emailSchema = z.email().register(z.globalRegistry, { 
  id: "email_address",
  title: "Email address",
  description: "Your email address",
  examples: ["first.last@example.com"]
});

.meta()

For a more convenient approach, use the .meta() method to register a schema in z.globalRegistry.

const emailSchema = z.email().meta({ 
  id: "email_address",
  title: "Email address",
  description: "Please enter a valid email address",
});

Calling .meta() without an argument will retrieve the metadata for a schema.

emailSchema.meta();
// => { id: "email_address", title: "Email address", ... }

.describe()

The .describe() method still exists for compatibility with Zod 3, but .meta() is now the recommended approach.

The .describe() method is a shorthand for registering a schema in z.globalRegistry with just a description field.

const emailSchema = z.email();
emailSchema.describe("An email address");
 
// equivalent to
emailSchema.meta({ description: "An email address" });

Custom registries

You've already seen a simple example of a custom registry:

import * as z from "zod";
 
const myRegistry = z.registry<{ description: string };>();

Let's look at some more advanced patterns.

Referencing inferred types

It's often valuable for the metadata type to reference the inferred type of a schema. For instance, you may want an examples field to contain examples of the schema's output.

import * as z from "zod";
 
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] });

The special symbol z.$output is a reference to the schemas inferred output type (z.infer<typeof schema>). Similarly you can use z.$input to reference the input type.

Constraining schema types

Pass a second generic to z.registry() to constrain the schema types that can be added to a registry. This registry only accepts string schemas.

import * as z from "zod";
 
const myRegistry = z.registry<{ description: string }, z.ZodString>();
 
myRegistry.add(z.string(), { description: "A number" }); // ✅
myRegistry.add(z.number(), { description: "A number" }); // ❌ 
//             ^ 'ZodNumber' is not assignable to parameter of type 'ZodString' 

On this page