Documentation

Getting Started

Pass a Zod schema, get realistic mock data back. The whole library is one idea: your schema already describes the shape and the field names, so it has enough information to fill itself in.

One complete example

Here is everything you need. Define a schema, call generate, and you get a fully populated value — field names drive the output, so firstName becomes a first name, email a valid address, createdAt a realistic date. The example below is type-checked at build time, and its type tokens link straight into the API reference:

import { generate } from "zod4-mock";
import { z } from "zod";

const User = z.object({
  id: z.uuid(),
  firstName: z.string(),
  lastName: z.string(),
  email: z.email(),
  role: z.enum(["admin", "user", "viewer"]),
  createdAt: z.date(),
});

const users = generate(z.array(User).min(3).max(10));

Install

Two packages: the generator and zod@^4 (your schemas). Node 18+ and TypeScript 5.4+:

pnpm add zod4-mock zod

That is the zero-config path. The variations below are different ways to do the same thing when you need more control — pick whichever one fits; each is complete on its own.

Pin a seed for reproducible data

Wrap generation in a world and pass it a seed. A world is a single generation session, and the seed is the one number that determines its output: the same seed yields the same data on every run and every machine. Build one per test file and reuse it.

import { createWorld } from "zod4-mock";
import { z } from "zod";

const User = z.object({
  id: z.uuid(),
  firstName: z.string(),
  email: z.email(),
});

const world = createWorld({ seed: 42 });
const users = world.generate(z.array(User).min(5));

Everything else you can pass to createWorld — optional-field probability, default array length, locale — is a default for that session. The seed is the only one you usually need.

Take over a field with matchers

When a field needs a specific shape, register a matcher for it. Each matcher receives a ctx with the seeded PRNG already applied (ctx.prng) and the full generator library (ctx.gen). Fields without a matcher fall through to the defaults — you only describe what you want to control.

import { createWorld } from "zod4-mock";
import { z } from "zod";

const Product = z.object({
  id: z.uuid(),
  name: z.string(),
  sku: z.string(),
  priceCents: z.number().int(),
});

const world = createWorld({ seed: 42 }).withSchema(Product, {
  matchers: {
    name: (ctx) => ctx.gen.commerce.productName(),
    sku: (ctx) => `SKU-${ctx.gen.string.alphanumeric(6)}`,
    priceCents: (ctx) => ctx.prng.int(100, 50_000),
  },
});

const products = world.generate(z.array(Product).min(10));

Keep foreign keys consistent with relations

To make one schema reference real records from another, declare a relation and read it in a matcher with ctx.related(name). Every generated post then points at a post author that actually exists in the dataset.

import { createWorld } from "zod4-mock";
import { z } from "zod";

const Author = z.object({
  authorId: z.uuid(),
  name: z.string(),
});

const Post = z.object({
  id: z.uuid(),
  authorId: z.uuid(),
  title: z.string(),
});

const world = createWorld({ seed: 42 })
  .withSchema(Author)
  .withSchema(Post, {
    relations: { author: Author },
    matchers: {
      authorId: (ctx) => ctx.related("author").authorId,
    },
  });

const authors = world.generate(z.array(Author).min(3));
const posts = world.generate(z.array(Post).min(10));

See it in a real dataset

Here's one user record from the live showcase dataset — every field generated from its schema, seed-pinned and cross-consistent with the orders and reviews around it:

Where to go next

  • Concepts — how the world, registry, and generation pipeline work
  • API Reference — every exported function and type
  • Key-Based Field Heuristics — complete list of auto-generated field names
  • Recipes — deriving schemas, transforms, localization, and more copy-pasteable patterns