Deterministic, schema-driven mock data for Zod v4.
Same seed → same data, always. Field names like email, firstName, and createdAt automatically produce realistic values. Schemas can declare relations so IDs stay consistent across multiple API shapes — without any manual wiring.
import { z } from "zod"; import { generate } from "zod4-mock"; const user = generate( z.object({ id: z.uuid(), firstName: z.string(), email: z.email(), role: z.enum(["admin", "user"]), createdAt: z.date(), }), ); // → { id: "3f2d…", firstName: "John", email: "john.smith42@…", role: "user", createdAt: Date }
No setup, no seed, no configuration. Field names drive the values — firstName gets a first name, email gets a valid email address, id gets a UUID.
npm install zod4-mock zod@^4
For tests, pin a seed so every run produces identical data:
import { createWorld } from "zod4-mock"; const world = createWorld({ seed: 42 }); const users = world.generate(z.array(UserSchema).min(5).max(20)); // Byte-identical output every run, every machine
Register matchers to control specific fields. ctx.gen gives you the full generator library with the PRNG already applied — no need to pass prng anywhere:
const world = createWorld({ seed: 42 }).withSchema(ProductSchema, { matchers: { name: (ctx) => ctx.gen.commerce.productName(), sku: (ctx) => `SKU-${ctx.gen.string.alphanumeric(6)}`, price: (ctx) => ctx.prng.int(100, 50_000), // cents, custom range }, }); const products = world.generate(z.array(ProductSchema).min(10));
Any field without a matcher falls through automatically: key-name heuristics first, then Zod type introspection.
Declare relations between schemas to keep foreign keys coherent across different API shapes:
const world = createWorld({ seed: 42 }) .withSchema(PersonSchema) .withSchema(DocumentSchema, { relations: { author: PersonSchema }, matchers: { authorId: (ctx) => ctx.related("author").personId, title: (ctx) => ctx.gen.word.sentence(), }, }); const people = world.generate(z.array(PersonSchema).min(3)); const documents = world.generate(z.array(DocumentSchema).min(10)); // documents[*].authorId ∈ people[*].personId — guaranteed
When two API shapes represent the same underlying entity, bind the second to the first with from. The source entity's data is available as ctx.source:
const world = createWorld({ seed: 42 }) .withSchema(PersonSchema) .withSchema(PersonSummarySchema, { from: PersonSchema, matchers: { id: (ctx) => ctx.source.personId, displayName: (ctx) => `${ctx.source.firstName} ${ctx.source.lastName}`, initials: (ctx) => `${ctx.source.firstName[0]}${ctx.source.lastName[0]}`, }, }); const people = world.generate(z.array(PersonSchema).min(5)); const summaries = world.generate(z.array(PersonSummarySchema)); // people[0].personId === summaries[0].id — always
Register matchers for a schema once — they apply automatically wherever that schema appears, including nested inside other schemas:
const world = createWorld({ seed: 42 }) .withSchema(AddressSchema, { matchers: { street: (ctx) => ctx.gen.location.street(), city: (ctx) => ctx.gen.location.city(), country: (ctx) => ctx.gen.location.countryCode(), }, }) .withSchema(PersonSchema); // has address: AddressSchema // PersonSchema's address field uses AddressSchema's matchers automatically const person = world.generate(PersonSchema);
Out of the box, zod4-mock produces simple English data — short curated name/word lists, no extras to install. For realistic, Markov-generated output (or a different language), install a locale package and pass it to createWorld:
npm install @zod4-mock/locale-en # rich English (Markov names + words) npm install @zod4-mock/locale-nl # Dutch (Markov names, tussenvoegsels, € prices)
import { createWorld } from "zod4-mock"; import { en } from "@zod4-mock/locale-en"; import { nl } from "@zod4-mock/locale-nl"; createWorld({ seed: 42 }); // built-in minimal English createWorld({ seed: 42, locale: en }); // full English, Markov-generated createWorld({ seed: 42, locale: nl }); // Dutch
Build custom locales by overriding individual sections with extend() — see the API reference.
generate(schema) works with no setup, no imports beyond the schemaemail, firstName, createdAt, userId, street, iban, vin, and 150+ more auto-generate realistic values.min(), .max(), .email(), .uuid(), z.enum(), z.union(), z.optional(), z.discriminatedUnion(), and morectx.gen — full generator library (person, internet, location, finance, commerce, …) pre-wired to the PRNG inside matchers@zod4-mock/locale-en, @zod4-mock/locale-nl) and extend() helper for variantszod@^4src/ — library corepackages/locale-core, packages/locale-en, packages/locale-nl — optional locale packagesplayground/ — vitest scratch surface for trying library APIssite/ — SvelteKit homepage (/, /bench, /showcase, /table, /docs),
deployed at gen-bench.vercel.app. Imported from the standalone gen-bench
repo on 2026-06-03. To deploy, the Vercel project needs Root directory set
to site and Install command pnpm install --frozen-lockfile; see
wiki/backlog/inbox/B82-vercel-deploy-from-site-subdir.md.docs/ — shipped user-facing reference (the URLs above)wiki/ — build-time spec / Vibin workflow source of truthMIT