Quickbase Liberation
A three-phase data platform that freed a marketing agency's entire operational history from Quickbase: a 537,000-record export engine, a self-hosted searchable backup viewer, and a schema-mapped migration pipeline into the team's Notion workspace with nothing silently lost.
The problem
The agency ran its entire operation, clients, projects, tasks, sales pipeline, and time tracking, on Quickbase: a legacy low-code database with per-seat licensing, no practical export path, and 15-plus years of accumulated business history locked inside. The team was moving day-to-day operations to Notion, but faced three risks: no complete backup existed outside the vendor, loading 135,000 historical action items into Notion would bury the team while abandoning them would erase institutional memory, and the team had hand-built a Notion framework with its own property names and relations that a naive record dump would not fit.
What was built
A Python CLI with three fully decoupled phases, each consuming only the output of the previous one.
Phase 1: complete raw export
An exhaustive Quickbase API export: app metadata, table schemas, report definitions, every record from every table, and every file attachment decoded to disk. Rate-limit handling with backoff, atomic per-table writes, and a state file make every run resumable; a crash never corrupts or restarts the archive. The result is a permanent, vendor-independent archive: 537,060 records and 33,600-plus attachments, roughly 53 GB.
Phase 2: backup database and read-only viewer
The raw archive compiles into a single SQLite database, and a verification command cross-checks record counts across raw JSON, CSV, and SQLite, validates every attachment on disk, and spot-checks field values against the archive. The migration was never trusted without it. On top sits a FastAPI read-only viewer that mirrors how the data looked in Quickbase: default-report column order, form-style record pages, related-record navigation, and attachment serving, deployed with Docker behind HTTPS so the full archive is one URL away for the whole team.
Phase 3: Notion migration, two modes
Mirror mode recreates Quickbase tables as new Notion databases, with two-pass relation loading and resumable state. Framework mode solves the harder problem: loading the four core tables into the team’s existing hand-built Notion databases, translating every field into the team’s own property names and option sets.
- A field-mapping specification covering per-field transforms and select-option translations got stakeholder sign-off before any code.
- Preservation-first design: any value with no clean Notion equivalent lands in an original-value property instead of being dropped.
- Preflight schema validation halts the load if the live workspace has drifted from the spec, and the loader self-provisions its own properties.
- Every migrated page deep-links back to its full history in the backup viewer, which is how 15 years stays accessible without flooding the working tool.
- Scoped loading: only clients with open projects (229 of 2,356) and their active work migrate, roughly 16,500 pages instead of 537,000 records.
Engineering decisions worth noting
- Decoupled phases with verification gates. Nothing advances until verification passes, which is what made a 53 GB, half-million-record migration safe to run unattended.
- Resumability everywhere. Export, build, and both loaders checkpoint to state files. The framework loader checkpoints after every single page create, because Notion page creation is not idempotent.
- Test-driven where corruption hides. The pure mapping layer is fully unit-tested (53 tests), because silent data corruption lives in mappers, not HTTP calls.
- Defensive correctness under review. Multi-pass code review caught real hazards before production: a SQL predicate that silently dropped NULL-status clients, an unguarded foreign-key parse that could kill a run mid-linking, and a checkpoint gap.
- API limits as design constraints. Notion cannot add options to status-type properties or write certain property types via API; the design routes around both rather than pretending they do not exist.
Key numbers
- 537,060 records exported and verified, plus 33,600-plus attachments (roughly 53 GB).
- Backup viewer live over HTTPS with the full archive searchable.
- Tester load of 450 pages ran with zero API errors and verified relations.
- Production migration scoped to 229 clients, roughly 16,500 pages.
- Cost: one VPS, replacing per-seat legacy licensing for archival access.
Status
Export, backup database, deployed viewer, and both Notion loaders complete and tester-verified, along with finishing work in the team’s workspace: role dashboards, live hub tables, and enriched client pages.
Questions
FAQ
What is Quickbase Liberation?
A three-phase data platform Brian Powell built to move a marketing agency off Quickbase without losing 15 years of history. A Python pipeline exported and verified 537,000 records and 53 GB of attachments, a self-hosted viewer made the archive permanently searchable, and a schema-mapped loader migrated active work into the team's existing Notion workspace.
How was 15 years of history kept without flooding Notion?
Only clients with open projects and their active work migrated into Notion, roughly 16,500 pages instead of 537,000 records. Every migrated page carries a deep link back to its full history in the self-hosted backup viewer, so the archive stays one click away without burying the working tool.
How was the migration kept safe?
The three phases are fully decoupled, each consuming only the previous phase's verified output, and nothing advances until a verification command cross-checking record counts, attachments, and field values passes. The Notion loader checkpoints after every page create, because page creation is not idempotent and a mid-run crash must never duplicate records in a live workspace.
What happens to data with no clean Notion equivalent?
It is never dropped. Any value without a clean mapping lands in a dedicated original-value property on the migrated page, and API limitations, like property types Notion cannot write, were routed around with approved fallback mappings rather than ignored.