Migration guides
How to verify an Airtable to PostgreSQL migration with our sample base
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.
The fastest way to trust an Airtable → PostgreSQL migration is not a sales deck — it is opening the source base, loading the migrated SQL, and running the same checks you would on your own data. This guide walks through exactly that using AT Migrator's public sample base and the PostgreSQL seed we generated from it.
Why verify with a real sample?
CSV exports and sync demos hide the hard parts: linked-record cardinality, junction tables, formula recreation, and row-level fidelity at scale. A purpose-built migration should preserve relationships as foreign keys and rebuild computed fields as live SQL. The sample base (48,000+ records) lets you confirm that before committing to your own base.
Step 1 — Open the sample Airtable base
The public base is read-only. Browse the tables, linked records, and views the same way you would in your own workspace.
Open the sample Airtable base →
Note table names, record counts per table, and a few linked-record pairs you can spot-check later in Postgres. The CRM base includes companies, contacts, deals, order_line_items, with many-to-many links stored in junction tables named __lnk_*.
Step 2 — Download the migrated SQL seed
The seed file contains the PostgreSQL schema and data AT Migrator generated from that base — tables, foreign keys, junction tables where needed, and COPY data in standard pg_dump format.
Download at-migrator-sample-migration.sql →
Step 3 — Import into Supabase
- Create a new Supabase project (or use an existing dev project).
- Connect with
psqlusing your project's connection string (Direct or pooler), then run:psql <connection-string> -f at-migrator-sample-migration.sql - Alternatively, paste sections in the SQL Editor for spot checks — not the full ~85k-line file.
- Confirm tables appear under Table Editor: companies, contacts, deals, order_line_items.
Step 4 — Import into Neon
- Create a Neon project and copy the connection string.
- Open the Neon SQL Editor, or connect with
psql. - Run the seed file:
psql <connection-string> -f at-migrator-sample-migration.sql
Step 5 — Import locally (Supabase CLI or plain Postgres)
For a local Postgres instance or Supabase local dev:
# Plain Postgres createdb sample_migration psql sample_migration -f at-migrator-sample-migration.sql # Supabase local (after supabase start) psql postgresql://postgres:postgres@localhost:54322/postgres \ -f at-migrator-sample-migration.sql
Step 6 — Compare Airtable and Postgres
Run these checks before you trust any migration — sample or production:
Row counts
For each main table, confirm the record count in Airtable matches Postgres:
SELECT 'companies' AS table_name, COUNT(*) FROM public.companies UNION ALL SELECT 'contacts', COUNT(*) FROM public.contacts UNION ALL SELECT 'deals', COUNT(*) FROM public.deals UNION ALL SELECT 'order_line_items', COUNT(*) FROM public.order_line_items;
Junction table integrity
Many-to-many links land in __lnk_* junction tables. Every link row should resolve to both sides:
SELECT COUNT(*) AS orphaned_company_links FROM public.__lnk_companies__contacts l LEFT JOIN public.companies c ON c._at_id = l.companies_id WHERE c._at_id IS NULL;
A result of 0 means no orphaned company links in the companies ↔ contacts junction table.
Spot-check linked records
Pick one Airtable record with linked children. Find the same record in Postgres via the _at_id column (the original Airtable record ID — not the integer id primary key) and confirm related rows in __lnk_companies__contacts match.
Formulas, rollups, and lookups — the vt_ views
In Airtable, formula, rollup, and lookup fields recalculate whenever underlying data changes. AT Migrator does not copy those values as static columns into the base tables — they would go stale the moment a linked record or source field changed. Instead, each table gets a companion view named vt_[table] (view table) that mirrors the Airtable grid: every stored column from the base table, plus computed fields rebuilt as SQL.
For this CRM sample, query these views — not the raw tables — when comparing computed fields to Airtable:
vt_companiesvt_contactsvt_dealsvt_order_line_items
What lives where:
- Base tables (
companies,contacts, etc.) — migrated source data only: text, numbers, dates, attachments (jsonb), and foreign-key-style link columns. vt_views — base columns plus formulas (e.g.line_total,weighted_value,health_label), rollups (e.g.of_contacts,line_item_total), lookups (e.g.deal_stage), and linked-record display fields (e.g.linked_contacts).
Verify a formula field
Pick a record in Airtable's Order Line Items table and note its record ID and Line Total value. In Postgres, query the view — the formula is exposed as line_total:
SELECT _at_id, quantity, unit_price, discount, line_total -- formula: qty × price × (1 − discount) FROM public.vt_order_line_items WHERE _at_id = '<airtable-record-id>' LIMIT 1;
Replace <airtable-record-id> with the Airtable ID (e.g. rec0…). The value should match what you see in the base — it is computed as quantity × unit_price × (1 − discount).
Verify a rollup field
Pick a Deals record with linked order line items. Compare Airtable's Line Items count and Line Item Total rollup to the view:
SELECT _at_id, deal_name, line_items, -- rollup: count of linked order line items line_item_total, -- rollup: sum of line_total on linked items weighted_value -- formula: amount × probability FROM public.vt_deals WHERE _at_id = '<airtable-record-id>' LIMIT 1;
line_items is a count of linked rows; line_item_total sums their line_total values. weighted_value is a separate formula ( amount × probability) you can check on the same row.
Repeat for other tables as needed — e.g. of_contacts and total_deal_value on vt_companies, or company_industry (lookup) on vt_contacts. If computed values match on a handful of spot-checked records, the migration logic is working; static sync tools would pass row-count checks but fail when you change a source field and re-query.
Attachments — files in your bucket, metadata in Postgres
Airtable attachment fields are files, not database values. AT Migrator handles them in two steps during migration:
- Transfer the file — each attachment is downloaded from Airtable's CDN and uploaded to your S3-compatible bucket (you connect storage during setup; we do not host the files).
- Store metadata in Postgres — the attachment column becomes
jsonbon the base table. Each file is one object in a JSON array withkey(path in your bucket),url(full fetch URL),filename,size(bytes), andmimetype.
In this CRM sample, the Companies table has a logo attachment field. After import, inspect it on the base table — attachments are not recomputed in vt_ views; they are stored data on companies.logo (and passed through unchanged on vt_companies).
Verify attachment metadata
Pick a company in Airtable that has a Logo attachment. Note the filename and file size. In Postgres, query the same record by _at_id:
SELECT _at_id, name, logo->0->>'filename' AS filename, (logo->0->>'size')::int AS size_bytes, logo->0->>'mimetype' AS mimetype, logo->0->>'key' AS storage_key, logo->0->>'url' AS file_url FROM public.companies WHERE _at_id = '<airtable-record-id>';
filename and size_bytes should match Airtable. Multi-file attachment fields use a longer array — use logo->1, logo->2, etc. for additional files.
Verify attachment counts
Compare how many companies have a logo in Airtable vs Postgres:
SELECT COUNT(*) AS companies_with_logo FROM public.companies WHERE logo IS NOT NULL AND jsonb_typeof(logo) = 'array' AND jsonb_array_length(logo) > 0;
Verify the files themselves (optional)
If you have access to the bucket used during the sample migration, open the file_url from the query above and confirm the image loads. On your own migration, spot-check a few URLs against your S3 bucket — the database should never contain file bytes, only pointers to where AT Migrator uploaded them.
Ready for your own base?
The sample proves the engine — including links, formulas, and attachments. Your migration uses the same pipeline with your bucket and schema. See how to migrate Airtable to PostgreSQL for the full playbook, or view pricing when you are ready to run it on your base.
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.