Founder & Lead Engineer, RAITHub
RAITHub ships and tests production software. See QA as a Service or talk to us.
When an EdTech app on no-code slows under real cohorts, hits a feature wall, or gets expensive, migrate in slices, not a big-bang rewrite. Export and model your data first, rebuild the riskiest workflow behind the front end you have, run old and new in parallel, and cut over one piece at a time. The danger is never the new code; it is losing learner data in transit.
If you would rather hand the migration to someone who does this safely, see how RAITHub would do it below.
This post is for founders whose learning app on Bubble, or a similar no-code platform, has outgrown it. It is engineering guidance drawn from RAITHub's code-rescue work and from PadhAI, the EdTech platform RAITHub built. The generic version is how to migrate from Bubble to real code without downtime; this is the EdTech case, where losing a student's course progress is the unforgivable failure.
When has an EdTech app actually outgrown no-code?
Not when a thread tells you to leave no-code, but when the platform is blocking the business. Look for these signals together, not one in isolation.
| Signal | What it means | Migrate? |
|---|---|---|
| Slow or timing out with a full cohort | The workload has passed what the platform serves well | Yes, if it is costing you renewals |
| A feature you need is impossible | Server-owned exam timers, complex grading, local payment rails | Yes, for that part first |
| Costs rising faster than revenue | Usage-based pricing outpacing a thin margin | Weigh it; model the three-year cost |
| You cannot pass a school's data or security review | No control over where data lives or who can see it | Yes, if institutions are your buyers |
| You just want it to feel faster | Could be a tuning issue, not a platform wall | No; diagnose first |
If the symptom is only speed, rule out a fixable bottleneck first, as in surviving the exam-season concurrency spike, before committing to a migration.
Why not just rewrite the whole thing at once?
Because a big-bang rewrite means running blind for months, then flipping a switch and hoping. During that time the old product still needs changes, students keep generating data, and the two versions drift. The safer path is the strangler pattern: build the new system piece by piece behind the existing front end, move one workflow at a time, and keep the old one running until the new one is proven. The generic strangler approach, and when a rewrite genuinely beats a refactor, are in rewrite vs refactor.
How do I migrate the data without losing student progress?
Treat data as the hard part and the code as the easy part. Student records, course progress, grades and payments are the irreplaceable asset.
- Export everything early and often. Pull a full export from the no-code platform and inspect it before you design anything. You are looking for what it actually stores, not what you assumed.
- Model the real relationships. No-code tools often flatten data. Rebuild students, enrolments, attempts, grades and payments as proper related tables, with keys that match the exports.
- Write an idempotent import. A migration you can run twice without creating duplicates lets you dry-run against staging repeatedly. Make it safe to re-run, as in idempotency in API design.
- Reconcile with counts and checksums. After each import, assert that the number of students, enrolments and grades matches, and spot-check real records. A silent off-by-one in grade import is a trust-ending bug.
A minimal, re-runnable import that will not double-create learners:
type LegacyStudent = { legacyId: string; email: string; name: string }
// Upsert by a stable natural key (legacyId) so re-running the migration is safe.
export async function importStudents(db: Db, rows: LegacyStudent[]) {
let created = 0
let updated = 0
for (const r of rows) {
const existing = await db.findByLegacyId(r.legacyId)
if (existing) {
await db.updateStudent(existing.id, { email: r.email, name: r.name })
updated++
} else {
await db.insertStudent({ legacyId: r.legacyId, email: r.email, name: r.name })
created++
}
}
return { created, updated, total: rows.length } // reconcile these against the export
}
interface Db {
findByLegacyId(legacyId: string): Promise<{ id: string } | null>
insertStudent(s: { legacyId: string; email: string; name: string }): Promise<void>
updateStudent(id: string, patch: { email: string; name: string }): Promise<void>
}
The money paths need the same care: migrating payments and enrolments without double-counting is its own discipline, close to zero-downtime SaaS migrations.
What order should I move things in?
Start with the workflow that is the biggest risk or the biggest limit, and leave the easy, stable parts on no-code until last.
| Phase | What moves | Why this order |
|---|---|---|
| 1. Audit and export | A full data export, a map of every workflow, and a parallel database | You cannot plan a migration you have not measured |
| 2. The riskiest workflow | The feature that is failing or impossible, e.g. the exam engine | Prove the new stack on the hardest part first |
| 3. Core records | Students, courses, enrolments, grades, payments | The irreplaceable data, migrated with reconciliation |
| 4. Remaining workflows | Content, dashboards, notifications | Lower risk; move once the core is stable |
| 5. Decommission | Turn off the no-code app after a parallel period | Keep a rollback until the new system has run a full cycle |
How do I keep both running during the move?
Run the no-code front end against the new backend for the migrated workflows, and keep writing to a single source of truth, so data never diverges. Pick a cutover window with no live exam, migrate a slice, reconcile, and only then move the next. Keep the old system readable until the new one has survived a full enrolment and exam cycle, so rollback is always possible. The general first-week approach to inheriting and stabilising a codebase is in taking over a codebase.
How long does a no-code EdTech migration take?
It depends on how much the no-code app stores and how tangled the workflows are, but a typical slice-by-slice migration of a focused learning app runs over several weeks, not days. The audit and data model come first; the import is written and dry-run many times against staging before any real cutover. The main risk of doing it yourself is a one-off import script that silently drops or duplicates records, with no reconciliation to catch it. Always migrate with counts and checksums, and keep the old data until you are certain.
How RAITHub would fix this
Scope:
- An audit of the no-code app: a full export, a map of every workflow, and the three-year cost comparison.
- A proper relational data model for students, courses, enrolments, attempts, grades and payments.
- An idempotent, dry-runnable import with count and checksum reconciliation, rehearsed against staging.
- The riskiest workflow rebuilt first, then a slice-by-slice cutover with a parallel-run period and a rollback plan.
- Automated tests over the migration and the new workflows.
Timeline: a diagnose-and-plan pass fits a 2–4 week code rescue engagement; the full migration is scoped from there. You receive: automated tests and CI, handover docs and a migration runbook, and full IP under an NDA signed before detailed discussion. Student data stays in your own cloud account; development uses synthetic data. Next step: a free 15-minute technical audit, then a written fixed quote; RAITHub publishes no rates. See what RAITHub builds for education on the EdTech industry page, and book the free audit with your current platform, a recent export and your next exam or enrolment date.
Frequently asked questions
When should an EdTech app move off no-code?
When the platform is blocking the business: it slows under real cohorts, a needed feature is impossible, costs outrun revenue, or you cannot pass a school's data or security review. Move the blocking part first. If the only symptom is speed, diagnose a fixable bottleneck before committing to a migration.
Will I lose student data migrating off Bubble?
Not if you do it carefully. Export everything, rebuild the real relationships as related tables, write an import you can run twice without duplicates, and reconcile counts and checksums after each run. Keep the old system readable until the new one has survived a full cycle, so rollback is always possible.
Should I rewrite everything at once?
No. A big-bang rewrite runs blind for months while the two versions drift. The safer path is slice by slice: rebuild one workflow at a time behind the existing front end, run old and new in parallel, and cut over gradually with a rollback plan.
What do I migrate first?
The riskiest or most limiting workflow, to prove the new stack on the hardest part, then the irreplaceable core records (students, enrolments, grades, payments) with reconciliation, then the lower-risk workflows. Decommission the no-code app only after a parallel period.
How long does it take to migrate an EdTech app off no-code?
Usually several weeks, not days, depending on how much the no-code app stores and how tangled the workflows are. The audit and data model come first, and the import is dry-run many times against staging before any real cutover.
Can we keep running while we migrate?
Yes. Point the existing front end at the new backend for migrated workflows, keep one source of truth so data never diverges, and cut over a slice at a time in windows with no live exam. The old system stays available for rollback until the new one is proven.
Related posts
Ready to discuss your project?
Book a free 15-minute technical audit with our engineering team.