Single-tenant to multi-tenant: a migration playbook
How do you migrate a SaaS product from single tenant to multi tenant?
Introduce a tenant key, isolate data by row or schema, centralise config and billing, migrate customers in waves behind a façade. It succeeds when the tenant key is enforced by the database rather than remembered by developers, and when each wave has a rollback path that keeps the old instance intact until sign-off.
A single tenant to multi tenant migration is the point at which a SaaS company stops running one copy of its product per customer and starts running one product for all of them. It happens because the per-customer model stops scaling: every deployment is slightly different, every upgrade is a project, and the infrastructure bill grows with the customer list rather than with usage. The playbook below is the one we use in SaaS development engagements: add a tenant identity, choose an isolation model, pull configuration and billing into one place, then move customers in waves behind a façade so nobody outside notices the change.
Why single-tenant products end up here
Single-tenant deployments are the natural result of early enterprise sales. The first big customer wanted their own database; the second wanted a custom field; the third wanted a different SSO. Each was reasonable, and together they produced twenty environments with drifted schemas and hand-edited config that the support team tracks in a spreadsheet.
The trigger for migration is rarely cost alone. It is usually a release that has to be applied twenty times, a security fix that was missed on three instances, or a new feature that depends on data across customers, such as benchmarking or a shared AI model. Whatever the trigger, the answer is the same architecture described in multi-tenant SaaS architecture: the decisions that avoid a rewrite, reached by migration rather than from a blank page.
Choosing the isolation model
| Model | How it works | Best for | Watch out for |
|---|---|---|---|
| Row-level (shared schema) | Every table carries a tenant_id; policies enforce it | Most B2B SaaS with many small and mid-size tenants | One missed filter leaks data; enforce in the database |
| Schema-per-tenant | One database, one schema per tenant, shared code | Tenants that need separate backups or migration timing | Migrations run N times; connection pooling needs care |
| Database-per-tenant | Separate databases behind a routing layer | Regulated customers, data-residency requirements | Closest to what you have now; least operational saving |
| Hybrid | Row-level by default, database-per-tenant for a few | Products with a long tail plus a few large regulated accounts | Two code paths to test; keep the routing in one place |
Most migrations land on row-level isolation with a hybrid option held in reserve for the one or two customers who will insist on their own database. The PostgreSQL row security policies documentation is the primary reference for making the tenant filter a property of the database rather than a habit of the developers.
Step one: introduce the tenant key
Before any customer moves, the codebase must be tenant-aware. Add a tenant identifier to every table that holds customer data, make it non-nullable, and add it to every unique constraint and index that matters. Then thread the tenant context through the application: resolved from the authenticated session, set once per request, never passed as a parameter that a developer could forget.
This step is done on the single-tenant codebase first and deployed to every instance with a constant tenant value. It is dull, it touches almost every file, and it is the foundation for everything after. Ship it as small releases with the test suite asserting that no query path can run without a tenant context.
Cross-tenant data is a design decision, not a leak
Some tables are legitimately shared: reference data, feature flags, plan definitions. Mark them explicitly; everything else gets the tenant key. Cross-tenant aggregation, when a feature needs it, runs as a separate service with audited access, never as a query that happens to omit the filter.
Step two: centralise configuration and identity
Single-tenant products hide configuration in environment variables, deployment scripts and per-instance settings tables. The migration collects all of it into a tenant configuration record: branding, feature entitlements, integrations, SSO settings, data retention, locale. Everything that used to differ between deployments becomes a row.
Identity follows the same pattern. Each tenant may bring its own identity provider, so the platform needs a single login surface that resolves the tenant from the email domain or a chosen slug and then hands off to that tenant's provider. Session tokens carry the tenant claim. Role definitions move from per-instance tables into a shared model with tenant-scoped assignments. The enterprise-ready SaaS post on SSO, RBAC and audit logs covers the shape in detail.
Step three: centralise billing and metering
Per-customer instances usually mean per-customer invoices raised by hand. A multi-tenant platform records plans, entitlements and usage against the tenant, so billing becomes a report rather than a monthly chore. It is also the moment to add metering if AI or API features are on the roadmap; the mechanics are in usage metering and AI billing for SaaS. Existing contracts load as custom plans so no customer sees a pricing change on migration day.
Step four: build the façade and migrate in waves
The façade
Each customer currently has a hostname, an API base URL and possibly IP allow-lists pointing at their instance. The façade is a routing layer placed in front of both old and new systems that resolves the tenant from the hostname or token and forwards to whichever backend that tenant currently lives on. Customers keep their URLs. Integrations keep working. The switch for a tenant becomes a routing change, which is also the rollback.
Data migration per tenant
Write one script that takes a single-tenant database, stamps every row with the tenant identifier, transforms any schema drift back to the canonical shape, and loads it into the shared store. Run it against a copy of every instance during the build, producing a reconciliation report each time: row counts, constraint violations, drifted columns. By the time real waves begin, the script has met every instance's quirks.
Waves
Move internal and sandbox tenants first, then small customers, then the largest. For each wave: freeze writes on the old instance briefly, run the migration, verify the reconciliation report, flip routing, watch for a defined period, then set the old instance to read-only and keep it for an agreed period so rollback is a routing change rather than a restore. Announce each wave as a maintenance window with an exact duration, and finish inside it.
Testing that isolation actually holds
Isolation is tested, not assumed. The suite should include a tenant-crossing test for every endpoint: authenticate as tenant A, request a resource belonging to tenant B by identifier, expect not-found. Add a job that samples production queries and alerts if any touched rows across more than one tenant. Run a penetration test before the first external wave, because the customers you are consolidating will ask, and because the failure mode of a leak is public.
A worked example
A compliance-workflow SaaS had grown to a few dozen enterprise customers, each on its own deployment. Releases had become quarterly because applying them everywhere took a week, and a security patch had once been missed on two instances. The company wanted to add a shared AI assistant that would learn from anonymised patterns across customers, which the per-instance model made impossible.
The migration ran over a quarter. The tenant key was introduced across the codebase in the first month and shipped to every instance as an ordinary release. Configuration and SSO were centralised next, with each customer's settings loaded as a tenant record. The migration script was run against copies of every instance until the reconciliation report was clean for all of them. Customers moved in five waves behind a façade, and two regulated accounts kept dedicated databases under the hybrid model. Releases became weekly. The AI assistant followed as a separate build, using the retrieval approach described in the in-app copilot case study.
Team and timeline
The migration is typically a backend lead, two backend engineers, a DevOps engineer for the façade and cutovers, and a QA engineer focused on isolation tests. Your side owns customer communication and the wave schedule, and one engineer who knows the historic quirks of each instance.
Most products fit an eight to sixteen week ReCore engagement at $31,500–105,000+ (from ₹22,40,000), with the range driven by the number of instances and how much schema drift the script has to absorb. Where the product also needs new surfaces, the work sits under SaaS development from $31,500 (₹20.8L). A Sprint Zero at $3,250 (₹2,00,000) produces the instance inventory, isolation decision and wave plan in ten working days, and is credited to the build. Ongoing operation of the consolidated platform is covered by the Care Plans on the pricing page.
Before you start: a checklist
- Inventory every instance: version, schema drift, custom config, integrations and IP allow-lists
- Decide the isolation model and which customers, if any, need the hybrid path
- Classify every table as tenant-scoped or shared, in writing
- Load existing contracts as custom plans so migration changes no invoice
- Write the migration script early and run it weekly against copies of every instance
- Build the tenant-crossing test for every endpoint before the first external wave
- Agree a wave schedule and a retention period for old instances with each customer
- Book a penetration test to complete before the first customer moves
Glossary
- Tenant: one customer organisation and everything it owns inside the platform
- Tenant key: the identifier stamped on every tenant-scoped row and carried in every session
- Row-level security: database policies that filter rows by tenant regardless of the query
- Façade: a routing layer that resolves the tenant and forwards to the old or new backend
- Schema drift: differences between instances caused by hand-applied changes over time
- Reconciliation report: the migration script's output comparing source and target counts and constraints
Related reading
Continue with multi-tenant LLM architecture for SaaS if AI features are the reason for consolidating, application modernization vs rewrite for the broader trade-off, and the SaaS industry page for what we build for SaaS companies.
Stamp the key, enforce it in the database, route through a façade, and move one wave at a time; the migration is dull by design, which is what makes it safe.
Frequently asked questions
Can some customers stay on their own database after the migration?
▾
Yes. A hybrid model keeps a few regulated or contractually demanding tenants on dedicated databases behind the same routing layer and codebase, while the rest share a row-level isolated store. Keep the routing logic in one place.
How do we avoid a data leak between tenants?
▾
Enforce the tenant filter in the database with row-level policies, thread the tenant context from the session rather than parameters, add a tenant-crossing test for every endpoint, and monitor production queries for cross-tenant access.
Will customers notice the migration?
▾
They should see only a short announced maintenance window per wave. URLs, integrations and invoices stay the same because a façade preserves hostnames and existing contracts are loaded as custom plans.