packages/docs/content/metadata.mdx
import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; import { Callout } from "fumadocs-ui/components/callout"
It's often useful to associate a schema with some additional metadata for documentation, code generation, AI structured outputs, form validation, and other purposes.
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);
myRegistry.clear(); // wipe registry
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 }); // ❌
Special handling for
id— Zod registries treat theidproperty specially. AnErrorwill be thrown if multiple schemas are registered with the sameidvalue. This is true for all registries, including the global registry.
.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" }),
})
const myRegistry = z.registry();
myRegistry.add(z.string());
myRegistry.add(z.number());
z.globalRegistryFor 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;
deprecated?: boolean;
[k: string]: unknown;
}
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: ["[email protected]"]
});
To globally augment the GlobalMeta interface, use declaration merging. Add the following anywhere in your codebase. Creating a zod.d.ts file in your project root is a common convention.
declare module "zod" {
interface GlobalMeta {
// add new fields here
examples?: unknown[];
}
}
// forces TypeScript to consider the file a module
export {}
.meta()For a more convenient approach, use the .meta() method to register a schema in z.globalRegistry.
<Tabs groupId="lib" items={["Zod", "Zod Mini"]}> <Tab value="Zod">
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", ... }
Metadata is associated with a specific schema instance. This is important to keep in mind, especially since Zod methods are immutable—they always return a new instance.
const A = z.string().meta({ description: "A cool string" });
A.meta(); // => { description: "A cool string" }
const B = A.refine(_ => true);
B.meta(); // => undefined
.describe()The .describe() method is a shorthand for registering a schema in z.globalRegistry with just a description field.
<Tabs groupId="lib" items={["Zod", "Zod Mini"]}> <Tab value="Zod">
const emailSchema = z.email();
emailSchema.describe("An email address");
// equivalent to
emailSchema.meta({ description: "An email address" });
// equivalent to z.email().check(z.meta({ description: "An email address" }));
</Tab>
</Tabs>
## Custom registries
You've already seen a simple example of a custom registry:
```ts
import * as z from "zod";
const myRegistry = z.registry<{ description: string };>();
Let's look at some more advanced patterns.
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.
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'