Schema DSL
Every generator type an entity's data block can use
Each entity is one JSON file, conventionally at mock/api/<entity>/schema.json:
{
"amount": 1000,
"data": { "fieldName": "<DSL string>" },
"fixtures": [{ "fieldName": "an exact literal value" }],
"bypass": false
}amount: how many records to generate for this entity. Required, non-negative.data: one DSL string per field. Required.fixtures(optional): exact, literal records applied positionally, for rows that need to stay fixed. See Fixtures below.bypass(optional,mockingpug/reactonly):truemakes every request for this entity fall through to the real network instead of being mocked. See React guide § bypass.
Every DSL string below is parsed by core's parseFieldType(). The exact
same function backs the CLI, mockingpug/next, the Vite plugin, and the
manual-import path for mockingpug/react, so behavior never diverges
between transports.
Scalars
| DSL | Type | Notes |
|---|---|---|
uuid | string | Seeded UUID v4, not crypto.randomUUID(). Deterministic from the seed, and dependency-free in a browser bundle. |
number | number | Random integer in [0, 1_000_000] by default. |
number.<min>-<max> | number | Random integer in [min, max], e.g. number.1-100. Negative bounds are allowed: number.-50-50. |
number.increment | number | Auto-incrementing counter, starting at 1, scoped per entity+field. Continues past the existing max when more records are appended to an entity later (not restarted at 1). |
username.FS | string | "First Last": a real first + last name pair. |
username.NN | string | "AdjectiveNoun123": an adjective + noun + number nickname. |
email | string | local.1234@<random-domain>. |
email[<domain>] | string | Same, with a fixed domain: email[gmail.com] → jane.492@gmail.com. |
hash / hash.md5 / hash.sha256 | string | A hex string shaped like a digest (32/32/64 hex chars). Not a real cryptographic hash: there's nothing to verify it against, and a real one would require Node's node:crypto (unavailable in a browser bundle) or the async Web Crypto API. |
lorem | string | 6 to 24 random lorem-ipsum words. |
lorem.<N> | string | Lorem text truncated/padded to exactly N characters. |
date / date.past / date.future | string (ISO) | A timestamp within one year of a fixed reference date, before/after/around it depending on the suffix. |
boolean | boolean | 50/50 by default. |
boolean.<p> | boolean | true with probability p (0 to 1), e.g. boolean.0.9. |
enum[a,b,c] | string | Uniformly random pick among the literal, comma-separated values. |
Arrays
array[<inner type>].<count> generates a fixed-length array, recursing into
the inner type for every element. The inner type can be any scalar above,
including another array[...] (nested arrays):
{ "tags": "array[lorem.8].5" }A crossRef inner type is not supported yet
array[data.category].3 parses without error but fails at generation
time with MP-GEN-001 — the array generator doesn't know how to resolve
a cross-entity reference per element (that resolution lives in a separate
dependency-graph module, not reachable from inside array[...]'s
item-generation loop). If you need a fixed-size set of related records
today, declare separate fields instead (category_id_1, category_id_2,
category_id_3), each "data.category.id". Track the roadmap for native
support.
count is checked against mock.config.js's limits.maxArrayDepth by
mpug doctor; see Reference → mock.config.js.
Custom dictionaries
A bare word that isn't one of the built-in types (role, department, …)
is looked up in mock/data/<name>.json, a JSON array of weighted entries:
[
{ "value": "ADMIN", "max": 5 },
{ "value": "USER", "chance": 0.9 },
{ "value": "MODER", "chance": 0.2 }
]value: the literal value to emit (any JSON type, not just strings).max(optional): hard cap on how many times this value can appear across the current generation run's new records. Once the cap is hit, the entry drops out of the pool for the rest of that run.chance(optional): relative weight in[0, 1]among the entries that still have room under theirmax. Entries with nochanceshare the remaining probability mass evenly.
Fixtures
Every scalar and dictionary type above picks a value randomly (seed-stable,
but still randomly assigned per record). That's the wrong tool when
specific rows are load-bearing: a category tree where slug: "fb" is
hardcoded into icon paths, cross-links, and navigation elsewhere in an
app, for instance. Regenerating that as random data would scramble the
names/slugs every time, breaking anything that matched on them by string.
fixtures fixes that: an array of literal record patches, applied
positionally. fixtures[0] always becomes record index 0,
fixtures[1] index 1, and so on, on every mpug generate run, regardless
of seed:
{
"amount": 200,
"data": { "id": "uuid", "name": "lorem", "slug": "lorem", "icon": "lorem" },
"fixtures": [
{ "name": "Facebook", "slug": "fb" },
{ "name": "Steam", "slug": "steam-keys" }
]
}A fixture only needs to list the fields that must stay fixed. Every field
it doesn't mention (id, icon above) is still schema-generated
normally. Records beyond fixtures.length (here, indices 2 through 199)
are entirely schema-generated, same as without fixtures at all.
Fixture values always win: even if slug's generator type changes later,
or an unrelated field on the schema changes, fixtures[0].slug still
comes out as "fb" on the next generate. amount must be at least
fixtures.length (mpug doctor/parsing rejects it otherwise, MP-SCHEMA-014).
Slugify
slugify[<field>,<separator>] derives a field from another field on the
same record: it reads that field's already-generated value, transliterates
any Cyrillic characters and Latin diacritics to plain ASCII, lowercases it,
and collapses everything that isn't a-z0-9 into <separator>:
{
"amount": 1000,
"data": {
"id": "uuid",
"title": "lorem.32",
"slug": "slugify[title,-]"
}
}"Привет, Мир!" → "privet-mir". An empty separator (slugify[title,])
concatenates words instead of separating them.
The source field (title above) must be declared earlier in data:
generation follows declaration order, so a slugify field can only read a
value that was already produced. A missing/unknown source field fails at
parse time (MP-SCHEMA-016); a source field declared after the slugify
field, or a self-reference, also fails at parse time (MP-SCHEMA-017).
Unlike a fixture, a slugify field is still generated fresh
from its source on every reconciliation that touches it (field added, or the
source field's own type changes) — it derives a value, it doesn't fix one.
Cross-entity relations
data.<entity> and data.<entity>.<field> are the two relation forms.
They behave very differently, and have their own dedicated page:
Relations & Generation Order.
Typo detection
An unrecognized type (emial[gmail.com], bool, usernme.FS) fails with a
SchemaError (MP-SCHEMA-001) that includes a Levenshtein-distance "did
you mean" suggestion against both the built-in type list and your project's
own custom dictionary names. The comparison is done on the type's base word
(the part before any [...]/. suffix), so a bracket parameter never
throws off the match:
unknown generator type "emial[gmail.com]"
did you mean "email"?Run npx mpug doctor any time to surface these before they ever
reach generation.