Generate realistic data.
Export to any format.
152 field types, statistical distributions, conditional weight overrides, and exports to CSV, SQL (7 dialects), JSON, JSONL, and Parquet.
Short answer
Every column gets a realistic type from a catalogue of 152, every number can follow a distribution you choose, and every foreign key points at a parent row that exists. Download the result as SQL for 7 dialects, or as CSV, JSON, JSONL or Parquet.
152 Realistic Field Types
Domain-specific data generators across 19 categories
- Basic & primitives
- Integer, float, string, text, boolean, UUID, enum, date, datetime, integer sequence
- Personal
- Names, age, date of birth, gender, nationality, SSN, blood type, prefix, suffix, occupation
- Contact
- Email, phone number, area code, username, password
- Address
- Street, city, state, postal code, country, building number, secondary address, full address
- Geographic
- Latitude, longitude, license plate
- Date & time
- Future date, past date, time
- Business & company
- Company name, department, industry, sector, sub-industry, job title, catch phrase, vehicle
- Commerce & products
- Order ID, product name, product category, subcategory, type
- Financial
- Credit card, IBAN, SWIFT, currency code/amount, decimal, EAN, stock ticker, price, percentage, tax rate, discount
- Internet & web
- URL, domain name, image URL, emoji, hashtag, slug
- Networking & developer
- IPv4, IPv6, MAC address, port, HTTP method, MIME type, file extension, user agent, API key, locale, language code
- Text & content
- Word, sentence, paragraph, book title, ISBN-13
- Healthcare
- CPT code & description, NDC code/drug name/generic name, ICD-10 code & description
- Hardware
- CPU, GPU
- Food & drink
- Dish, drink
- Education
- Academic degree, university
- Color
- Color name, hex color
- Measurement
- Height, weight
- Fantasy & creative
- Character class, item, name, race, spell, weapon
Constraints, distributions and correlations
What you can set on a column, and how the values are spread across the rows.
- Value ranges
- min: 0, max: 999.99
- A floor and a ceiling on a numeric column.
- String length
- min_length: 3, max_length: 50
- A character count for text columns, applied to the finished value.
- Regex patterns
- pattern: [A-Z]{2}-[0-9]{4}
- Values that match a shape you hand it.
- Allowed values
- choices: ["active", "pending", "closed"]
- A fixed list, which outranks whatever the field type would have produced.
- Weighted values
- 70 / 20 / 10
- How often each of those values comes up.
- Date ranges
- start: 2020-01-01, end: 2024-12-31
- Fixed dates, or a window measured from the day you generate.
- Field correlations
- Columns that agree with each other: a city inside its own state, a price that fits its category, an order total that matches its line items.
Probability Distributions
Model real-world data patterns
Apply Normal, LogNormal, Exponential, or Triangular distributions to any numeric field. Generate salary data that clusters around a median, response times with a long tail, or test scores that follow a bell curve.
Conditional Overrides
Distributions that vary by context
Change how a field is distributed based on another field's value. Salary distributions that shift by department, price ranges that vary by product category, or age distributions that differ by region. Your test data reflects the same patterns as production.
Dependencies and derived rules
Lookups keep related columns consistent. Rules compute one column from another.
Field Dependencies
Lookup tables keep related values consistent
AI-generated schemas with state, city, and ZIP columns get coherent geographic dependencies wired automatically - no clicks required. Same for sector/industry/sub-industry chains. ZIP codes always match their city and state by construction; industry codes always roll up to a real sector. Hand-edit the dependencies in the visual editor when you want different parent fields.
Derived Rules
Conditional logic that mirrors real-world relationships
Define WHEN/THEN rules with formulas that reference other fields. Example: when age >= 40 and BMI >= 30, multiply heart rate by 1.5. Build realistic correlations that simple random generation can't produce.
Cardinality Ratios
Per-relationship row count math, so child tables scale with their parents
Per-Relationship Ratios
Children scale with parents, not by hand
Set "8-12 patients per doctor" or "100-500 line items per order" instead of hand-typing a row count for every child table. SynthForge picks a uniform-random multiplier per parent and resolves child counts in topological order. Multiple parent ratios on the same child (M:N junction tables) sum naturally.
Auto-Saved Defaults
Last generation's settings come back next time
Every successful generation persists its row counts and ratios as the schema's defaults. Open the same schema's generation form tomorrow, next month, or from a different machine - the inputs come back exactly the way you left them. No more re-typing 100, 100, 100 across every table.
Export to 7 SQL dialects, CSV, JSON, JSONL and Parquet
Every SQL export ships the DDL plus that dialect's bulk loader. Every dataset also downloads as CSV, JSON, JSONL and Parquet.
A loader script per dialect
Each one creates the tables, loads the CSVs with the command that dialect wants, and adds the foreign keys.
- PostgreSQL
- \copy from the CSV
- MySQL
- LOAD DATA LOCAL INFILE
- MariaDB
- LOAD DATA LOCAL INFILE
- SQL Server
- BULK INSERT
- SQLite
- .import
- DuckDB
- COPY FROM
- CockroachDB
- IMPORT INTO
Files, whatever you load them into
Pick any combination; they all come down in one archive.
- CSV
- One file per table, RFC 4180. This is what every loader script reads.
- JSON
- One file per table, or the whole schema as one tree with child rows embedded in their parents.
- JSONL
- The same two shapes, one document per line, which is what mongoimport takes.
- Parquet
- One snappy-compressed file per table.
Generate Data. Visualize It Your Way.
Take SynthForge IO data and plug it into your favorite tools - these charts were built from our Ecommerce and Banking template schemas using Matplotlib
Total Revenue by State
Top 20 Cities by Revenue
Monthly Revenue Trend (2021-2026)
Avg Order Value vs Avg Rating by State
Loan Risk Profile by Type
Loan Payment Status Distribution
Customer Net Worth by Age - Step-Function Correlation
Data generated by SynthForge IO - charts rendered with Matplotlib
Built for scale, and for scripts
Progress you can read
Progress names the table it just finished and the number of rows it produced, so a long run tells you where it actually is instead of moving a bar on a timer.
Million-row datasets
Generate production-scale datasets with full referential integrity. Dependency ordering is automatic and rows are written in chunks, so memory stays flat as the dataset grows.
Fill a dev database
Generate request and response data that matches your schema, catch the edge cases before production does, and put realistic records in the database you develop against.
Prototype a pipeline
Test ETL work before the production data exists, validate a transformation against realistic input, and generate a million rows per job when you need to load test it.
Frequently Asked Questions
What field types does SynthForge IO support?
There are 152, grouped into 19 categories in the picker, from the ordinary ones like integer, email and city through to ICD-10 codes, IBANs and MIME types. Two controls matter more than the count. An allowed-values list you type wins over the field type's own values, whatever the type, so a column can stay typed as state and still be held to the four states your test covers. And a date column has a range mode: switch it from an absolute range to relative to today with an offset in days, and the window moves with every run instead of your fixtures aging past the dates you pinned in March.
How do I stop related columns from contradicting each other?
Point them at the same reference data. Every state, city and postal code triple comes out of the GeoNames postal table, all 50 states plus DC, and there is no invented fallback: a combination the reference data does not hold comes back empty and the dataset says which column and what to change, rather than shipping a postal code that belongs to another city. The same shape covers sector to industry to sub-industry, and an ICD-10 code with its description, which are read off one row so they agree. For anything arithmetic there are rules: when status is refunded, refund_amount is total times 0.9. A rule set makes you say what happens when nothing matches, either a catch-all or an explicit null, so the column cannot quietly come back empty.
Can a numeric column follow a distribution instead of flat random?
Set the column to normal, log normal, exponential or triangular, and give it the numbers in the units you already think in. A log normal salary column takes a mean of 140,000 and a spread of 60,000, not a mu and a sigma in log space; the conversion happens for you. The column's own min and max then clip whatever the distribution produced, which is the thing to know before you centre a normal near its minimum: the tail that would have gone below the bound arrives sitting exactly on it.
Can I set per-relationship row counts, like N children per parent?
Switch a child table from a fixed row count to per parent row and give it a range, for example 8 to 12 patients per doctor. One multiplier is drawn from that range for the relationship rather than for each doctor, and it replaces whatever row count was typed for that table. Ranges resolve in foreign key order, so doctors to patients to visits cascades, and two parents feeding the same junction table add up. How the children spread across parents is a separate setting: the default picks a parent at random for each child row, which leaves some doctors with no patients at all, so set the foreign key's sampling strategy to uniform when every parent has to be referenced the same number of times.
Can I generate the same dataset twice?
Every dataset carries a seed. Send the same seed with the same schema and the same row counts and the rows come back the same, which is what makes generated data worth committing as a fixture; leave it blank and one is allocated and recorded, so you can repeat a run you did not plan to repeat. UUID columns are the exception and are fresh every time. The form also pre-fills from your last finished run for that schema, row counts, ranges, seed and locale together, and those live on the schema rather than in your browser, so they follow you to another machine.
What is actually in the download?
One archive per dataset, split by format: a csv folder holding orders.csv and one file for every other table, a json folder holding orders.json or orders.jsonl, a parquet folder, and a single nested JSON file when you asked for embedded documents. Ask for CSV and you also get one loader script per SQL dialect, 7 of them, each dropping tables in reverse dependency order, creating them in dependency order, running that dialect's own bulk load command over the CSVs, and adding the foreign keys either inline or at the end, whichever the dialect prefers. The UNIQUE constraints in those scripts are decided by reading the CSV the script is about to load rather than by what the schema intended, so a script never declares a UNIQUE its own rows would break; where a column turned out not to be unique the constraint comes off and the dataset tells you which one.