Skip to content

What is PDL?

PDL (Phony Definition Language) is the declarative, JSON-based language you use to describe the data you want. You don't write how to generate each value — you declare what the data is (a table of users; an id; a name that sounds Turkish; an email built from that name) and Phony produces it, reproducibly, from a seed.

PDL is to synthetic data what a schema migration is to a database: a precise, versionable, reviewable description that any runtime can execute identically.

  • Declarative — describe the shape, not the procedure.
  • JSON — editable by hand, by tools, and by agents; validated by a JSON Schema; diffable in Git.
  • Deterministic(schema, seed, reference-time) fully determines the output (see How generation works).

The shape of a schema

Every PDL document has up to four top-level sections:

json
{
  "generators":    { /* reusable value producers */ },
  "entities":      { /* tables: named fields wired to generators */ },
  "relationships": [ /* optional explicit FK declarations */ ],
  "scenarios":     { /* how many rows of each entity to generate */ }
}
SectionWhat it declaresRequired
generatorsnamed, reusable producers (an id, a name, an email…)for anything non-trivial
entitiestables — a set of named fields, each wired to a generatorto produce rows
relationshipsexplicit foreign-key edges (cardinality, on-delete)optional
scenariosnamed row counts per entity (dev, staging, …)to run a scenario

A complete, annotated example

json
{
  "generators": {
    "id":         { "type": "logic", "algorithm": "uuid_v7" },
    "first_name": { "type": "model", "source": "person.first_names",
                    "generation": { "mode": "word" } },
    "last_name":  { "type": "model", "source": "person.last_names",
                    "generation": { "mode": "word" } },
    "email": {
      "type": "template",
      "pattern": "{{lowercase(first_name)}}.{{lowercase(last_name)}}@example.com",
      "unique": true
    },
    "age": { "type": "logic", "algorithm": "int_between",
             "params": { "min": 18, "max": 80 } }
  },

  "entities": {
    "User": {
      "fields": {
        "id":         { "generator": "id", "primary_key": true },
        "first_name": { "generator": "first_name" },
        "last_name":  { "generator": "last_name" },
        "email":      { "generator": "email" },
        "age":        { "generator": "age" },
        "full_name":  { "type": "template", "pattern": "{{self.first_name}} {{self.last_name}}" }
      }
    },
    "Order": {
      "fields": {
        "id":      { "generator": "id", "primary_key": true },
        "user_id": { "ref": "User.id" },
        "total":   { "type": "logic", "algorithm": "float_between",
                     "params": { "min": 10, "max": 500, "precision": 2 } }
      }
    }
  },

  "scenarios": { "dev": { "User": 100, "Order": 300 } }
}

What this declares, precisely:

  • Five reusable generators. first_name/last_name are N-gram models (trained on samples, producing novel locale-appropriate names). email is a template that composes those names; unique: true guarantees no duplicates.
  • Two entities (tables). User.email reuses the email generator; User.full_name is an inline template that reads sibling fields via self.; Order.user_id is a foreign key to User.id.
  • One scenario, dev: 100 users and 300 orders. Each order's user_id points at a real, already-generated user — joins are always valid.

Run it with a seed and you get 100 + 300 rows; run it again with the same seed and you get the same rows.

The two kinds of "generator"

You'll see generators in two places, and it's worth being precise:

  1. Schema generators ("generators": { … }) — named, reusable, referenced by many fields. Define once, use everywhere.
  2. Inline field generators — a field can carry a generator definition directly ("full_name": { "type": "template", … }) when it isn't reused.

Both compile to the same internal form (see How generation works).

How to read the rest of these docs

You want…Read
Every generator type + its parametersGenerators in depth
Fields, foreign keys, computed columns, scenariosEntities & relationships
The {{ … }} expression language and its functionsExpressions (PEL)
Compilation, seeds, determinism, running a schemaHow generation works
Install & run a schema (Rust)The engine (Rust)

The exhaustive design specification — including parts not yet implemented — lives under Spec → PDL Specification.

Phony Cloud — Documentation & Specification