zod-v4-mocks
v3.4.0
Published
Mock generator for zod v4
Maintainers
Readme
zod-v4-mocks
Mock generator for zod v4
Note: If you're using
[email protected]withimport from 'zod/v4'(preview), please use zod-v4-preview-mocks instead. It is compatible with[email protected].
Version Guide
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.
Installation
npm install zod-v4-mocksBasic Usage
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));Usage Details
1. Basic Usage
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);2. Custom Configuration
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;
}3. Supply Specific Values (as Custom Generator)
You can supply fixed values for specific schema types.
const mockUserWithSupply = initGenerator()
.supply(z.ZodString, 'custom name')
.supply(z.ZodEmail, '[email protected]')
.generate(basicSchema);If you configure multiple values for the same ZodType condition, the first configured value takes priority.
const mockUserWithSupply = initGenerator()
.supply(z.ZodEmail, '[email protected]')
.supply(z.ZodEmail, '[email protected]')
.generate(basicSchema);4. Override mock generator (as Custom Generator)
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 '[email protected]';
}
};
const mockUserWithSupply = initGenerator()
.supply(z.ZodEmail, '[email protected]')
.override(customGenerator)
.generate(basicSchema);5. Complex Schemas
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);6. Consistent Data Generation (Register)
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"
}
]7. Pinpoint values with supplyRef / supplyPath
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 / maps
Specific paths beat marker paths. '$key' is intentionally not provided (Map/Record keys must be unique, so "all keys = X" collapses to one entry).
8. Generate many mocks (generateMany / factory)
const users = initGenerator().generateMany(UserSchema, 50);
const f = initGenerator().factory(UserSchema);
const a = f.next();
const batch = f.take(10);9. z.custom() / z.instanceof() via meta
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.
10. Property-name based faker mapping (opt-in)
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: '[email protected]', age: 37, createdAt: <Date> }You can also pass a KeyMapper function for full control; returning undefined falls back to the built-in defaults.
11. CLI
# 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 10Options: -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.
12. Shared config file (zod-v4-mocks.config.{ts,js,mjs})
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, commonsupplyRef, 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.
13. MCP server (npx zod-v4-mocks mcp)
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 mcpRegister 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 theexportargument 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 asjson(default),ts, orjs.count > 1yields 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).
Unsupported Schemas
.catchall()is ignored.function()is not supported.nativeEnum()is deprecated in v4.promise()is deprecated in v4.superRefine()is deprecated in v4
Schemas requiring user input
z.custom()/z.instanceof()— must definemeta({ mock: ... })(see section 9) or usesupplyRef. Without one, the slot is dropped from arrays/objects.
Partially Supported Schemas
z.ZodLazyis partially supported- If schema has toplevel
z.union, this library would throw error - If schema has
z.tupleorz.intersectionanywhere, this library would cause RangeError
- If schema has toplevel
z.ZodIntersectionis partially supported- Same schema types are basically supported
- But, if the elements of the Map/Set/Array/Enum/Union do not have compatible elements, then this library would throw error.
- Different schema types are not supported in principle
- But, ZodObject and ZodRecord would be successfully generated
- But, ZodArray and ZodTuple would be successfully generated
- If one element type is ZodAny/ZodUnknown, the other element type is used
- Same schema types are basically 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)- Recommendation: Use method forms (
.regex(),.trim()) instead of.check()function
Future Support
(No items currently planned)
Determinism
When you pass the same seed to initGenerator, generated values are deterministic across runs.
- Covered by seed determinism: strings, numbers, dates, arrays, unions, regex-based strings,
cidrv6,base64url, etc. - Internally, we ensure that random sources (including
RandExpfor regex generation) are wired to the seeded Faker RNG.
Tips for custom generation via override
- If you define a custom generator, use the provided
options.fakerrather thanMath.random()orDate.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 12345Note
In .templateLiteral
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
Branded types
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