Audit your Airtable base before you migrate to PostgreSQL

See every table, field, and relationship — with data quality analysis, dictionary candidates, and cardinality detection. Everything you need to design your PostgreSQL schema.

Open source on GitHubWorks with Claude Code, Codex, CursorNo data leaves your machine

One command. Full migration readiness report.

Get a full report you can feed directly to your AI coding agent — or read yourself. Markdown and HTML, runs locally.

Data Quality Audit

  • Per-field null rates, cardinality, value distributions
  • Dictionary candidates (text fields with few unique values)
  • Unused and rare select choices flagged for cleanup
  • Similar choice / typo detection via word-overlap analysis
  • Composite value warnings (commas/semicolons in choices)

Relationship Mapping

  • Many-to-One vs Many-to-Many detection from real data
  • Dependency graph with topological sort
  • Circular dependency warnings
  • Cross-base link detection

Migration Readiness Score

  • Low / Medium / High complexity rating
  • Full PostgreSQL schema specification (MIGRATION.json)
  • Airtable → PostgreSQL type mapping with validation constraints
  • Import order based on dependencies
  • Computed fields inventory (what to recreate as app logic)

Get started in 60 seconds

# Claude Code
/plugin marketplace add mperlak/airtable-migration-audit
/plugin install airtable-migration-audit

# OpenAI Codex
$skill-installer install https://github.com/mperlak/airtable-migration-audit/tree/main/skills/airtable-migration-audit

# Any Agent Skills-compatible agent (agentskills.io)
npx skills add mperlak/airtable-migration-audit

# Set your Airtable token (required for all methods):
export AIRTABLE_API_KEY=patXXXXXXXX

# Then ask your agent:
"Audit my Airtable base"
# → The tool lists your bases and lets you pick one

Need an Airtable API token? Get one at airtable.com/create/tokens (scopes: schema.bases:read + data.records:read for full analysis, or just schema.bases:read for a fast schema-only audit)

What a real audit looks like

Interactive preview of a report from a 5-table Airtable base with 37,000+ records. Click around — everything works.

AIRTABLE_REPORT.html

Airtable Migration Audit

High complexityFull analysis

appXXXXXXXXX appXXXXXXXXX · 2026-03-14 12:02:56

by straktur.com · Runs locally · report stored on your machine

High complexity migration. 5 tables, 37,284 records, 69 data quality issues to resolve first. 5 many-to-many relationships need junction tables. Circular dependencies detected — requires two-pass import.

Summary

5
Tables
37,284
Records
73
Data fields
18
Computed fields
8
Relationships
22
Select fields
69
Warnings

Migration Readiness

Tables to migrate5 (5 with data, 0 empty)
Computed fields18 — recreate as app logic
Select fields22 → enum types or lookup tables
Relationships3 Many-to-One (FK), 5 Many-to-Many (junction)
Data quality warnings69 — fix before migrating
Circular dependencies1
Cross-base links0
Recommended Target Schema
5
Core tables
5
Junction tables
12
Lookup tables
22
Total tables
3
Foreign keys

Data Quality Warnings 69

Data cleanup needed
TableFieldIssue
ProductsDescriptionOnly 4 unique values across 3,102 records — dictionary candidate
ProductsSub CategoryChoice "Stickers" used by only 2 record(s)
ProductsSub CategoryChoice "Armchair" used by only 1 record(s)
Shopping ListsModel StatusChoice "Manufacturer DB" used by only 1 record(s)
ProjectsProcessing StatusChoice "AI to describe" used by only 2 record(s)
ProjectsRoom TypeConstant field — all 1,810 records have value "Kids Room". Consider removing
ProjectsPurpose5 pair(s) of similar choices — possible duplicates/typos
ProjectsTagging in ProjectsSingle-value field — only value is "Tagged" (378 records, 79% null). Convert to boolean
3D ModelsModel TypeChoice "roller blind" used by only 2 record(s)
3D ModelsAuthorOnly 4 unique values across 833 records — dictionary candidate
Schema decisions
TableFieldIssue
ProductsManufacturerAll records have 0-1 links — Many-to-One relationship (single FK)
ProductsShopping ListsRecords have up to 439 links — Many-to-Many relationship
Shopping ListsProjectAll records have 0-1 links — Many-to-One relationship (single FK)
Shopping ListsProductRecords have up to 3 links — Many-to-Many relationship
Shopping ListsNameMax length 425 chars — use text() instead of varchar()
ProjectsShopping ListRecords have up to 93 links — Many-to-Many relationship
ManufacturersProductsRecords have up to 167 links — Many-to-Many relationship
3D ModelsShopping ListsRecords have up to 5 links — Many-to-Many relationship
Low-priority cleanup
TableFieldIssue
ProductsStatus2 defined choice(s) never used: Discontinued, On Hold
ProductsSub Category1 defined choice(s) never used: re
Shopping ListsModel Status1 defined choice(s) never used: nr
Shopping ListsProjectsAll values are empty
ProjectsProcessing Status3 defined choice(s) never used: Export - fixes, EXT - to process, AI Described
ProjectsLead Architect2 defined choice(s) never used: Olga, Roksana
ManufacturersNotesAll values are empty
ManufacturersStatus3 defined choice(s) never used: Todo, In progress, Done
ManufacturersShopping ListsAll values are empty
3D ModelsModel Type2 defined choice(s) never used: Armchair, leon

