Log in

Help

How a model is put together, what each field type does, and what happens when you save or download. Open any section.

Trying to build something specific? Read the Recipes.Common scenarios end to end: a funnel whose dates make sense, billing and shipping on one row, data that looks like it came from real people.

Tables, and how they relate

Tables, and how they relateRow counts, the two ways tables relate, and the link columns each one makes.

Each table on the left side of the screen is a list of one kind of thing, for example, customers, orders, or events. Select a table to edit it. The fields in each table are the columns for the data set.

Tables relate in two ways, and choosing between them is the main structural decision in a model. Something that belongs to one row is nested inside it, so each customer gets orders of their own. Something shared, that many rows across the model point at, stays a separate table with a Reference field pointing at it: one product catalog, drawn on by every cart. The test is whether the rows are owned by one parent or drawn from a common pool.

Most of the time one kind of thing belongs to another. An order belongs to a customer. A customer does not belong to anything. When that is true, you nest the order table under the customer table, so that it sits inside it. There are three ways: set “nested under” at the top of the order table, drag one table onto another, or use the + beside a table to add a new one already inside it. Once it is nested that control changes, and reads “Each customer has 2 to 8 orders” instead. The builder handles the rest.

customers
idname
c-104Dana Okafor
c-105Marie Lambert
orders, nested under customers
idcustomer_idtotal
o-881c-10442.10
o-882c-10418.99
o-883c-1057.50

The highlighted column is the one nesting adds for you. Every order carries the ID of the customer it belongs to, so the first two orders above are both Dana’s. You do not create that column or point anything at it. It appears on the order table as a column you cannot edit or remove, and it is what lets any tool you load the files into put the right orders beside the right customer.

Nesting also decides how many rows you get. A table at the top level has a row count you type in. A nested table does not: you say how many rows each parent should have, as a range, and the total follows from that. So you type 500 customers and “2 to 8 orders each”, and the order count works itself out.

A model of about 3,000 rows
customers500 rows
└ orders2 to 8 per customer≈ 2,500

Because it is a range rather than a fixed number, a nested table’s size is always an estimate. And a model’s total is every table added together, which is why it grows faster than it looks: adding 100 more customers here adds around 600 rows, not 100.

When the rows are shared rather than owned, do not nest. A product does not belong to one cart, and nesting products under carts would give every cart its own private copies of the catalog. Instead keep products as its own table at the top level and add a Reference field to the table that needs it. That column holds a real id from the table it points at, so the two join, and any number of tables can point at the same list.

products, a table at the top level
idname
p-402Cotton tee
p-403Wool socks
cart_items, with a Reference to products
idproduct_idquantity
i-771p-4022
i-772p-4031
i-773p-4021

The highlighted column looks like the one nesting adds, and holds the same kind of value, but you created it and you control it: which table it points at, whether some rows are far more popular than others, and whether the same row can be pointed at twice within one group, meaning within one parent row where this table is nested, or across the whole table where it is not. The one thing it cannot do is point at a nested table, because a nested table’s row count is only an estimate.

The as-of date

The as-of dateThe one setting the rest of the tool is built on, and what moves when you change it.

No date in a model is stored as a calendar date. Each one is stored as a distance from a single reference day, the as-of date: “90 days before”, “within the last two years”, “3 days after created_at”.

The As of section at the top of the rail sets that day, and there are two choices. Current date (“updated every time you generate”) means the reference day is whatever day it is when the rows are made, so the data is current every time and never needs revisiting. Fixed date (“stays the same every time you generate”) pins it to one date and keeps it there.

Whichever you choose is saved with the model, so every download of it uses that setting, wherever you start the download from.

Moving the as-of date moves the dates and nothing else. Ids, names, numbers, categories, addresses and row counts all stay exactly as they were. That is the part worth knowing if you demo from this data: regenerate next month and your script still points at the same records, with dates that make sense.

The same row, generated three months apart
cust_00148  Dana Okafor  2026-06-02  1,284.50
cust_00148  Dana Okafor  2026-09-02  1,284.50

Generation is repeatable, not random: the same model and the same as-of day always produce the same rows. Adding a field or changing a row count does not reshuffle the values that were already there.

Setting up a date field

Setting up a date fieldA moment or a range, what it counts from, and keeping a chain of dates in order.

A date field is either a single moment or a range of time that rows are spread across. A range is the usual choice: a signup date somewhere in the last two years, rather than every customer signing up on the same afternoon.

