The key-based generator fires after matchers and per-schema withKeyMap /
world-level withGenerators, and before schema-based generation. It
inspects the field name and, when it recognises the pattern, produces a semantically meaningful
value — no matcher required.
This page lists every exact-key entry, every pattern rule, and the Dutch-language aliases that
ship in src/generators/data/key-map.ts. Use world.explain(schema) to
inspect what the engine would pick for any given schema.
How it works
The library calls generateFromKey(key, schema, ctx):
- The field name is lowercased for matching (case-insensitive —
CreatedAt,created_at,CREATEDATall match the same rule). - The Zod schema is unwrapped to its leaf type (
string,number,date, …) —getLeafDef(schema)stripsoptional,nullable,default,readonly,catch,brand. DEFAULT_KEY_MAP[leafType][lowercasedKey]is consulted first. If an exact-key entry exists, it wins.- Otherwise the rules in
DEFAULT_KEY_PATTERNS[leafType]are tried in order; the first matching pattern wins. - If neither table matches, the key-based generator returns
undefinedand the engine falls through to schema-based generation.
Many rules are schema-type-gated: a key like email only matches when
the field is z.string(). A field named email: z.number() falls through
to schema-based generation (no exact-key fire from DEFAULT_KEY_MAP.string).
The full resolution order in WorldImpl.generateObjectFields is (the term is the
identifier shown in world.explain output):
world.withSchema(...).world.withKeyMap(...).world.withGenerators(...).DEFAULT_KEY_MAP (e.g. person.firstName).DEFAULT_KEY_PATTERNS plus a leaf-type suffix for dates.Exact-key generators (string)
These fire when the field is a z.string() (or unwrapped to one) and the lowercased
field name exactly matches the key.
Person
.min()/.max())Internet
@example.com emailhttp / httpsLocation
Finance
.min())Commerce
.min()/.max())AB-1234Company
Phone
Vehicle
Color (CSS / UI)
#a1b2c3)System
Word & free text
.min()/.max())Exact-key generators (number)
These fire when the field is a z.number() (or unwrapped to one).
.min()/.max(), default 1 – 10 000).min()/.max(), default 1 – 500)[0, 5][0, 100]p = 0.5), modal at lower
bound (default [1, 100])p = 0.5), modal at lower
bound; min = 0 native (default [0, 50])[18, 80]); tight bounds → uniform-int[currentYear - 50, currentYear]; tight → uniform-intPattern generators
When an exact-key entry does not match, the engine tries pattern rules. Each rule has a test(lowercasedKey) function; the first match wins.
Pattern generators (string)
id, or ends with id, uuid, guid. UUID (RFC 4122 v4)name. Full nameurl, link, or starts with url. HTTPS URLemail. Realistic
emailat, date, starts with date, or ends with _on (excluding position). ISO 8601 date stringPattern generators (date)
When the field is a z.date() (or coerced/unwrapped to one), the date pattern produces
a Date object.
at, date, starts with date, or ends with _on (excluding position). A Date instancePattern generators (number)
When the field is a z.number(), the date pattern produces a Unix timestamp in
milliseconds.
at, date, starts with date, or ends with _on (excluding position). Unix-ms timestampThe same key with a different Zod type yields a different output: createdAt: z.string() → ISO string; createdAt: z.date() → Date; createdAt: z.number() → ms timestamp.
Localised aliases
The Dutch-language keys live in DEFAULT_KEY_MAP itself — they are
not provided by the locale packages. The locale packages (@zod4-mock/locale-en, @zod4-mock/locale-nl, @zod4-mock/locale-names) supply LocaleData (vocabulary, formatters, Markov models), not key maps. Adding a new locale
does not add new key aliases; those changes happen in src/generators/data/key-map.ts.
The Dutch aliases that ship today:
Strings
.min()/.max())Numbers
.min()/.max(), default 1 – 10 000).min()/.max(), default 1 – 500)Using world.explain to debug a schema
world.explain(schema) reports — for each top-level field — which row of this table
the engine would pick. It is read-only and PRNG-neutral, so it never disturbs the next generate call. See world.explain in the API Reference for the
full type.
Try the schema below in the playground — every field resolves through the heuristics above
(firstName → a first name, email → an email, id → a UUID),
while homeAddress matches no rule and falls through to schema-based generation:
const UserSchema = z.object({
id: z.string(),
firstName: z.string(),
email: z.string(),
homeAddress: z.string(), // ← does NOT match any exact key
kind: z.string(),
});
const world = createWorld({ seed: 1 }).withSchema(UserSchema, {
matchers: { kind: () => "admin" },
});
console.log(world.explain(UserSchema).toString());
// id → string.uuid (key-pattern: ends with "id")
// firstName → person.firstName (exact key: "firstname")
// email → internet.email (exact key: "email")
// homeAddress → schema-based (no key match, no matcher)
// kind → matcher:kind (matcher registered via withSchema) The homeAddress line is the near-miss diagnostic: the field name
didn't match any rule, so a random schema-based string will be produced. Renaming it to address, or registering a matcher, attaches a realistic generator.
Overriding a built-in heuristic
Option A — Matcher in withSchema (field-specific, highest priority):
world.withSchema(OrderSchema, {
matchers: {
email: (ctx) => `orders+${ctx.gen.string.uuid()}@mycompany.com`,
},
}); Option B — world.withGenerators (world-wide, overrides built-ins):
world.withGenerators({
email: (_schema, ctx) => `user${ctx.prng.int(1, 999)}@internal.example.com`,
}); Custom generators registered via withGenerators or WorldOptions.generators take priority over the built-in heuristics. Keys are matched case-insensitively.
Adding domain-specific generators
const world = createWorld({
seed: 42,
generators: {
durationS: (_schema, ctx) => ctx.prng.int(30, 3600),
vendorCode: (_schema, ctx) => `V-${ctx.prng.int(1000, 9999)}`,
},
}); Or additively via world.withGenerators({...}). See KeyGenerator for
the signature.
Important notes
- Field names are matched after lowercasing. Original casing is irrelevant.
- Date heuristics are type-aware. A field named
createdAtproduces aDate, an ISO string, or a numeric timestamp depending on the Zod schema. - Other heuristics are schema-type-gated.
quantity,count, etc. only fire forz.number(); az.string()field namedquantityfalls through to schema-based generation. - Custom generators take priority. Anything registered via
WorldOptions.generatorsorworld.withGenerators(...)overrides the built-in table. inline:<key>identifiers indicate a length-aware or composite inline closure inDEFAULT_KEY_MAP(e.g.bio,description,sku,accountnumber). The behaviour is documented in the description column.