Gityosan/zod-v4-mocks(opens in a new tab)
Language: TypeScript
Mock generator for zod v4
- stars
- 4
- watchers
- 0
- forks
- 0
- open issues
- 1
- Last updated
- 06/19/2026
Language: TypeScript
Mock generator for zod v4
Mock generator for zod v4
Note: If you're using
zod@3.25.76withimport from 'zod/v4'(preview), please use zod-v4-preview-mocks instead. It is compatible withzod-v4-mocks@1.2.1.
This library supports Zod v4.0.0+ only (v2.x).
Important: v2.0.0–v2.0.4 had compatibility issues with Zod versions older than v4.3.5. If you use Zod v4.0.0–v4.3.4, please upgrade to v2.1.0+ which properly supports all Zod v4.0.0+ versions.
For Zod v3.25.76 (v4 preview), use zod-v4-preview-mocks.
npm install zod-v4-mocks
import { initGenerator } from 'zod-v4-mocks'; import { z } from 'zod'; const schema = z.string(); const generator = initGenerator({ seed: 1 }); const res = generator.generate(schema); console.log(schema.parse(res));
The simplest way to use the library. Generate mock data from a schema.
import { z } from 'zod'; import { initGenerator } from 'zod-v4-mocks'; const basicSchema = z.object({ id: z.uuid(), name: z.string(), email: z.email(), age: z.number().min(18).max(120).optional(), isActive: z.boolean(), tags: z.array(z.string()), createdAt: z.date(), }); const mockUser = initGenerator().generate(basicSchema);
You can customize settings such as seed value, array length, and locale.
const config = { seed: 42, array: { min: 2, max: 5 }, locale: 'ja', } as const; const mockUserWithConfig = initGenerator(config).generate(basicSchema);
The available configuration options are as follows:
interface MockConfig { /** * @default [en, base] from faker.js */ locale?: LocaleType | LocaleType[]; /** * @default generateMersenne53Randomizer() from faker.js */ randomizer?: Randomizer; /** * @default 1 */ seed: number; /** * @default { min: 1, max: 3 } */ array: { min: number; max: number }; /** * @default { min: 1, max: 3 } */ map: { min: number; max: number }; /** * @default { min: 1, max: 3 } */ set: { min: number; max: number }; /** * @default { min: 1, max: 3 } */ record: { min: number; max: number }; /** * @default 0.5 */ optionalProbability: number; /** * @default 0.5 */ nullableProbability: number; /** * @default 0.5 */ defaultProbability: number; /** * @default 5 * @deprecated Use `recursiveDepthLimit` instead. */ lazyDepthLimit: number; /** * @default 5 * @description Max depth for recursive schemas (z.lazy / circular getters). */ recursiveDepthLimit?: number; /** * @description meta's attribute name which is used to generate consistent property value */ consistentKey?: string; /** * @default 'mock' * @description meta's attribute name read for z.custom() / z.instanceof(). */ customMockKey?: string; /** * @default 'off' * @description Map property names to faker functions for primitive leaves. * 'auto' uses built-in defaults; a KeyMapper function fully customizes. */ keyMapping?: 'off' | 'auto' | KeyMapper; /** * @default true * @description Run a pre-flight schema walk that rejects (or auto-fixes) * constructs the generator cannot safely mock. */ preflightCheck?: boolean; }
You can supply fixed values for specific schema types.
const mockUserWithSupply = initGenerator() .supply(z.ZodString, 'custom name') .supply(z.ZodEmail, 'custom_email@example.com') .generate(basicSchema);
If you configure multiple values for the same ZodType condition, the first configured value takes priority.
const mockUserWithSupply = initGenerator() .supply(z.ZodEmail, 'prioritized_email@example.com') .supply(z.ZodEmail, 'not_prioritized_email@example.com') .generate(basicSchema);
You can define custom generator functions for specific schemas.
import { type CustomGeneratorType } from 'zod-v4-mocks'; const customGenerator: CustomGeneratorType = (schema, options) => { const { faker } = options; if (schema instanceof z.ZodString && schema === basicSchema.shape.name) { return 'custom name: ' + faker.person.fullName(); } }; const mockUserWithOverride = initGenerator() .override(customGenerator) .generate(basicSchema);
If you configure multiple values for the same ZodType condition, the first configured value takes priority.
const customGenerator: CustomGeneratorType = (schema, options) => { const { faker } = options; if (schema instanceof z.ZodEmail) { return 'not_prioritized_email@example.com'; } }; const mockUserWithSupply = initGenerator() .supply(z.ZodEmail, 'prioritized_email@example.com') .override(customGenerator) .generate(basicSchema);
Supports complex schemas including nested objects, arrays, records, and unions.
const complexSchema = z.object({ user: z.object({ profile: z.object({ name: z.string(), bio: z.string().optional(), }), preferences: z.object({ theme: z.enum(['light', 'dark', 'auto']), notifications: z.boolean().default(true), }), }), posts: z.array( z.object({ title: z.string(), content: z.string(), publishedAt: z.date().nullable(), tags: z.array(z.string()), }), ), metadata: z.record( z.string(), z.union([z.string(), z.number(), z.boolean()]), ), }); const complexMock = initGenerator().generate(complexSchema);
You can generate consistent data between related fields using metadata.
// Set the metadata key name const consistentKey = 'name'; const UserId = z.uuid().meta({ [consistentKey]: 'UserId' }); const CommentId = z.uuid().meta({ [consistentKey]: 'CommentId' }); const PostId = z.uuid().meta({ [consistentKey]: 'PostId' }); const userSchema = z.object({ id: UserId, // 1: Same UserId will be generated name: z.string(), }); const commentSchema = z.object({ id: CommentId, postId: PostId, // 2: Same PostId will be generated user: userSchema, userId: UserId, // 1: Same UserId will be generated value: z.string(), }); const postSchema = z.object({ id: PostId, // 2: Same PostId will be generated comments: z.array(commentSchema), value: z.string(), }); const PostsResponse = z.array(postSchema); // Register schemas first, then generate const schemas = [CommentId, UserId, PostId]; const mock = initGenerator({ consistentKey }) .register(schemas) .generate(PostsResponse);
This generator will generate mock like below.
[
{
"id": "08e93b6a-0a0b-4718-81af-c91ba0c86c67",
"comments": [
{
"id": "b438b6fa-765b-4706-8b22-88adb9b5534a",
"postId": "08e93b6a-0a0b-4718-81af-c91ba0c86c67",
"user": {
"id": "c9b26358-a125-4ad8-ad65-52d58980fe34",
"name": "acceptus"
},
"userId": "c9b26358-a125-4ad8-ad65-52d58980fe34",
"value": "antepono"
},
{
"id": "667646ef-8915-46d4-a7f8-91d98546ae8a",
"postId": "08e93b6a-0a0b-4718-81af-c91ba0c86c67",
"user": {
"id": "ef49c6b2-9df1-4637-a814-e97cb0bd2c44",
"name": "tergiversatio"
},
"userId": "ef49c6b2-9df1-4637-a814-e97cb0bd2c44",
"value": "adipisci"
},
{
"id": "9ad1c339-1eab-4098-922b-5b86ed5046bf",
"postId": "08e93b6a-0a0b-4718-81af-c91ba0c86c67",
"user": {
"id": "1aeb77c9-19d5-4ec9-bc1d-de21266813c7",
"name": "sum"
},
"userId": "1aeb77c9-19d5-4ec9-bc1d-de21266813c7",
"value": "volup"
}
],
"value": "ut"
},
{
"id": "5d26f3ee-1785-4031-93ce-04f8a07f617f",
"comments": [
{
"id": "b438b6fa-765b-4706-8b22-88adb9b5534a",
"postId": "5d26f3ee-1785-4031-93ce-04f8a07f617f",
"user": {
"id": "c9b26358-a125-4ad8-ad65-52d58980fe34",
"name": "vitae"
},
"userId": "c9b26358-a125-4ad8-ad65-52d58980fe34",
"value": "curtus"
},
{
"id": "667646ef-8915-46d4-a7f8-91d98546ae8a",
"postId": "5d26f3ee-1785-4031-93ce-04f8a07f617f",
"user": {
"id": "ef49c6b2-9df1-4637-a814-e97cb0bd2c44",
"name": "uterque"
},
"userId": "ef49c6b2-9df1-4637-a814-e97cb0bd2c44",
"value": "constans"
},
{
"id": "9ad1c339-1eab-4098-922b-5b86ed5046bf",
"postId": "5d26f3ee-1785-4031-93ce-04f8a07f617f",
"user": {
"id": "1aeb77c9-19d5-4ec9-bc1d-de21266813c7",
"name": "vetus"
},
"userId": "1aeb77c9-19d5-4ec9-bc1d-de21266813c7",
"value": "arcesso"
}
],
"value": "vilicus"
}
]supply targets a Zod constructor (every ZodString etc.). When you need to fix a value at one specific spot in the tree, use supplyRef (by reference) or supplyPath (by structural path).
const Name = z.string(); const Schema = z.object({ user: z.object({ name: Name }), scores: z.record(z.string(), z.number()), pair: z.tuple([z.string(), z.string()]), }); initGenerator() // by reference — same Name node only .supplyRef(Name, 'fixed') // by path — object key .supplyPath(['user', 'name'], 'Alice') // record key injection (forces 'alice' to exist) .supplyPath(['scores', 'alice'], 100) // tuple index .supplyPath(['pair', 0], 'first') // wildcard markers: $item / $value .supplyPath(['scores', '$value'], 0) .generate(Schema);
Path segments are string | number | symbol. Markers:
'$item' — all elements of arrays / tuples / sets'$value' — all values of records / mapsSpecific paths beat marker paths. '$key' is intentionally not provided (Map/Record keys must be unique, so "all keys = X" collapses to one entry).
const users = initGenerator().generateMany(UserSchema, 50); const f = initGenerator().factory(UserSchema); const a = f.next(); const batch = f.take(10);
Attach a generator function (or static value) under the configured customMockKey (default 'mock'):
const FileLike = z.custom<File>(v => v instanceof File) .meta({ mock: (faker) => new File(['x'], faker.system.fileName()) }); const BigDec = z.instanceof(BigDecimal) .meta({ mock: () => new BigDecimal('1.5') });
If neither meta.mock nor supplyRef is provided, the slot is silently dropped from arrays/objects (warned for tuples) — it is no longer filled with a placeholder string.
Set keyMapping: 'auto' to map common property names to faker functions automatically:
const Schema = z.object({ firstName: z.string(), email: z.string(), age: z.number(), createdAt: z.date(), }); initGenerator({ keyMapping: 'auto' }).generate(Schema); // → { firstName: 'Hannah', email: 'a@example.com', age: 37, createdAt: <Date> }
You can also pass a KeyMapper function for full control; returning undefined falls back to the built-in defaults.
# JS / ESM module npx zod-v4-mocks generate ./schemas.js User --count 10 -o users.json --pretty # TS module — run through tsx npx tsx node_modules/zod-v4-mocks/dist/cli.js generate ./schemas.ts User -c 10
Options: -c/--count, -s/--seed, -o/--output, -f/--format (json|ts|js|bin), -l/--locale, --pretty, --silent, --config, --profile. Run --help on any command for the full reference.
A single config file expresses generator setup that's reused both from the CLI and from runtime code (tests, scripts). Three layers are supported:
baseConfig — project-wide defaults (locale, common supplyRef, project conventions)extend.cliConfig — additions when run via the CLIextend.testConfig — additions when consumed from tests// zod-v4-mocks.config.ts import { defineMockConfig } from 'zod-v4-mocks/config'; import { UserId, FIXED_UUID } from './src/schemas/ids'; export default defineMockConfig({ baseConfig: ({ initGenerator }) => initGenerator({ locale: 'ja', keyMapping: 'auto' }) .supplyRef(UserId, FIXED_UUID), extend: { cliConfig: (base) => base.updateConfig({ seed: 1 }) .supplyPath(['createdAt'], new Date('2024-01-01')), testConfig: (base) => base.override((schema, opts) => /* test-only rules */ undefined), }, });
CLI auto-discovers the file from cwd (or pass --config <path>) and uses the cli profile by default (--profile base|cli|test).
From tests / Node code:
import { loadConfig } from 'zod-v4-mocks/config'; const { createBase, createCli, createTest } = await loadConfig(); beforeEach(() => { // Each call returns a fresh MockGenerator — no cross-test mutation. gen = createTest() .updateConfig({ seed: testCase.seed }) .supplyPath(['user', 'email'], 'override@x'); });
loadConfig() is built on c12, so TS configs work out of the box and the resolved config is cached.
Expose the generator over the Model Context Protocol so AI agents can generate mocks from your Zod schemas. The server speaks MCP over stdio:
npx zod-v4-mocks mcp
Register it with an MCP client (e.g. Claude Desktop, Cursor) by pointing the command at this package:
{
"mcpServers": {
"zod-v4-mocks": {
"command": "npx",
"args": ["-y", "zod-v4-mocks", "mcp"]
}
}
}Four tools are exposed:
usage — no input. Explains the server, the file-path contract, and the typical workflow. Call this first if an agent is unsure how to proceed.list_schemas — input { module }. Lists the names of every Zod schema exported by a JS/ESM module. Use these as the export argument below.preflight — input { module, export?, config?, profile? }. Runs the pre-flight schema walk without generating data and reports constructs that cannot be safely mocked (error) or that may not satisfy .parse() / are auto-fixed (warning). Returns "no issues" for a clean schema. Handy to validate a schema before generating.generate_mock — input { module, export?, count?, seed?, locale?, format?, pretty?, config?, profile? }. Generates mock data and returns it serialised as json (default), ts, or js. count > 1 yields an array.Schemas are referenced by file path (the same contract as the generate CLI command), because a Zod schema cannot be faithfully reconstructed from JSON. As with the CLI, point at a compiled .js/.mjs module — TypeScript files are not loaded directly. The server reuses the auto-discovered zod-v4-mocks.config.{ts,js,mjs} file when present.
@modelcontextprotocol/sdk is an optional dependency. npx installs it automatically; if it is ever missing, the mcp command prints install instructions (npm install @modelcontextprotocol/sdk).
.catchall() is ignored.function() is not supported.nativeEnum() is deprecated in v4.promise() is deprecated in v4.superRefine() is deprecated in v4z.custom() / z.instanceof() — must define meta({ mock: ... }) (see section 9) or use supplyRef. Without one, the slot is dropped from arrays/objects.z.ZodLazy is partially supported
z.union, this library would throw errorz.tuple or z.intersection anywhere, this library would cause RangeErrorz.ZodIntersection is partially supported
z.ZodEffects (transform/refine/preprocess/check) is partially supported
.transform() and .preprocess() are fully supported.refine() validation conditions are ignored during mock generation.check() function is mostly NOT supported:
z.string().check(z.regex(/pattern/)) - ❌ NOT supportedz.string().check(z.minLength(10)) - ❌ NOT supportedz.string().check(z.overwrite(...)) - ✅ Supported (only exception)z.string().check(z.trim()) - ✅ Supported (overwrite helper).regex(), .trim()) instead of .check() function(No items currently planned)
When you pass the same seed to initGenerator, generated values are deterministic across runs.
cidrv6, base64url, etc.RandExp for regex generation) are wired to the seeded Faker RNG.Tips for custom generation via override
options.faker rather than Math.random() or Date.now() to keep determinism.import { type CustomGeneratorType, initGenerator } from 'zod-v4-mocks'; import { z } from 'zod'; const Email = z.email(); const deterministicOverride: CustomGeneratorType = (schema, options) => { const { faker } = options; // seeded if (schema === Email) { const user = faker.internet.userName(); const host = faker.internet.domainName(); return `${user}@${host}`; } }; const gen = initGenerator({ seed: 12345 }).override(deterministicOverride); const a = gen.generate(Email); // same output for the same seed const b = gen.generate(Email); // same across runs with seed 12345
const schema = z.templateLiteral(['Value: ', z.undefined()]); // or const schema = z.templateLiteral(['Value: ', undefined]); type Schema = z.infer<typeof schema>; // typesciprt guess `type Schema = "Value: "`
But, zod expect "Value: undefined", so, this library would generate Value: undefined
In Zod v4, brand() is a type-level concept. There is no special brand class exposed at runtime, so generated values follow the inner schema (e.g., z.string().brand<'UserId'>() generates a string). Meanwhile, thanks to the overload initGenerator().generate<T extends z.ZodTypeAny>(schema: T): z.infer<T>, the returned value type is inferred with the brand.
const BrandedUserId = z.string().brand<'UserId'>(); const val = initGenerator().generate(BrandedUserId); // The type is inferred as branded type T = z.infer<typeof BrandedUserId>; const _: T = val; // OK