The beginning and end dates of the range are built from four parts: a number (e.g. 30), a unit (e.g. days), a before or after selection, and where to count from (e.g. the as-of date for your model or another field’s date). For example: 30 days after the created_at date. You can also count from a date on the table this one is nested under, which is how a child row stays behind its parent.

An order that cannot precede its customer
customers.signed_upfrom24 months beforethe as-of date
orders.placed_atfrom0 days afterits customer’s signed_up
orders.shipped_atfrom1 day afterthis order’s placed_at

Anchoring each date to the one before it is what keeps a chain in order at any as-of date. Nothing ships before it was placed, and no order predates the customer who made it, because the order is not written against a calendar at all.

There are three other controls. Spread decides where rows bunch up inside a range: evenly, toward the earlier end, or toward recent. Format chooses date only, date and time, or an ISO timestamp. And two checkboxes let a date sit in the future (due dates, upcoming events) or be earlier than the record it hangs off (a date of birth on a customer created last week). The future one only appears when the date could actually reach the future. If the whole range counts backwards from the as-of date, there is nothing for it to do, so it stays out of the way.

There is one thing to watch out for. If a date counts from a field that is sometimes empty, the rows where it is empty count from the as-of date instead. Keep a gated date and the date it counts from gated the same way.

Field types

Field typesEvery type, in the order the picker lists them, with what each one produces.

ID

A unique label for each row, so no two rows can be mistaken for each other. Two shapes are available: a counter, which is an optional prefix, a zero-padded row number and an optional suffix (cust_00148), or a UUID (8f14e45f-ceea-4e78-b3d1-9a2b7c05e611). The UUID is stable rather than random, so the same model always gives the same keys, which a real random one could never do. Prefix and suffix work with both. An ID is the one type that is never left blank and never gated.

Reference

Points at a row in a separate table, like a product in a catalog. The column holds that row’s id, so the two tables join on it, and every value is a row that really exists. You can also bring other columns across from that same row, such as a product’s name or price, so they sit beside the id as ordinary columns of your own.

This is for tables that stand beside each other. To give each row in this table its own set of something, like customers having orders or car makes having car models, nest that table instead and the link is made for you. Nesting is one to many, and a Reference is many pointing at one.

Three settings shape it. Popularity decides whether every row is equally likely or a handful take most of the pointers, which is what real order data looks like. No repeats stops two rows in the same group pointing at the same thing, so a cart cannot list one product twice. And % blank leaves some rows with no product at all.

You can only point at a table that sits at the top level, not one nested inside another, and the table you point at has to have an ID field for the column to hold.

The basics

Category

Your own list of values, in the mix you choose. Each one gets a percentage against it, so you state the mix directly: 45% shipped, 30% placed, 25% delivered. Percentages are always visible, and a “Make even” button splits them for you. They do not have to total 100; if they do not, the shares are scaled to fit and the card says so.

Presets cover the common lists (order status, priority, department, payment method, device) with realistic weights already set. This is also the type most worth reaching for instead of something exotic: a status column is a Category, and gating your date fields on its values is how a funnel stays consistent.

Number

A range, with a shape so the column does not look synthetic. You also set how many decimal places. The shape is the part that does the work: order totals cluster low with a long tail, so “mostly small, a few large” reads truer than an even spread across 5 to 5,000. The card draws the shape as a histogram so you can see what you have chosen.

Date

A day, or a day and a time, set relative to now or to another date rather than fixed to a calendar. It has its own section above.

True/false

A real boolean, with the share of rows that come out true. It is a type of its own rather than a two-value Category because a Category produces the text “true”, which lands in a typed boolean column as either an error or, worse, as a string that reads as true whatever it says. A blank here is empty, never false: “we did not record this” and “this is false” are different claims.

Text

A column of prose, for notes, descriptions and comments. You choose a length (short, medium or long, around 8, 22 and 50 words) and how many different versions to write. Rows draw from that pool of versions rather than each getting its own, so 50,000 rows cost however many versions you asked for, not 50,000 pieces of writing. More versions means less visible repetition: at a few hundred rows, 3 repeats obviously and 50 does not.

The words are lorem ipsum today. Realistic, AI-driven writing comes with the Pro plan. The card shows you the versions it will use, and everything else about the field works now.

People and companies

Name

A person: the full name, the first or last on its own, or both as two columns. Where it is two columns they are drawn from one person, and that option is the only setting anywhere that changes how many columns a field produces, so both column names appear as soon as you choose it.

