Migration guides
How to Migrate Airtable to PostgreSQL: A Step-by-Step Guide
Discuss this article with AI
Opens your AI tool with a prompt about this article. It can read the live page and answer your questions.
Moving an Airtable base to PostgreSQL is far less scary than it sounds — provided you do it in the right order and treat the relationships, not the rows, as the hard part. This guide walks through the whole thing: mapping your schema, turning linked records into foreign keys, handling rollups and attachments, validating the result, and cutting over without taking your team offline.
Before you start: map the territory
Resist the urge to export a CSV and start importing. First, inventory what you actually have. For each table, list the field types, note every linked-record relationship, and flag the rollups, lookups, and formulas that depend on those links. This map is what turns a chaotic migration into a checklist. It also surfaces the question worth answering up front: which fields hold real data and which are just derived values you can recompute later?
Step 1 — Design the relational schema
Each Airtable table becomes a Postgres table. Each field becomes a column with a real type: text, numeric, boolean, date, timestamptz, and so on. Single selects map naturally to a text column (or a Postgres enum if the options are fixed); multi-selects map to a text[] array or a related lookup table. Give every table a real primary key — keep the Airtable record ID in a column so you can trace records back during validation.
Step 2 — Convert linked records into foreign keys
This is the heart of the migration and the thing CSV exports get wrong. Airtable’s linked records are relationships, and a relational database models relationships explicitly:
- One-to-many (e.g. a Project links to many Tasks): add a foreign key column on the “many” side —
tasks.project_idreferencingprojects.id. - Many-to-many (e.g. Authors link to many Books and vice versa): create a junction table —
authors_books— holding one row per relationship with both foreign keys.
Done right, this is a strict upgrade: foreign keys enforce referential integrity, so you can no longer end up with a task pointing at a project that does not exist. Why this maps so cleanly is covered in the Airtable vs PostgreSQL comparison.
Step 3 — Rebuild rollups, lookups, and formulas
In Airtable these fields store a recalculated value. In Postgres you usually do not store them at all — you derive them when you query, which keeps them always-correct and removes the recalculation cost:
- Lookups become a
JOINback to the related table. - Rollups (sum, count, max of linked records) become an aggregate query —
SUM(...),COUNT(...)with aGROUP BY. - Formulas become a
GENERATEDcolumn for simple per-row math, or a computed expression in the query for anything involving other rows.
If a derived value is read constantly and expensive to compute, wrap it in a materialised view so it is precomputed and refreshed on a schedule — the best of both worlds.
Step 4 — Move attachments to object storage
Attachments are not database values; they are files that happen to be referenced from a cell. The standard pattern is to copy each file into object storage (such as Amazon S3 or a compatible bucket) and store its URL in a Postgres text column. That keeps the database lean and serves files efficiently. A purpose-built tool can do this for you — AT Migrator moves attachments as part of the migration, or writes them to storage you control with the self-hosted option.
Step 5 — Migrate the data and validate
With the schema in place, load the data table by table, parents before children so foreign keys resolve. Then — before anyone trusts the new database — validate:
- Row counts match on every table, source versus target.
- Relationships resolve — no orphaned foreign keys, junction rows line up with both parents.
- Spot-check values, especially dates, numbers with precision, and anything that passed through a formula.
This validation step is where hand-rolled scripts tend to fall down and where dedicated tooling earns its keep — AT Migrator runs these checks automatically and reports any mismatch before you rely on the data.
Step 6 — Cut over without downtime
Two patterns cover almost every team:
| Freeze & cutover | Dual-write | |
|---|---|---|
| How it works | Pause edits, run final migration, validate, switch apps to Postgres | Write to Airtable and Postgres simultaneously, then cut over once at parity |
| Downtime | A short, planned freeze window | None |
| Complexity | Low — best for most teams | Higher — needs sync code |
| Best for | Internal tools, business hours edits | Always-on production systems |
For the majority of bases, a short freeze over a quiet evening is more than enough. Only reach for dual-write when the system genuinely cannot pause.
How long does it take?
The data and schema move in minutes with the right tooling. The project timeline depends on everything attached to the base. A clean single base can be done and validated in a day. A multi-base workspace with heavy automations and integrations is usually a one-to-four week effort — and most of that is rebuilding automations and testing, not moving rows. If you are still weighing whether the move is worth it, the signs you have outgrown Airtable are a good gut check.
Do it once, do it right
A migration done carefully — relationships modelled properly, derived values rebuilt, data validated on both sides — leaves you with a faster, cheaper, more capable system and no nasty surprises. Done as a rushed CSV export, it silently drops attachments and relationships you only notice weeks later.
If you would rather not hand-build the schema mapping and validation, that is exactly what AT Migrator automates. Book a discovery call and we will walk through your specific base, estimate the timeline, and flag anything tricky before you commit.
Not sure if you should migrate yet?
Get a free 30-minute discovery call with a migration engineer. We will look at your bases, estimate the work, and tell you honestly whether Postgres is the right move — no pressure, no obligation.