Import Order

#TableRecordsDepends On
1Products3,102Manufacturers, Shopping Lists
2Shopping Lists19,855Projects, Products, 3D Models
3Projects1,810Shopping Lists
4Manufacturers59Products
53D Models833Shopping Lists
Circular dependencies: Products ↔ Manufacturers ↔ Shopping Lists ↔ 3D Models ↔ Projects. Import both without FK values, then resolve in a post-import pass.
Fields Overview
#FieldTypeNull%Notes
1NamePKsingleLineText0%
2SKUsingleLineText1%
3DescriptionmultilineText100%4 unique — dictionary?
4StatussingleSelect2%4 choices (2 used)
5ManufacturerlinkedRecord → Manufacturers5%M:1 (FK)95% 0-1 links → M2O
6Main CategorysingleSelect70%4 choices (4 used)
7Sub CategorysingleSelect73%25 choices (24 used)
8Shopping ListslinkedRecord → Shopping Lists48%M:MUp to 439 links → M2M
9AttachmentsmultipleAttachments100%0 files
10Created DatecreatedTime0%2021-11-03 → 2026-03-12
Computed Fields3 — skip during import
FieldTypeResultRecreate As
Project (from Shopping Lists)multipleLookupValuesmultipleRecordLinksSQL JOIN
H1rollupsingleLineTextSQL aggregate / JOIN
SKU Rollup (from Shopping Lists)rollupsingleLineTextSQL aggregate / JOIN
Field Details
Status singleSelect
Records: 3,102 · Null: 62 (2%) · 4 choices (2 used)
ChoiceCount%
Active2,21072%
Needs Update83027%
Discontinued00% — never used
On Hold00% — never used
Manufacturer multipleRecordLinks
Links to: Manufacturers · 0 links: 5% · 1 link: 95% · 2+ links: 0%
→ Many-to-One (FK)
All records have 0-1 links — Many-to-One relationship (single FK)
Generated by airtable-migration-audit · straktur.com · Report stored locally — never uploaded

Your report will be customized to your actual data — field types, relationships, and quality issues unique to your base.

What most Airtable bases reveal

Select fields that should be lookup tables

Airtable select fields are convenient but become a problem at scale. When a Status field has the same 5 choices across 3 tables, that's 3 copies of the same data. The audit detects these and recommends normalizing them into shared lookup tables with foreign keys.

Linked records that lie about cardinality

Airtable shows every linked record field as if it could hold multiple values. But in practice, most linked record fields are used as single-value foreign keys. The audit checks actual data — if no record ever has more than one link, it's a Many-to-One, not Many-to-Many. This distinction determines whether you need a simple FK column or a full junction table.

Computed fields you'll need to recreate

Formulas, rollups, lookups, and counts in Airtable are calculated server-side. They don't export as data — they export as snapshots that go stale immediately. The audit inventories every computed field so you know exactly what application logic or database views to build in your new system.

Hidden data quality issues

Fields with 60% null values. Text fields storing what should be numbers. A text field with only 4 unique values across 1,631 records — that's not free text, that's a dictionary that should be a lookup table. These issues are invisible in Airtable's UI but critical for a clean migration. The audit surfaces them automatically.

Ready to build? Skip 2 weeks of boilerplate.

The audit tells you what to build. Straktur gives you where to build it.

Straktur is a Next.js boilerplate for internal business apps — the kind of app you'd build to replace Airtable. It comes with authentication, CRUD generators, Drizzle ORM with PostgreSQL, and an architecture optimized for AI coding agents.

The audit generates a MIGRATION.json file that maps your Airtable structure to Drizzle schema, lookup tables, and junction tables — including Airtable-to-PostgreSQL type mapping and validation constraints. Feed it to Straktur and start with a working app skeleton instead of a blank project.

One-time fee. No per-seat pricing. No subscription.

Airtable Base

airtable-migration-audit

Migration Report

+ MIGRATION.json

Straktur

Next.js App

with Drizzle Schema

Your Airtable base is waiting

npx skills add mperlak/airtable-migration-audit

Built by Marcin — who migrated his own company off Airtable after 6 years and 49,000+ records.