WorkBook a call

Migration guide · Internal tool builders

How Airtable fields map to Postgres when you outgrow your base

Every Airtable field type has a Postgres equivalent, but only some become columns: linked records become foreign keys or join tables, lookups and rollups become queries, and attachments become files in your own storage. Automations turn into backend jobs and Interfaces into app screens, while Airtable can stay on as a back office.

By Max Winter-LeinweberUpdated 7 min read

Code export
None, Interfaces stay in Airtable
Data export
CSV per view, or the Web API
Hardest part
Linked records and formulas
Typical timeline
4 to 8 weeks

Start from the schema, not the CSV

Exported as CSV, a base looks like a set of spreadsheets. Read through the Airtable metadata API, it's a list of field types and options, and that list tells you exactly how much work the move to Postgres will be.

Plain text, numbers and dates move across in an afternoon. Linked records, lookups, rollups, formulas and attachments are where the time goes, because none of them is really a value: they're relationships, calculations or pointers to files. Count each type and you have a rough estimate before anyone writes code.

CSV exports also lie a little: they follow the view's filters and hidden fields, and write links as names. The Web API returns every record ID and every link as a list of IDs, which is what a reliable import needs.

Airtable field types and their Postgres columns

Most field types map to one typed column with a constraint. The ones marked as a query or a view don't get a column at all; they're recalculated from other data when you need them.

How each Airtable field type is represented in Postgres
Airtable fieldPostgresWatch out for
Single line texttext, with not null where requiredTrailing spaces and empty strings that should be null
Long texttextRich text arrives as Markdown, so decide how the app renders it
Number, currency, percentnumeric with fixed precision, or integer pencePercent is stored as a fraction, so 0.2 means 20 percent
Checkboxboolean not null default falseThe API omits unticked boxes, so a missing value means false
Date, date with timedate, or timestamptzCheck the field's time zone setting before converting
Single selecttext with a check constraint, or a lookup tableOptions people typed by hand, with spelling variants
Multiple selecttext array, or a join tableUse a join table if you will filter or report on it
Linked recordForeign key, or a join tableEvery link is many to many until you decide otherwise
LookupNo column, a join in a query or viewStoring it creates a copy that goes stale
Rollup, countNo column, an aggregate in a viewHeavy rollups may need a materialised view
FormulaGenerated column, view or app codeOnly formulas using the same row can be generated columns
AttachmentA files table pointing at object storageDownload URLs expire within hours
Collaborator, created byForeign key to a users tableAirtable users are not your app's users
Created time, last modifiedtimestamptz with a default and a triggerKeep the original values from Airtable on import
AutonumberIdentity columnCarry the old numbers if anyone quotes them
Email, URL, phonetext with a check constraintYears of unvalidated input
ButtonNo column, an action in the appIt usually triggers a script or automation you must rebuild

Linked records need a decision, not a conversion

Decide the real cardinality of every link before you design a table. Airtable stores every link as a list on both sides, so a Project linked to a Client looks identical to a Project linked to five Clients. Postgres needs to know which one you meant.

  • One to many. If each project belongs to exactly one client, the projects table gets a client_id foreign key. Query the data first: if any project has two clients, someone used the link differently and you need to resolve it.

  • Many to many. If a project can have several clients and a client several projects, create a join table with both IDs and a unique constraint on the pair.

  • Links to the same table. Parent tasks, related records and referrals become a self-referencing foreign key or a join table pointing back at the same table.

Formulas, lookups and rollups become queries

Sort formulas into two piles: ones that only use fields on the same record, and ones that reach across links. The first pile can become Postgres generated columns, which recalculate whenever the row changes. The second pile becomes views or queries, because a generated column can't read another table.

There's a third, smaller pile that catches people out. Formulas using TODAY() or NOW(), such as days overdue, can't be generated columns, because Postgres only allows calculations that give the same answer every time. Calculate those when the query runs.

Lookups and rollups follow the same logic. An invoice total rolled up from line items becomes a sum in a view, or a materialised view if the table is large. Read each formula's text from the metadata API and translate it by hand.

Copy every attachment before you switch anything off

Attachments are where data actually gets lost. An attachment field holds download URLs that expire after a few hours, so a migration that saves the links instead of the files ends up with a table of dead addresses.

Download every file during the import, upload it to storage you own, such as Supabase Storage or S3, and record it in a files table with the owning record, file name, size and path. Compare counts before and after.

What replaces automations, scripts and Interfaces

Automations become backend jobs and Interfaces become screens in your app. Neither exports, so list each one, write down what it actually does, and rebuild it with the retries and alerts Airtable never gave you.

Airtable features and what replaces them in a Postgres backed app
In AirtableIn the new app
When a record is created or updatedA database trigger or an event that queues a backend job
When a record matches conditionsA check in the code that changes the record, or a scheduled query
At a scheduled timeA cron job or scheduled function
When a webhook is receivedAn API endpoint that validates and stores the payload
Send an email actionA job calling an email provider, with retries and a log
Run a script actionA server function in the repository, under review and tests
Zapier or Make scenarioDirect API integrations in the backend
FormsApp forms with server-side validation
Interfaces for staffAn admin area, or Airtable kept as a synced back office
Interfaces or portals for customersSigned-in app screens scoped to the user's own data

Not everything has to leave: a base synced one way from Postgres makes a fine back office, as long as only one system accepts edits.

Running the import

Script the import so you can run it many times, then run it for real in one planned window while Airtable is frozen.

  1. Fetch every table through the Web API, 100 records a page, and save the raw JSON so you can rerun the transform without fetching again.

  2. Respect the limit of five requests per second per base, and back off when the API returns a 429.

  3. Clean and load rows with their airtable_id, then resolve links in a second pass.

  4. Download attachments into your own storage and record them in the files table.

  5. Check record counts, file counts and a sample of calculated values against Airtable.

  6. On cutover day, make the base read-only, rerun the import, switch integrations and keep the base as an archive.

Frequently asked questions

How do I export all my data from Airtable?
Use the Airtable Web API to fetch every record with its record ID and linked record IDs, or export each table as CSV from an unfiltered view with every field showing. Download attachments separately, because their URLs expire after a few hours.
What replaces Airtable automations when you move to Postgres?
Backend code: jobs triggered by database events, scheduled tasks and webhook endpoints. Each one gets retries, logging and alerts, so a failure is noticed straight away instead of discovered by a customer.
Can I keep using Airtable alongside a Postgres database?
Yes. Many teams keep Airtable as a back office and sync selected data into it one way from Postgres. Only one system should accept edits for any given table, otherwise you end up reconciling two versions of the truth.
Does the Airtable API have rate limits for exporting data?
Yes. The Airtable Web API allows five requests per second per base and returns up to 100 records per page. A script that waits and retries after a 429 response can export even large bases reliably.
How long does it take to move from Airtable to a production app?
Typically four to eight weeks, depending on the number of tables, linked records and automations, how clean the data is, and how much of the operation moves rather than staying in Airtable as a back office.
Portrait of Max Winter-Leinweber

About the author

Max Winter-Leinweber

Founder, Scryptic

Max is the founder of Scryptic and a full-stack developer with five years of experience building software for enterprise clients and startups. He builds MVPs for UK founders and takes apps made with no-code and AI builders through to production.

Ready to move your Airtable app to production?

Walk us through what you have built on a free call. We review the app, its data and its accounts, then send a plan and a written estimate.

Book a discovery call
All 29 posts

Retool

When your Retool tool becomes the product

What changes when customers start logging in to an internal Retool app, from auth and row level permissions to tenancy, billing and design, and what to do.

7 min read