logo
Basic Utils
Home
postiz
  • Zod v4 Metadata Handling With Examples

    Table of Contents

    1. Introduction
    2. Use Cases: Where and Why You Might Want to Attach Zod Metadata
    3. Basic Metadata in Zod Using .describe() Method in Zod v4
    4. Advanced Metadata with z.registry() and z.globalRegistry in Zod v4
    5. Using z.globalRegistry in Zod v4
    6. Using .meta() in Zod v4
    7. Exporting Zod Schemas as JSON Schema using toJSONSchema() in in Zod v4
    8. Conclusion
    zod-logo
    zod-logo
    basicutils.com

    Introduction

    Metadata in Zod refers to secondary information that you add to schemas without affecting validation or parsing behavior. It's useful in scenarios where you want to provide extra data—such as documentation, forms, and UIs—anywhere you need to include additional context like labels, descriptions, or example values.

    Zod v4 comes enhanced with powerful metadata handling capabilities. In this article/tutorial, we will explore the functionality provided by Zod v4 for working with metadata.

    Use Cases: Where and Why You Might Want to Attach Zod Metadata

    For Documentation

    One of the most common use cases for metadata in Zod is documentation generation. When you're building an API, you might want to document your data validation logic. Metadata like descriptions, examples, and field names can be used to generate structured documentation automatically.

    Example: If you're building an API with Zod and using tools like OpenAPI or GraphQL, you might want to document what each field means or provide example values for request and response bodies. By attaching metadata to schemas, you can automatically generate API documentation that includes human-readable descriptions for each field.

    Example:

    const userSchema = z.object({
      username: z.string().describe("The user's unique username"),
      email: z.string().email().describe("The user's email address"),
    });
    

    This metadata can then be used by documentation generators to provide clearer, richer documentation for your API.

    For Form Generation Tools (e.g., Labels, Placeholders)

    Another key use case for metadata in Zod is dynamic form generation. Many form generation tools (like React Hook Form or custom UI libraries) need field labels, placeholders, or additional information to render forms correctly. By attaching metadata to Zod schemas, you can easily retrieve that information when rendering a form.

    Example: In an application where users fill out forms (e.g., registration, profile updates), metadata can be used to provide labels and placeholders for form fields:

    const formSchema = z.object({
      username: z.string().describe("Enter your username"),
      email: z.string().email().describe("Enter your email address"),
    });
    

    With this metadata, your form rendering logic can use the descriptions to automatically populate field labels and tooltips, improving the user experience.

    For Code Generation

    When generating code based on schemas, metadata can guide the generation process. For example, in scenarios where schemas are used to generate TypeScript types or client libraries, metadata can specify additional constraints or annotations that influence the structure and behavior of the generated code.

    For UI Tools (e.g., Tooltips, Descriptions)

    In user interfaces, metadata can be leveraged to provide additional tooltips, hints, or descriptions that help guide users through forms or other input fields.

    Basic Metadata in Zod Using .describe() Method in Zod v4

    The .describe() method is the simplest and most direct way to attach metadata to a Zod schema.

    🧪 Syntax Example

    const schema = z.string().describe("A user-provided name");
    

    In this example, the schema still enforces that the value must be a string, but now it also carries the descriptive label: "A user-provided name".

    What .describe() Does:

    • It does not affect validation in any way.
    • The schema behaves identically at runtime, whether or not a description is attached.
    • The description is stored in the internal .description property of the schema instance and can be accessed when needed.

    Advanced Metadata with z.registry() and z.globalRegistry in Zod v4

    While Zod’s .describe() method allows you to attach basic string metadata to a schema, complex applications often require richer, structured metadata. That’s where z.registry() comes in. z.registry() is a feature in Zod that allows you to associate rich, strongly typed metadata with schemas.

    Referencing Inferred Types: z.$output and z.$input

    HelperDescription
    z.$outputInferred output type of the schema (after parsing or transformation)
    z.$inputInferred input type (before transformation)

    Examples

    Example: Type-Safe Examples Field

    Let’s define metadata for schemas where we want to include example values that must match the schema’s output type.

    import { z } from "zod";
    // Define the metadata structure
    type MyMeta = {
      examples: z.$output[]; // examples must match schema output type
    };
    // Create a registry instance
    const myRegistry = z.registry<MyMeta>();
    // Add schemas and their metadata
    myRegistry.add(z.string(), { examples: ["hello", "world"] });
    myRegistry.add(z.number(), { examples: [1, 2, 3] });
    

    Explanation:

    Because of z.$output[], the examples field is type-safe:

    • If you try to add string examples to a number schema, TypeScript will throw a type error.
    • If you use a schema with transformation (like .transform()), the metadata type will match the transformed output.

    Example: Handling Transformations with $output

    const transformed = z.string().transform((val) => parseInt(val));
    // Output type is number, not string
    myRegistry.add(transformed, { examples: [123, 456] }); // ✅ OK
    myRegistry.add(transformed, { examples: ["123"] });    // ❌ Type error
    

    Here, z.$output resolves to number because .transform() changes the final result type.

    Constraining the Registry to Specific Schema Types

    You can also restrict what kinds of schemas are allowed in the registry. For instance, if you're building a form generator that only supports strings:

    const stringOnlyRegistry = z.registry<MyMeta, z.ZodString>();
    stringOnlyRegistry.add(z.string(), { examples: ["name", "title"] }); // ✅
    stringOnlyRegistry.add(z.number(), { examples: [1] }); // ❌ Error: not a ZodString
    

    This ensures the registry is strictly typed, both in the metadata and in the allowed schema types.

    Using z.globalRegistry in Zod v4

    While .describe() is great for adding simple string descriptions, it falls short for more sophisticated metadata. For such cases, z.globalRegistry provides a powerful tool that allows you to globally manage schemas and their metadata in a centralized registry.

    Core Methods of z.globalRegistry

    MethodDescription
    .add(schema, metadata)Adds a schema to the global registry with metadata.
    .get(schema)Retrieves metadata for the given schema.
    .has(schema)Checks if the schema exists in the global registry.
    .remove(schema)Removes the schema and its metadata from the global registry.

    Adding schemas to z.globalRegistry

    import { z } from "zod";
    // Define a User schema
    const UserSchema = z.object({
      id: z.number().int().positive(),
      name: z.string().min(1),
      email: z.string().email()
    });
    // Register it globally with metadata
    z.globalRegistry.add(UserSchema, {
      id: "userSchema",
      title: "User Object",
      description: "Represents a single registered user",
      examples: [
        { id: 1, name: "Alice", email: "alice@example.com" }
      ]
    });
    // Define a Product schema
    const ProductSchema = z.object({
      sku: z.string().min(5),
      name: z.string(),
      price: z.number().nonnegative()
    });
    // Register the product schema
    z.globalRegistry.add(ProductSchema, {
      id: "productSchema",
      title: "Product",
      description: "Information about a product",
      examples: [
        { sku: "PRD-12345", name: "Monitor", price: 299.99 }
      ]
    });
    

    Getting schemas from z.globalRegistry

    console.log(z.globalRegistry.get(UserSchema));
    

    Using .meta() in Zod v4

    The .meta() method allows you to attach arbitrary metadata to a schema, including custom fields, descriptions, titles, and examples. Unlike .describe(), which is limited to a string, .meta() accepts any arbitrary object as metadata.

    What .meta() Does

    • Accepts an object with any shape — it's not restricted to predefined keys.
    • Stores metadata in a .metadata internal property.
    • Does not affect validation or parsing behavior at runtime.

    Example:

    // Define a schema and attach metadata using .meta()
    const schemaWithMeta = z.string().meta({
      title: "Username",
      description: "Must be a unique user name",
      examples: ["alice", "bob"],
      customField: "extra metadata is allowed"
    });
    // Access the metadata from the schema
    console.log(schemaWithMeta.meta());
    // Output:
    // {
    //   title: "Username",
    //   description: "Must be a unique user name",
    //   examples: ["alice", "bob"],
    //   customField: "extra metadata is allowed"
    // }
    

    Exporting Zod Schemas as JSON Schema using toJSONSchema() in in Zod v4

    Zod provides a toJSONSchema() function that converts your Zod schema into JSON Schema, making it easy to integrate with tools like OpenAPI, Swagger, or any system that uses standard JSON schema definitions. Any metadata in z.globalRegistry is automatically included in the JSON Schema output.

    Usage Example

    import { z } from "zod";
    const schema = z.object({
      id: z.number().describe("A positive numeric ID"),
      name: z.string().describe("User name"),
    });
    const jsonSchema = z.toJSONSchema(schema);
    console.log(JSON.stringify(jsonSchema, null, 2));
    

    Expected Output

    {
      "type": "object",
      "properties": {
        "id": {
          "description": "A positive numeric ID",
          "type": "number"
        },
        "name": {
          "description": "User name",
          "type": "string"
        }
      },
      "required": [
        "id",
        "name"
      ]
    }
    

    Conclusion

    Zod’s metadata system—especially when combined with z.registry() and the powerful z.$output and z.$input helpers—unlocks a whole new level of expressiveness and type safety. It allows you to build rich developer tooling, dynamic UIs, and self-documented APIs while keeping everything aligned with your schema definitions.

    By structuring metadata around the actual inferred types of your schemas, you ensure consistency between your data models and their auxiliary information—like examples, placeholders, or labels. And with the ability to constrain and organize schemas in registries, your codebase stays predictable and maintainable.

    If you’re building anything that requires structured introspection of your schemas—like form generators, OpenAPI layers, or admin dashboards—Zod’s metadata features are well worth integrating into your workflow.

    References

    Background References

    1. (April 2, 2025). Metadata and registries. *Zod*. Retrieved April 2, 2025 from https://v4.zod.dev/metadata

    About the Author

    Joseph Horace's photo

    Joseph Horace

    Horace is a dedicated software developer with a deep passion for technology and problem-solving. With years of experience in developing robust and scalable applications, Horace specializes in building user-friendly solutions using cutting-edge technologies. His expertise spans across multiple areas of software development, with a focus on delivering high-quality code and seamless user experiences. Horace believes in continuous learning and enjoys sharing insights with the community through contributions and collaborations. When not coding, he enjoys exploring new technologies and staying updated on industry trends.

    logo
    Basic Utils

    simplify and inspire technology

    ©2024, basicutils.com