Two Name fields in one table means two different people, which is what you want for buyer_name beside seller_name. If you meant one person split across two columns, use the first-and-last option instead, and the builder offers to combine them for you when it sees a likely pair.

Email

An address built from the person in the same row. So dana.okafor42@example.com sits beside Dana Okafor rather than beside a stranger. Every address ends in example.com, which is reserved for this purpose, so nothing generated can reach a real inbox. If the table holds more than one Name field, a picker appears so you can say which person this address belongs to.

Username

A handle for that same person, in the style you pick. The styles are dana.okafor, dokafor, and danaokafor. It uses the same person picker as Email. Handles are not guaranteed unique, so use an ID if you need a key.

Phone

A non-functional but realistic-looking phone number. It is drawn from ranges reserved for fiction, so it cannot be dialed. You pick a format, or tick “Match the country” if the table has an address with a country column, and each row gets a number for its own country.

Germany, Spain, the Netherlands and New Zealand have no reserved block, so rows in those countries fall back to the format you picked. A visibly foreign number is deliberate: a plausible local one would be somebody’s real line.

Company

An invented trading name, with an optional Inc or Ltd. LLC is available too. For B2B data, where the customer is a company rather than a person.

Places and links

Place

An address with columns you can choose to include or exclude. It adds several of them at once, and they read as one place, which is why it works differently from every other type. It has its own section below.

URL

A link on a domain reserved for fiction, so it reaches nothing real. It can carry a path, and it uses example.com and its siblings.

Addresses, and why Place works differently

Addresses, and why Place works differentlyOne address handing out several columns, country mixes, two addresses in one table, and rows with no address.

An address is several columns that have to agree with each other, which no other field type has to do. Set up three separate fields for city, region and postal code and every row gets a place that does not exist: Portland, Texas, 02134.

So you set up one address, and it hands out the columns you asked for. All of them read the same record on each row, so they agree by construction.

Four columns, one place
4821 Ridgeway Ave  Portland  Oregon  97205

Choosing “Place” adds the address immediately, along with four columns: street, city, region and postal code. Its settings panel opens beside them, and that is where you set which countries it draws from and in what mix, and tick the other parts you need: county, country, a one-line full address, and latitude and longitude. There is no separate setup step to finish, and no undo, so if you did not mean to add it, “Remove this address” takes every column back in one press.

Once added, each part is a normal field card. Rename it, reorder it, gate it, blank it, delete it. Deleting one leaves the rest coherent, because they read the shared record rather than reading each other. Deleting every part does not delete the address, though. It stays, making no columns until you tick one again. “Remove this address” is the only thing that removes it.

Countries

Nine countries are available today: the United States, Canada, the United Kingdom, Australia, New Zealand, Germany, France, Spain and the Netherlands. The mix is weighted the same way a Category is, so a realistic customer table can be 60% US, 30% UK, 10% Germany.

Two things change once an address holds more than one country. Labels go neutral, because no single country’s word fits every row: State becomes Region, ZIP code becomes Postal code, County becomes District. You can still name the column state if that is what your system expects. And a country column is ticked for you, because without one there is nothing to tell you whether M1 4BT is a postal code or a typo. Untick it if you do not want it. One gap worth knowing: Canada and Australia have no county-level data, so that part comes out empty for those rows.

Regions come out in full: Oregon, not OR. Street names and house numbers are invented, so no row lands on somebody’s actual house. City and region are real. Postal codes are real only as far as the part that names a place: in M1 4BT the M1 is a real Manchester district and the 4BT is generated, so the code is the right shape and the right city without ever being one building.

Two addresses in one table

Add Place twice. Billing and shipping then draw independently, so a row can be billed in Leeds and shipped to Bristol. The first address’s columns are unprefixed, and a second one pre-fills prefixed names (shipping_city). Those are starting points: renaming the address later does not rename its columns, so nothing moves under an export or a script you have already written.

Rows with no address

There are two ways an address can be missing, and they are set in different places. “How many rows have no address at all”, in the address settings, empties every part together, which is what you mean by “20% of customers have not given us an address”. A “% blank” on a single part leaves scattered holes in that column alone, which is ordinary messy data: a street with no county.

Setting 20% blank on four parts separately does not do the first thing. It leaves about 59% of rows missing something and almost none missing everything, because each part is blanked independently: 80% of four parts all landing is 41% of rows fully intact.

Latitude and longitude

