Multi-tenant software, built with billing, roles and the parts nobody demos.
A SaaS product is not a web app with a login screen. It is a web app where tenant A can never see tenant B's data, where a downgraded plan actually loses access, and where the invoice is right every month without a human involved.
What this is
The first decision is how tenants are isolated, and it is expensive to change later. A shared database with a tenant_id column on every table is the default, cheap to run and fine for most products, but it means every single query has to filter by tenant, and one query that forgets is a data leak between paying customers found in production rather than in a demo. Schema-per-tenant or database-per-tenant cost more to operate but make that class of bug structurally harder to write. We pick based on the sensitivity of the data and the scale you are actually building for, and we write the decision down, because a support ticket six months in is the wrong time to discover it was never made deliberately.
Billing is not a Stripe button, it is a state machine: a trial that has to actually expire, a downgrade that has to actually remove access to the feature it was gating, a card that fails and needs a dunning sequence before the account gets suspended, usage-based metering that has to reconcile against what the product actually recorded. We centralize the plan-limit logic in one place rather than letting `if (plan === 'pro')` spread through the codebase, because that is what turns a plan change into a week of grepping instead of an edit in one file.
The rest of the surface is what a support team lives in: inviting a teammate, removing one, transferring ownership when a founder leaves, an audit log that answers who changed a setting, and admin impersonation so support can see what a customer sees without asking for their password. None of it is the product's core feature and all of it is where most support tickets come from when it is missing.
What you get
Multi-tenant data model, isolation decided and enforced
At the query layer, argued against your data sensitivity and scale, not defaulted and not hoped for in application code.
Roles and permissions as a first-class model
Owner, admin, member, and whatever the product actually needs, not a single boolean column standing in for access control.
Billing integration
Subscriptions, usage metering where relevant, upgrades, downgrades that actually revoke access, and dunning for a card that fails.
Team and account management
Invite, remove, transfer ownership: the flows a support ticket gets written about when they are missing.
Admin and support tooling
Impersonation and an audit log, so support can see a tenant's account and answer who changed what without a screen-share.
An onboarding flow that gets a new tenant working
Without a phone call, because that call does not scale past your first fifty customers.
Background job infrastructure
For exports, emails and webhooks out to the customer, anything that cannot happen inline in a request.
Source, infrastructure as code and the deploy pipeline
Handed over, reproducible without us.
When this fits, and when it does not
A good fit
- The product will be sold to more than one paying customer, and those customers must never see each other's data.
- You need billing that handles upgrades, downgrades and failed payments without a spreadsheet reconciling it monthly.
- You are past the prototype: an earlier version proved people want it, and now it has to hold up under real accounts and real money.
- Enterprise customers are asking for SSO, roles or an audit log, and "we will add that later" has run out of runway.
- You want one team building the product, the billing and the tenant boundary together, so isolation is not welded on afterward by whoever is left to do it.
Not a good fit
- There is no external customer. This is a tool your own team uses, with no billing and no tenant boundary to defend. That is internal tools, and it is a smaller build, we will say so and point you there.
- What you have is one working system that needs to talk to another one, not a new product. That is API integration.
- You want a prototype to show ten people at a conference next month. Build that fast and cheap first, and not with us. Come back once real accounts have to hold up.
- You need a mobile app on top of a backend that already exists, and nothing else. That is mobile app development on its own.
How it runs
- 01
Model tenants, roles and the isolation boundary
Before writing product features against it, because retrofitting isolation is the expensive version of this decision.
- 02
Build the core product
With billing plumbed in from the first plan, not dropped in at the end once the product is already live.
- 03
Wire the account layer
Invite, remove, impersonate, audit: the parts a support team will actually use every week.
- 04
Load and isolation testing
One tenant's usage must not slow down or leak into another's, verified, not assumed.
- 05
Hand over
Source, infrastructure and the runbook for a plan change, a refund and a data export request.
Questions we get
Which billing provider do you use?
Stripe by default, because most of the plumbing you need already exists and does not need reinventing. We build against a different provider when a specific requirement, usually tax handling in a particular region or an existing contract, makes it the better call.
How do you actually keep tenant data separate?
Decided at the start against your data's sensitivity: a shared database with tenant filtering enforced at the query layer for most products, schema or database separation where the isolation has to be structural rather than a discipline every query has to remember. Either way it gets tested, not just written.
Do you build the mobile app for this too?
Yes, when the product needs one. It is built against the same API the web product uses, scoped together with the SaaS build rather than as a separate contract with its own contract to the backend. See mobile app development for that side of it.
We already have half of this built. Do you rewrite it?
No, not by default. We read what exists, keep what holds, and extend it. A rewrite is what happens when the isolation model itself cannot support what you are adding, and we will say plainly when that is the actual situation rather than defaulting to it.
Building a product more than one company will pay for?
Tell us who the tenants are and what a downgrade should actually remove. An engineer reads it and tells you which part of it is the actual product and which part is scaffolding.