Engineering decisions

Migrating off Bubble to custom code

EdgeBrain Studios12 min read

Leave Bubble when its engineering constraints start costing more than the rebuild will, not when you cross a revenue line. Four triggers actually matter: your workflow-unit bill grows faster than your revenue, a list view needs a compound index nobody can create, you can’t review a change before it ships, and an enterprise buyer sends a security questionnaire you can’t answer. Know what you’re buying, too. Bubble’s visual workflows can’t be exported, so this is a rewrite of logic against an exported dataset. Anyone calling it a migration is selling you something.

The rest of this piece argues for one specific shape of that rewrite: move the data layer to Postgres first, run both surfaces against it, and cut over screen by screen. Big-bang rebuilds put a revenue-shaped hole in the middle of your year.

Why revenue thresholds are the wrong trigger

Every guide competing for this query answers “when” with a number about you rather than a number about your system. The figures in circulation are consistent enough to look authoritative: rebuild somewhere around $5,000–$15,000 MRR, expect degradation past 30,000–50,000 records in a table, and again somewhere near 100,000 daily active users. Those are the published claims of the agencies ranking for this term, not measurements we have taken.

They’re wrong in both directions, which is the tell. A scheduling tool with 4,000 records and eleven nested backend workflows firing on every booking will hurt long before 30,000 rows. A read-heavy directory with 400,000 records and three write paths will sit on Bubble happily for years. Record count is a proxy for query shape, and proxies drift.

MRR is worse. It measures your ability to pay for a rebuild, which is why vendors like it. It says nothing about whether the platform is actually the binding constraint on your roadmap. Plenty of teams at $20,000 MRR should keep shipping features. Some at $4,000 are already losing deals to a control they can’t implement.

The four engineering triggers that mean it’s time

Each of these is observable this week, from inside your own account, in under a day. Two or more firing at once is a real signal.

TriggerHow to measure itWhat it means
Workflow-unit cost curvePlot WU consumed per paying account, month over monthRising per-account cost means unit economics degrade with growth
Query planner ceilingTime your slowest list view with filters applied and cache clearedNo index DDL, no EXPLAIN, no way to fix it from inside
No diffable historyTry to answer “what changed last Tuesday, and who approved it”No code review, so no defence against a bad change
Security questionnaireRead the last one a prospect sent and count what you cannot answerCompliance is now a sales blocker, not an engineering nicety

Your workflow-unit bill curves away from your revenue

Bubble meters usage in workflow units. Consumption tracks the work your app does rather than the seats you sell, so a feature that loops over a list server-side costs in proportion to list length. Recursive workflows, scheduled sweeps, and API polling are the usual offenders.

The metric to watch is not the invoice. It is workflow units per paying account. Flat or falling is fine at any absolute price. Rising is the signal, because it means every new customer is worth less than the last one, and no amount of optimisation inside a visual workflow editor changes the shape of that curve. It buys you a quarter or two.

A list view needs a compound index and you cannot create one

This is the constraint that ends most Bubble apps, and it’s the one the incumbent guides describe as “scalability” without naming the mechanism. You don’t get a psql connection. You can’t run EXPLAIN (ANALYZE, BUFFERS), you can’t create a partial index, and you cannot add a compound index on (org_id, status, created_at DESC) to make the one screen every user opens all day return in 40ms instead of 4 seconds.

A repeating group with four constraints, a sort, and a privacy rule is a query. It has a plan. You simply have no access to the plan and no lever to change it. Every optimisation available to you, whether denormalising fields onto the parent thing, precomputing counts on write, or splitting a table, is a workaround for a missing index. Those workarounds accumulate, and each one adds write-path complexity that will show up again as data drift.

You cannot review a change before it ships

Bubble has version history and branching. What it doesn’t have is a textual diff, which means it cannot have a pull request, which means it cannot have a code review. Nobody can look at a fourteen-step workflow and a colleague’s changes to it side by side and say “this branch drops the null check on line six.”

With one or two builders that’s survivable. At four it isn’t. The cost shows up as an unusual class of incident: changes nobody remembers making, in workflows nobody can attribute, discovered by a customer. This is the same failure pattern we describe in how to judge whether to rewrite or refactor a codebase you inherited, except that here it’s a property of the platform rather than of the team, so no amount of discipline fixes it.

A security questionnaire stalls an enterprise deal

Bubble maintains platform-level compliance on its higher tiers, and that’s real. It doesn’t transfer to your application. A SOC 2 Type II audit asks about your controls: change management with segregation of duties, evidence that changes were reviewed and approved before production, access logs at the record level, a documented incident process with artefacts.

Two of those are effectively unanswerable when your change management has no diffs and no approvals. You can write a compensating control and some auditors will accept it. What you cannot do is make the answer boring, and enterprise procurement is a game where boring answers win. If you sell into healthcare or payments, this trigger fires earliest and hardest, usually during the first seven-figure conversation you have.

There is no export, so there is no migration

Be precise about the thing being sold to you. Your data comes out: Bubble exposes CSV export from the Data tab and a Data API, and Airtable has a documented REST API, rate-limited at five requests per second per base, which matters a great deal when you’re pulling 200,000 records out of it.

Your logic does not come out. Visual workflows have no export format, no source representation you can transpile, and no intermediate anyone can parse. Every conditional, every “only when” clause, every privacy rule and every scheduled workflow has to be read by a human on one screen and re-implemented on another. There’s no tool, and no AI shortcut worth trusting, because the failure mode is a silently missing edge case rather than a compile error.