Ticking this adds two columns, and they are numbers rather than text. They are the centroid of the city or postal code, not the invented street address, so they are plausible rather than precise. That is the right trade for demo data, but do not use them to test a distance calculation to the metre.

Phone numbers that follow the address

A Phone field is not part of the address, but it can read one. If the table has a country column, Phone offers “match the country”, and each row gets a number belonging to its own country. See Phone under field types for which countries have a reserved block.

One thing is deliberately not solved: names do not match countries. A row in Germany can hold an American-sounding person.

Making one field follow another

Making one field follow anotherTwo separate controls: which value a row gets, and whether it gets one at all.

Two controls sit on a field card and answer different questions. They sound similar, so it is worth knowing which one you want before you open either.

Use different values depending on … (which)

This control sits on Category and Number fields. Point one at another field, and each value of that parent gets its own list. Product names depend on category, so electronics gives you laptops and monitors while grocery gives you coffee and rice. On a Number field the same control gives each parent value its own range and shape: free accounts land between 5 and 50, enterprise between 500 and 5,000.

The rows are built from the parent’s own values rather than typed, so you cannot key off a value that does not exist. Anything you have not filled in falls to the “Anything else” row, and there is a row for parent values that are blank.

The parent does not have to be a Category. Point at a Number and the rows are ranges you add; point at a Date and they are stretches of days before the as-of date; point at a True/false field and there are two rows.

Only fill this in when … (whether)

This decides whether a row gets a value at all. It works on any field type, including dates, and it stacks on top of an ordinary “% blank”.

This is how you build a funnel without a special field type for it. Make status an ordinary Category so you state the mix yourself, then gate each date on the statuses that should have it: shipped_at only when status is shipped or delivered, delivered_at only when it is delivered. Anchor each date to the one before it and the order comes free.

Four orders, one gated pair of dates
statusplaced_atshipped_atdelivered_at
placed2026-07-02
shipped2026-06-112026-06-13
delivered2026-05-282026-05-302026-06-02
placed2026-07-19

A field cannot depend on a field that depends on it, so the picker will not offer a choice that would make a loop.

Blanks and gaps

Blanks and gapsHow to make data patchy on purpose, and what a blank looks like in each file format.

Real data has holes in it, and software that has only ever been demoed against complete rows tends to fall over on the first empty one. Every field type except ID carries a “% blank” so you can put those holes in deliberately.

A blank is an absence, in whatever way the format has one: null in JSON, an empty cell in XLSX, an empty field in CSV. It is never the text “null” or an empty string standing in for a value somebody supplied.

“% blank” and “Only fill this in when” express different things and can be used together. A gate says the field does not apply to this row: an order that never shipped has no ship date. A blank says it applies and is missing: the customer has a phone number and you do not have it.

Previewing and downloading

Previewing and downloadingWhat the formats each contain, and why every download regenerates first.

The dock at the bottom shows real generated rows from the table you have selected, not a mockup, and it updates as you edit. It shows a handful of rows however big the model is, so a large model stays responsive to type in.

There are four formats, and they do not all contain the same thing:

  • XLSX gives you every table, one sheet each, with a type per column.
  • JSON gives you every table, with real numbers, booleans and nulls.
  • CSV gives you one table: the one you have selected. A CSV file cannot hold more than one, so if you need all of them, take XLSX, JSON or SQL, or download each table in turn.
  • SQL gives you every table as insert statements, ready to load straight into a database. Pressing it opens two options: whether to include the statements that create the tables, and whether to empty them first. The second one deletes rows when you run the file, so it is off unless you tick it.

Every download generates the data again before writing the file, so a download is never a stale snapshot from earlier in the session. If the model’s as-of date is set to today, that means the file you just got is dated today, whichever page you started the download from. One thing that does differ by page: downloads from My models use the saved copy of a model, so unsaved changes you made in the builder are not in them. After it lands, a line in the dock tells you the filename and how many rows, and on a model with more than one table, which tables you got.

A table with no columns yet cannot be written as SQL, since there is nothing to insert, so it is left out of that file and the options tell you before you export. Every other format writes it as an empty table.

CSV has one quirk worth knowing about, and it is the format’s fault rather than ours. A CSV file cannot say what it contains, so it carries a marker that tells Excel on Windows to read it as UTF-8. Without it, accented names arrive mangled. Anything that is not Excel ignores the marker.

A model bigger than your plan allows can still be built and previewed, but it cannot be saved or downloaded until it is smaller. The dock says so, and your Account page shows where each of your models stands.

Still stuck, or something here wrong? Read the FAQ, or .