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