This has one useful consequence for how you buy. Any quote that prices “migration” by record count or table count is pricing the easy half. The expensive half is the workflow inventory, and the first thing a competent partner should do is count them: how many backend workflows, how many API workflows, how many pages, how many privacy rules, and which of them nobody can explain any more. That last category is your real risk register.

The strangler fig: move data first, rewrite surfaces second

The standard proposal on this SERP is a parallel rebuild followed by a cutover weekend. We wouldn’t run it that way for a product with live revenue. A three-month freeze on shipping, ending with a single irreversible switch, concentrates all the risk at the exact moment you have the least slack.

The alternative inverts the order. Postgres becomes the system of record first, while Bubble is still the front end, and then surfaces move one at a time.

Phase one: Postgres becomes the system of record

Model the schema properly, with real foreign keys, real constraints, real enums, timestamps in UTC, and row-level security policies expressing what Bubble’s privacy rules were trying to say. Backfill from the Data API. Keep Bubble’s unique id as an indexed external key column so every later reconciliation has a join.

Then connect Bubble to it. The SQL Database Connector, available on higher plans, lets Bubble read and write an external Postgres directly. Where the connector can’t reach, a small sync worker with idempotency keys and a reconciliation job that diffs both sides nightly will do. Our preference for the target is Postgres with row-level security in place from day one, which is most of what our Supabase and Postgres work consists of.

Phase two: one surface at a time

Now the rebuild is incremental and reversible. Put a reverse proxy in front, route /app/reportsto the new Next.js implementation and leave everything else on Bubble. Both read the same rows, so there’s no sync question and no data-freshness bug class. Ship the highest-pain screen first, usually the slow list view from trigger two, which is also the one that proves the compound index was the whole problem.

As an illustration of the sequencing, not a claim about any particular engagement: reports, then admin tooling, then the customer dashboard, then auth and billing last, because auth is the piece where a half-migrated state hurts most.

Phase three: turn Bubble off deliberately

When the last surface moves, cancel the plan. Not before. Keep an export of the app and a database snapshot from the final day, because the workflow inventory you built in phase one is the only documentation of six years of business rules that will ever exist.

Where this approach costs you

The strangler fig is not free, and the parallel-rebuild crowd is not being stupid. Three real costs.

Two systems, two sets of bugs.For the length of the migration you’re paying Bubble and paying for hosting, and every incident starts with a question about which side it came from. Budget the overlap explicitly rather than pretending it’s a rounding error.

The connector is a latency tax.External database calls from Bubble are synchronous and slower than its native data layer. Some screens get worse before they get better, and you won’t get a transaction that spans a Bubble workflow and your Postgres writes. Design for idempotency, not for atomicity across the boundary.

It suits some apps badly.If your Bubble app is genuinely small, meaning a dozen workflows, three tables and one screen, the phased approach is ceremony. Rebuild it directly in six weeks and skip the connector entirely. The phased route earns its overhead when there’s enough live revenue that a bad cutover weekend would be a business event.

What it costs, and who is saying so

The competing guides on this query converge on $30,000–$50,000 and two to four months for a mid-complexity SaaS rebuild. That’s their published range, cited here because it’s the number you’ll encounter elsewhere on this search, and it’s a reasonable order of magnitude for the work described above. We haven’t verified it and it is not ours.

Ours, published on our web development pages and unchanged for this article: MVPs start at $24,000, platform builds start at $28,000, and most projects land between $18,000 and $45,000. Those bands are estimates in US dollars, not quotations; the quote that follows scoping is fixed in writing before kickoff. A phase-one data-layer move alone sits well below a full platform build, because it’s schema work plus a connector plus reconciliation rather than a product rebuild. If you want to pressure-test a number before talking to anyone, our MVP cost estimator shows which inputs move it, and what building an MVP actually costs takes the same arithmetic apart in more detail.

The variable nobody quotes on is the one that decides it: how many workflows exist, and how many of them anyone still understands. Count them before you accept a fixed price, and be suspicious of any bid offered without that count.

When the answer is to stay on Bubble

Three cases, said plainly, where hiring us or anyone else would be the wrong call.

You are still finding product-market fit. If your data model changed twice this quarter, Bubble is the correct tool and a rebuild converts your fastest advantage into your slowest. Iterate until the model stops moving.

Your app is internal tooling. Forty employees, no external buyers, no compliance surface. None of the four triggers can fire in a way that matters. Spend the $30,000 somewhere it earns.

Something off the shelf already does it.A large share of Bubble apps are a CRM, a booking flow, or a client portal with your logo on it. If a $200-a-month vertical SaaS covers eighty percent, buy it and integrate the remaining twenty. Custom software has to earn its maintenance cost every year, forever, and a lot of these don’t.

The two-week diagnostic to run before you commit

Do this before you accept a proposal from anybody, us included. Week one: pull the workflow inventory, meaning every backend workflow, API workflow, page and privacy rule, tagged owned, understood, or orphaned. Plot workflow units per paying account for the last six months. Time your three slowest screens with a cold cache.

Week two: model the two hardest tables in real Postgres and load a production-sized copy into them. If the compound index turns the slow screen fast on real volume, you’ve proved the diagnosis and you know what phase one buys. If it does not, your problem was never the platform, and a rebuild would have moved it rather than fixed it.

That’s a fortnight and it de-risks a six-figure decision. If you’d rather have a second opinion on the inventory before you start, send us the numbers, meaning the workflow count, the WU curve and the slow screen, and we’ll tell you which of the four triggers is actually firing, including when the answer is that none of them are.

Got a version of this problem?

Send a paragraph describing what you’re trying to build and what’s currently in the way. We reply inside two working days with a scope, a price and a start date. If we’re not the right people for it, we say that instead.