---
title: "How to Migrate From Bubble to Custom Code Without Downtime"
author: "Nate Laquis"
author_role: "Founder & CEO"
date: "2026-05-23"
category: "How to Build"
tags:
  - migrate from Bubble to custom code
  - Bubble migration guide
  - no-code to custom code
  - zero downtime migration
  - Bubble to Next.js
  - Bubble database migration
  - custom code migration strategy
excerpt: "Your Bubble app got you paying customers. Now it is buckling under real load and you need to migrate to custom code. Here is exactly how to do it without a single second of downtime or a single lost user."
reading_time: "12 min read"
canonical_url: "https://kanopylabs.com/blog/how-to-migrate-from-bubble-to-custom-code"
---

# How to Migrate From Bubble to Custom Code Without Downtime

## Why Bubble Migrations Fail and How Yours Will Not

Most Bubble-to-custom-code migrations fail for the same reason: founders treat them like a rewrite instead of a migration. They shut down the Bubble app on a Friday night, flip a DNS switch, and pray that the new codebase handles everything. Users hit broken flows. Data gets lost between systems. Support tickets flood in. Two weeks later the team is running both platforms simultaneously, paying double, fixing bugs on both sides.

![Developer writing migration code on a screen with multiple terminal windows open](https://images.unsplash.com/photo-1555949963-ff9fe0c870eb?w=800&q=80)

We have migrated over a dozen Bubble apps to custom code at Kanopy, and the pattern is clear. The teams that succeed treat migration as a phased parallel operation, not a big-bang switchover. Your Bubble app keeps running. Your custom app launches alongside it. You shift traffic gradually, feature by feature, validating every step. When you finally decommission Bubble, nobody notices because every user has already been running on custom code for weeks.

This guide walks you through exactly how to pull that off. Not theory, not hand-waving about "best practices." Specific steps, tools, and timelines based on real projects. If you have 500 or more active users and your Bubble app is generating revenue, you cannot afford a migration that involves downtime. Your users will leave. Your competitors will pounce. And rebuilding trust after a botched migration takes far longer than doing it right the first time.

The approach here works for Bubble apps of any complexity, from simple CRUD tools to multi-tenant marketplaces with Stripe Connect, real-time messaging, and complex permission models. The specifics change, but the strategy stays the same: parallel infrastructure, phased cutover, zero downtime.

## Auditing Your Bubble App Before Writing a Single Line of Code

Before you touch your new codebase, you need a ruthless inventory of what your Bubble app actually does. This is not optional. Bubble hides an enormous amount of complexity behind its visual interface, and every team we have worked with underestimates their app's scope by at least 30 percent on the first pass.

Start with your data model. Go to the Data tab in Bubble and export your full schema. Document every data type, every field, every relationship. Pay close attention to fields of type "List of [Something]" because those are implicit many-to-many relationships that Bubble manages behind the scenes. In PostgreSQL or MySQL, those become join tables, and getting them wrong corrupts your relational integrity. Count your total records per table. If you have more than 100,000 records in any single type, your data migration strategy needs dedicated tooling, not a manual CSV export.

Next, map your workflows. Open the Workflow tab and document every single workflow trigger, condition, and action chain. Bubble workflows are the closest thing to business logic your app has, and they are notoriously hard to reverse-engineer after the fact. Group them by page and by trigger type (page load, button click, custom event, recurring event, API workflow). The workflows that run on the backend as API workflows are especially important because they likely handle payments, notifications, and data transformations that users never see but absolutely depend on.

Then audit your plugins. Go to the Plugins tab and list every installed plugin, whether it is active, and what it does. Common ones include Stripe for payments, SendGrid or Postmark for email, Algolia for search, and various OAuth plugins for social login. Each plugin represents an integration you will need to rebuild in custom code. Some are straightforward (Stripe has excellent SDKs in every language). Others, like Bubble-specific plugins that wrap obscure APIs, may require significant research to replicate.

Finally, catalog your API endpoints. If your Bubble app exposes a Data API or has backend API workflows that external services call, those endpoints need to remain functional during migration. Document every endpoint URL, method, request format, and response shape. Your custom app will need to replicate these exactly, or you will break every external integration that depends on them.

This audit typically takes two to five days for a moderately complex app. Do not skip it. The audit document becomes your migration spec, your test plan, and your progress tracker all in one. If you want to understand the [full cost implications of a no-code migration](/blog/how-much-does-it-cost-to-migrate-from-no-code-to-custom), starting with a proper audit is what separates accurate estimates from expensive surprises.

## Choosing Your Target Stack and Architecture

Your tech stack choice determines your migration complexity, your long-term maintenance cost, and how easy it will be to hire developers down the road. After migrating dozens of Bubble apps, we have strong opinions about what works.

### Frontend: Next.js or Remix

For most Bubble migrations, Next.js is the clear winner. It gives you server-side rendering for SEO-critical pages, API routes for backend logic, and a massive ecosystem of libraries. If your Bubble app is a content-heavy marketplace or a SaaS dashboard, Next.js handles both use cases elegantly. Remix is a strong alternative if your app is form-heavy and you want progressive enhancement out of the box. We default to Next.js unless there is a specific reason not to.

### Backend: Node.js with Express or Fastify

If your Bubble app has complex backend logic that outgrows Next.js API routes, split it into a dedicated backend service. Fastify is our recommendation over Express for new projects. It is faster, has better TypeScript support, and its plugin system encourages clean architecture. For apps with heavy background processing (batch email sends, report generation, data aggregation), add BullMQ with Redis for job queues. Bubble's "Schedule API Workflow" feature maps directly to a background job queue.

### Database: PostgreSQL, Always

There is no debate here. PostgreSQL handles everything Bubble's database does, with dramatically better performance. Bubble's "Do a search for" becomes a SQL query with proper indexing. Bubble's "List of Things" becomes a join table with foreign keys. Bubble's "Privacy Rules" become row-level security policies. Use Prisma or Drizzle as your ORM. Prisma has better documentation and tooling. Drizzle is lighter weight and closer to raw SQL. We use Prisma for most migrations because its migration system makes iterating on the schema straightforward.

### Infrastructure: Vercel, Railway, or AWS

For startups under 50,000 monthly active users, Vercel for the frontend and Railway for the backend and database gives you a deployment experience almost as simple as Bubble with none of the performance constraints. Monthly cost: $30 to $150 depending on traffic. For apps that need more control, compliance certifications, or multi-region deployment, go straight to AWS with ECS or EKS. Monthly cost: $200 to $800 for a production setup with proper monitoring. Either way, you are spending a fraction of what Bubble charges at scale.

![Laptop showing a modern code editor with a TypeScript project for a Bubble migration](https://images.unsplash.com/photo-1517694712202-14dd9538aa97?w=800&q=80)

The key principle: pick boring technology. Your migration is already complex enough. Do not layer on experimental frameworks, bleeding-edge databases, or infrastructure you have never operated in production. Next.js, PostgreSQL, and Vercel/Railway are battle-tested, well-documented, and easy to hire for. That is what matters when you are migrating a live product.

## Extracting and Migrating Your Data Without Corruption

Data migration is where Bubble-to-custom-code projects live or die. Bubble stores data in its own proprietary format with unique IDs, implicit relationships, and data types that do not map cleanly to standard SQL databases. Get this wrong and you corrupt user records, break payment histories, or lose months of transaction data. Here is how to get it right.

### Step 1: Export from Bubble

Bubble offers CSV exports from the Data tab, but these are unreliable for large datasets. CSVs truncate long text fields, mangle date formats, and flatten list relationships into comma-separated strings. For any app with more than 10,000 records, use the Bubble Data API instead. Write a script that paginates through every data type, fetches records in batches of 100, and stores them as JSON. This preserves data types, nested objects, and list relationships. A simple Node.js script with Axios handles this in under 100 lines of code. Budget one to three hours for the extraction script and two to eight hours for the actual extraction, depending on your data volume.

### Step 2: Transform Data Types

Bubble's internal data types need translation. Bubble's unique IDs (which look like "1679842389284x382947293847") need to be mapped to UUIDs or auto-incrementing integers. Bubble's "Geographic Address" fields need to be split into structured address components (street, city, state, zip, lat, lng). Bubble's "Date Range" fields become two separate timestamp columns (start_at, end_at). Bubble's "Image" fields store Bubble CDN URLs that will stop working when you cancel your Bubble subscription, so you need to download every image and re-upload to S3 or Cloudflare R2. Build a transformation pipeline that handles each of these conversions. We typically use a series of TypeScript functions, one per data type, that read the raw Bubble JSON and output clean PostgreSQL-ready records.

### Step 3: Maintain an ID Mapping Table

This is the step everyone forgets. Create a mapping table that links every Bubble unique ID to its new database ID. You will need this mapping for the entire duration of the migration because your Bubble app and your custom app will be running simultaneously. When a user creates a record in Bubble during the transition period, you need to sync it to PostgreSQL using the correct foreign key relationships. Without the mapping table, you cannot resolve those relationships. Keep the mapping table in PostgreSQL alongside your production data, and do not delete it until the Bubble app is fully decommissioned.

### Step 4: Validate Everything

After the initial data migration, run validation scripts that compare record counts, check referential integrity, and verify that critical business data (account balances, subscription statuses, payment histories) matches between Bubble and PostgreSQL. We build a validation dashboard that runs these checks automatically and flags discrepancies. This is not optional. Every Bubble migration we have done has uncovered data inconsistencies during validation, from duplicate records that Bubble allowed because it lacks unique constraints, to orphaned relationships where one side of a link was deleted but the other was not.

## The Parallel Deployment Strategy for Zero Downtime

This is the core technique that makes zero-downtime migration possible. Instead of replacing Bubble all at once, you run both systems in parallel and shift users gradually. Here is the exact playbook.

### Phase 1: Build the Custom App Behind a Feature Flag (Weeks 1 to 6)

Build your custom application while your Bubble app continues serving all production traffic. Do not touch Bubble. Do not redirect users. Just build. Deploy your custom app to a staging environment and test against migrated data. Use LaunchDarkly, Unleash, or even a simple database flag to control which users see the new experience. At this stage, zero users are on the custom app. You are building and testing in isolation.

### Phase 2: Dual-Write with Bubble as Primary (Weeks 6 to 8)

Set up a synchronization layer between Bubble and your custom database. When a user performs an action in Bubble (creates a record, updates a profile, makes a payment), a Bubble API workflow fires and sends that event to your custom backend via webhook. Your custom backend processes the event and writes it to PostgreSQL. This dual-write system ensures your custom database stays current with production data. Build it for eventual consistency, not strict real-time sync. A few seconds of delay is fine. What matters is that no data gets lost.

Critically, Bubble remains the primary system during this phase. If the sync fails, users are unaffected because they are still using Bubble. You monitor the sync pipeline, fix failures, and build confidence that your custom backend handles every event type correctly.

### Phase 3: Canary Release to 5 Percent of Users (Weeks 8 to 10)

Route 5 percent of your traffic to the custom app using your feature flag system. Choose a user segment that is representative but low-risk, perhaps internal team members, beta testers, or users in a specific geography. Monitor everything: error rates, page load times, conversion funnels, support tickets. Compare metrics between the Bubble cohort and the custom code cohort. If the custom app performs as well or better on every metric, expand to 20 percent. If something breaks, roll back instantly by flipping the feature flag. The Bubble app is still running. Nothing is lost.

### Phase 4: Progressive Rollout to 100 Percent (Weeks 10 to 12)

Increase traffic to the custom app in increments: 20 percent, 50 percent, 80 percent, 100 percent. At each step, monitor for a full 48 hours before expanding further. During this phase, reverse the dual-write direction. The custom app becomes the primary system, and you sync data back to Bubble so the remaining Bubble users see current data. By the time you reach 100 percent, the Bubble app is receiving zero traffic but still has current data as a fallback.

### Phase 5: Decommission Bubble (Week 12 to 14)

Once all users have been on the custom app for at least two weeks with no issues, shut down the dual-write sync and cancel your Bubble subscription. Keep a final data export as an archive. Update DNS records to remove any Bubble references. You are done.

If you are planning a more complex migration that involves multiple source platforms, our guide on [building a no-code to pro-code migration pipeline](/blog/how-to-build-a-no-code-to-pro-code-migration-pipeline) covers the orchestration layer you will need.

## Handling Authentication, Payments, and Integrations

Three areas trip up nearly every Bubble migration: user authentication, payment processing, and third-party integrations. Each one requires specific handling to avoid disrupting users.

![Code on a monitor showing authentication and payment integration logic during a migration](https://images.unsplash.com/photo-1461749280684-dccba630e2f6?w=800&q=80)

### Authentication: Migrate Sessions, Not Passwords

Bubble hashes passwords using its own algorithm, and you cannot export the hashes. This means you cannot simply copy user credentials to your new system. The standard approach: use a "lazy migration" pattern. Set up authentication in your custom app using NextAuth.js, Clerk, or Auth0. When a user logs in for the first time after migration, prompt them to "set a new password" via a password reset flow that feels natural, not alarming. Frame it as a security upgrade. For users who log in via Google, Facebook, or other OAuth providers, the migration is seamless because OAuth tokens are provider-side. Just configure the same OAuth apps in your custom backend, and users log in without noticing anything changed.

If you use Clerk or Auth0, you can pre-import user profiles (email, name, metadata) so the accounts exist before users arrive. They just need to authenticate fresh. This eliminates the "sign up again" friction that kills retention during migrations.

### Payments: Keep the Same Stripe Account

If your Bubble app uses Stripe (and it probably does), your migration is straightforward. Stripe accounts are independent of your application platform. Your customer objects, subscription records, payment methods, and invoice history all live in Stripe, not in Bubble. Point your custom app's Stripe integration at the same Stripe account using the same API keys, and everything works. Subscriptions keep billing. Saved cards keep working. Invoice history remains accessible.

The one trap: Bubble's Stripe plugin creates Stripe webhooks that point to Bubble endpoints. Before decommissioning Bubble, update your Stripe webhook endpoints to point to your custom backend. If you miss this, Stripe events (successful payments, failed charges, subscription cancellations) will fire into the void, and your app will not know about them. Test the webhook switchover thoroughly in Stripe's test mode before touching production.

### Third-Party Integrations

SendGrid, Twilio, Intercom, Segment, Mixpanel, Slack webhooks, Zapier automations: every integration your Bubble app uses needs to be reconnected to your custom backend. Prioritize them by business impact. Payment and notification integrations come first. Analytics and CRM integrations can wait. For Zapier automations, the move to custom code is actually an upgrade because you replace fragile multi-step Zaps with direct API calls or proper event-driven architectures using something like Inngest or Trigger.dev. Every Zapier automation we have replaced with native code has been faster, more reliable, and cheaper to operate.

## Testing, Monitoring, and Knowing When You Are Done

A zero-downtime migration is only as good as your testing and monitoring. If you cannot prove that the custom app works at least as well as Bubble, you are gambling with your users and your revenue.

### Automated Testing Before Launch

Write end-to-end tests for every critical user flow: sign up, log in, create the core resource, make a payment, receive a notification. Use Playwright or Cypress. These tests should run against both the Bubble app and the custom app so you can compare behavior directly. If a user can do something in Bubble, they must be able to do the same thing in the custom app with equal or better performance. We typically write 40 to 80 end-to-end tests for a mid-complexity migration. They catch regressions that manual QA misses, especially edge cases around permissions, error handling, and empty states.

### Performance Benchmarks

Before you route a single user to the custom app, benchmark it. Measure page load times, API response times, and database query durations. Set targets: every page loads in under one second, every API call responds in under 200 milliseconds, no database query exceeds 50 milliseconds. These targets should be easy to hit because you are migrating away from Bubble precisely because it is slow. If your custom app is not dramatically faster, something is wrong with your architecture, and you need to fix it before going live.

### Production Monitoring

Deploy monitoring on day one, not after launch. Use Sentry for error tracking, Datadog or Grafana Cloud for infrastructure metrics, and Posthog or Mixpanel for user behavior analytics. Set up alerts for error rate spikes, response time degradation, and database connection pool exhaustion. During the parallel deployment phase, compare these metrics between your Bubble cohort and your custom code cohort in real time. Any statistically significant regression in the custom app is a blocker for expanding the rollout.

### Knowing When to Decommission

You are ready to turn off Bubble when all of the following are true. 100 percent of users have been on the custom app for at least 14 days. Error rates on the custom app are equal to or lower than error rates on Bubble. Support ticket volume has returned to pre-migration baseline. All payment flows have processed at least one full billing cycle on the custom backend. No data sync discrepancies have been detected in the last seven days.

Do not rush this. The cost of running Bubble for an extra two weeks is trivial compared to the cost of a premature decommission that forces a rollback. We have seen teams save themselves by keeping Bubble running as a safety net for one extra billing cycle. Patience at this stage is the cheapest insurance you will ever buy.

If you are weighing whether to tackle this migration yourself or bring in a team that has done it before, [book a free strategy call](/get-started) and we will walk through your specific app, timeline, and budget. Migrations like this are exactly what we do at Kanopy, and we would rather help you plan it right than watch you learn the hard lessons the expensive way.

---

*Originally published on [Kanopy Labs](https://kanopylabs.com/blog/how-to-migrate-from-bubble-to-custom-code)*
