Skip to content

CLI and YAML specs

Describe data declaratively and generate it without writing Go. Every library capability is reachable from the command line, and every subcommand reads and writes files — none of them connects to anything.

go install github.com/bakhod1r/synth/cmd/synth@latest

A spec

# users.yaml
name: users
count: 1000
locale: uz_UZ
fields:
  id:      { kind: uuid, pk: true }
  name:    { kind: name }
  email:   { kind: email, from: name }
  status:  { kind: enum, choices: [active, inactive], weights: [0.9, 0.1] }
  balance: { kind: amount, min: 0, max: 1000000, dist: lognormal, mu: 9, sigma: 1.2 }
synth gen -s users.yaml -o users.csv          # or -f jsonl | sql
synth gen -s users.yaml -f sql -n 100000 --seed 42

Subcommands

Command What it does
synth gen generate rows from a spec or a preset
synth profile learn a spec from a real export
synth mask anonymize a real dump
synth verify audit an existing dataset
synth diff compare two datasets' shape
synth cdc generate a change-event history
synth snapshot the table as it stood at an instant
synth ui the browser workbench, loopback only

Flags

  -s, --spec       YAML data-definition file
  -i, --in         input file to profile or mask
  -o, --out        output file (default: stdout)
  -f, --format     csv | jsonl | sql | parquet | pgcopy | pgcopy-binary
  -n, --rows       number of rows or events
  -l, --locale     locale (e.g. uz_UZ)
      --name       table name for a profiled spec
      --key        masking key; the same key keeps foreign keys joinable
      --seed       deterministic seed
      --chaos      fraction of edge-case values (0..1)
      --preset     built-in schema
      --unmasked   return raw card numbers and identifiers instead of masked
      --at, --from, --to   instants for snapshot (2026-01-01 or RFC 3339)
      --churn      mean updates per row over the window
      --ref        foreign key to resolve, as col=parent.csv:key (repeatable)
      --fk         foreign key to fill from a parent file, col=parent.csv:key (repeatable)
      --append     extend the output file instead of overwriting it
      --update-rate, --delete-rate, --snapshot   CDC history shape
      --soft-delete   emit a delete as an update stamping deleted_at
      --child, --child-fk   cascade CDC: child spec and its FK column to the parent
      --k, --qi    k-anonymity: require each --qi col,col combination k+ times
      --dp         Laplace-noise a numeric column while masking, col:epsilon:sensitivity

Presets

A built-in schema, for when you want rows now and a spec later:

synth gen --preset user -n 100 -o users.csv

employee, event, order, patient, payment, product, transaction, user.

--preset user is slower than it looks

It generates about 5,000 rows/sec, where the same shape without its password_hash column runs at roughly 500,000. That column runs PBKDF2 at 1,000 iterations — a key derivation function, meant to be expensive. See Benchmarks.

Typical pipelines

# Learn a spec from a real export, then generate from the spec forever after.
synth profile -i prod_export.csv -o users.yaml
synth gen -s users.yaml -n 1000000 -o fake_users.csv

# Anonymize a real dump. The same --key across files keeps foreign keys joinable.
synth mask -i prod_export.csv -o safe.csv --key "$MASK_KEY"

# Generate a coherent insert/update/delete history in Debezium's envelope shape.
synth cdc -s users.yaml -o changes.jsonl -n 10000 --update-rate 0.3 --delete-rate 0.1

synth mask refuses to run without --key (an unkeyed run is not reproducible) and refuses to write over its own input.