All systems operational200+ sites we watch overReply in under 2h+54 9 11 7820-8554
← Blog

Multi-tenant SaaS with Next.js and PostgreSQL: who can build it

For founders and executives choosing who builds a multi-tenant SaaS: the tenancy model, auth, billing and operations a team must handle, and the questions that expose real experience.

werun.dev19 min read
Get a summary withChatGPTClaudeGoogle AI Mode
Building of identical rooms with separate keys, illustrating a multi-tenant SaaS on Next.js and PostgreSQL

You have a SaaS idea, or a product that has outgrown its first version, and every customer needs their own workspace, their own users and their own data. You are looking for a team that can build a multi-tenant SaaS on Next.js and PostgreSQL, and most proposals look alike on paper. The difference shows later: the first data leak, the first migration that stalls, the first customer who slows everyone else down.

This guide tells you what that team must be able to do: choose a tenancy model, route and authenticate each customer, tie billing to plan limits, and run migrations and backups across every tenant. For each part you get what it means for your business, what good looks like and one question that separates experience from a pitch. For the wider question of picking an agency, our guide to choosing a Next.js development agency for SaaS is the place to start.

The short answer: a good team recommends shared tables with row level security for most MVPs, enforces the tenant in the database, designs plan limits in week one and can show you how a single tenant is restored. The sections below show how to check each claim.

Key takeaways

  • Multi-tenancy is the hardest part of a SaaS build to judge from a demo, so test it with specific questions before you sign.
  • Shared tables with PostgreSQL row level security fit most B2B MVPs, and a tenant_id on every table keeps later moves open.
  • Enforce the tenant in the database, so a forgotten filter in one query cannot show a customer another customer's data.
  • Design plan limits, billing webhooks and per-tenant operations in the first weeks, because they are costly to retrofit.
  • Eight questions in this guide expose real experience, from how a migration reaches every tenant to how one customer is restored.

Who builds multi-tenant SaaS and what to expect

Four kinds of teams build multi-tenant SaaS: your own engineers, a product studio, a generalist web agency and a specialist SaaS team. Multi-tenancy is the part that separates them, because a demo hides it and a launch exposes it. Knowing what to look for lets you shortlist with confidence.

What multi-tenant means for your business

Multi-tenant means one running product serves many customer organisations, called tenants, and each sees only its own data. You ship one release, fix a bug once and pay for one platform, while every customer feels they have a private product. Your cost per customer falls as you grow. The risk is that one mistake can show a customer's data to another.

A tenant is usually a company, not a person. Inside it sit members with roles such as owner, admin and viewer. Pricing, support and compliance promises all hang on that structure, so it is a product decision before it is a technical one.

Building facade with many identical doors, each opened by its own key, picturing tenants sharing one product
One product, many doors, one key per customer

Three kinds of teams you will meet

Each type has a real strength, and each has a question worth asking:

  • In-house engineers know your domain best. Ask who has run a multi-tenant product in production, because hiring that experience can take months.
  • Product studios and generalist agencies ship interfaces and first releases quickly. Ask for evidence on tenancy, billing and operations, beyond design and speed.
  • Specialist SaaS teams have met isolation, metering and migrations before. Ask what they would do differently on your product, and why.

A small senior team that has run a multi-tenant product often beats a larger team that learns on your budget. Size matters less than who has seen these problems before.

Choose a multi-tenant SaaS tenancy model

Your tenancy model sets your cost per customer, how strong isolation is and how hard each release becomes. Three models exist. A good team recommends one for your stage and tells you what would make you change it.

Shared schema with row level security

All tenants share the same tables, and every row carries a tenant id. PostgreSQL filters rows by it through row level security (RLS), a built-in feature that attaches a rule to a table. Because the rule lives in the database, it applies to every query the app runs.

-- A tenant-scoped table
create table projects (
  id uuid primary key default gen_random_uuid(),
  tenant_id uuid not null,
  name text not null,
  created_at timestamptz not null default now()
);

-- Every query filters on tenant_id, so index it first
create index projects_tenant_idx on projects (tenant_id, created_at);

-- Turn on RLS and apply it to the table owner too
alter table projects enable row level security;
alter table projects force row level security;

-- Rows are visible and writable only for the tenant set on the transaction
create policy tenant_isolation on projects
  using (tenant_id = current_setting('app.tenant_id', true)::uuid)
  with check (tenant_id = current_setting('app.tenant_id', true)::uuid);

-- The app connects as an ordinary role, never a superuser
create role app_user login;
grant select, insert, update, delete on projects to app_user;

The policy compares each row's tenant to a setting the app provides for the current transaction. Two details separate real experience from a tutorial. Table owners bypass policies by default and superusers always do, so the table needs force row level security and the app must connect as an ordinary role. The PostgreSQL documentation on row security policies describes both. When no tenant is set, the policy matches nothing, so a missing setting returns no rows instead of everyone's rows.

Schema per tenant

Each tenant gets its own schema, a separate set of tables inside the same database. Isolation is easier to explain to an auditor, and moving or restoring one tenant is simpler. The price is migrations: every change runs once per tenant, which needs tooling, ordering and retries once you pass a few hundred customers.

Database per tenant

Each tenant gets its own database, sometimes on its own server. This is the strongest isolation, and some enterprise or regulated customers require it, along with a choice of region. It also costs the most per tenant, because provisioning, monitoring and migrations multiply with every customer.

How to pick: a quick comparison

Our pick for an MVP

Shared schema with RLS

  • Isolation enforced by database rules
  • Lowest cost per tenant
  • One migration serves every tenant
  • Fits MVPs and most B2B products

Option

Schema per tenant

  • Separate tables per tenant
  • Moderate cost per tenant
  • Migrations run once per tenant
  • Fits clearer audit boundaries and per-tenant restores

Option

Database per tenant

  • Strongest isolation
  • Highest cost per tenant
  • Migrations across many databases
  • Fits regulated or enterprise customers

Most B2B MVPs should start shared and keep the door open. A tenant id on every table from day one turns a later move of one large customer to its own database into a copy job, not a rewrite. Ask any team what would make them move you off the shared model. A good answer names a customer requirement, such as residency or contractual isolation, and never a matter of taste.

Multi-tenant architecture in Next.js: routing and auth

Your customer opens acme.yourapp.com and lands in their own workspace. Before the product reads a single row, it must know which tenant this is, who the member is and what they may do. Next.js resolves the first, your auth layer the second, and PostgreSQL stays as the safety net.

Tenant request path

  1. 01Open subdomainCustomer visits acme.yourapp.com
  2. 02Resolve tenantNext.js reads the host
  3. 03Check member and roleAuth layer confirms access
  4. 04Row level securityPostgreSQL returns only that tenant's rows

A request moves from a customer's subdomain through tenant lookup and a member check to a database that returns only that tenant's rows

How a request reaches only one tenant's data

Subdomains and custom domains

Subdomains give each customer a branded address with one wildcard DNS record and one deployment. A small function reads the host header and returns the tenant slug. Next.js runs it in the request interception file, called middleware in earlier versions and proxy in recent ones, so check the Next.js documentation for the name your version uses.

// lib/tenant.ts
const ROOT_DOMAIN = process.env.ROOT_DOMAIN ?? 'yourapp.com';

// Returns the tenant slug for acme.yourapp.com, or null for other hosts
export function tenantFromHost(hostHeader: string | null): string | null {
  if (!hostHeader) return null;
  const host = hostHeader.split(':')[0].toLowerCase();
  const suffix = `.${ROOT_DOMAIN}`;
  if (!host.endsWith(suffix)) return null; // root domain or custom domain
  const slug = host.slice(0, -suffix.length);
  return /^[a-z0-9-]+$/.test(slug) && slug !== 'www' ? slug : null;
}
// proxy.ts (named middleware.ts in earlier Next.js versions)
import { NextRequest, NextResponse } from 'next/server';
import { tenantFromHost } from './lib/tenant';

// In earlier versions, export this same function as middleware
export function proxy(request: NextRequest) {
  const headers = new Headers(request.headers);
  headers.delete('x-tenant-slug'); // never trust a value sent by the browser
  const slug = tenantFromHost(request.headers.get('host'));
  if (slug) headers.set('x-tenant-slug', slug);
  return NextResponse.next({ request: { headers } });
}

The function removes any incoming tenant header before it sets its own, so a visitor cannot pose as another tenant. Custom domains such as app.customer.com follow the same path with one addition: a table that maps each verified domain to a tenant. Experienced teams also plan domain ownership checks and automatic certificates, because customers will ask.

Organisations, members and roles

People rarely belong to one organisation only. A consultant may be an admin at one customer and a viewer at another, so users, organisations and memberships are three separate things. The membership holds the role, and every action checks it on the server, never only in the interface.

Invitations need the same care: a signed link, an expiry, a role chosen by the inviter and a way to revoke. Good teams name permissions after what people do, such as inviting members or exporting data, and map roles to them. That keeps the product ready when a customer asks for a custom role or single sign-on.

Passing the tenant to the database safely

Once the app knows the tenant, it hands that fact to PostgreSQL inside a transaction. The helper below opens a transaction, sets the tenant for that transaction only and runs your queries. Because the setting is local to the transaction, it is safe with connection pooling, where many requests share few connections.

// lib/db.ts
import { Pool, PoolClient } from 'pg';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });

export async function withTenant<T>(
  tenantId: string,
  work: (client: PoolClient) => Promise<T>
): Promise<T> {
  const client = await pool.connect();
  try {
    await client.query('begin');
    // The third argument true keeps the setting local to this transaction
    await client.query("select set_config('app.tenant_id', $1, true)", [tenantId]);
    const result = await work(client);
    await client.query('commit');
    return result;
  } catch (error) {
    await client.query('rollback');
    throw error;
  } finally {
    client.release();
  }
}

// Usage: only this tenant's projects come back
// const rows = await withTenant(tenantId, (db) =>
//   db.query('select id, name from projects order by created_at desc'));

Every database call in the product goes through this helper. A reviewer can check that in minutes, and a test can prove it.

The one idea

The tenant must be enforced by the database. When PostgreSQL applies the rule, a forgotten filter in one query returns nothing instead of another customer's data. Ask any team to show the policy and a test that fails without it.

Billing, metering and plan limits

Billing is where tenancy meets revenue. Each tenant has a plan, and the product must allow, warn or block actions according to it. Good looks like one source of truth for what each tenant may do, updated automatically when payments change.

Usage to plan limit

  1. 01Usage event recordedEach tenant action is logged
  2. 02Stripe meters usageStripe Billing totals it per tenant
  3. 03Webhook updates entitlementsOne source of truth for limits
  • AAllowTenant is within plan
    1. Action proceeds
  • BWarnTenant nears the limit
    1. Show upgrade notice
  • CBlockTenant is over the limit
    1. Action refusedUpgrade path offered

Usage is recorded, metered by Stripe Billing and synced by webhook to entitlements, then the app allows, warns or blocks; a failed payment moves the tenant to a grace state

From usage event to plan limit, with a grace state

Plans, seats and usage meters

Decide what you charge for before anyone writes code. Stripe Billing supports subscriptions, per-seat prices and usage-based prices, and the product must record the matching facts for each tenant.

Pricing modelWhat the product recordsWhat it enforces
Flat planPlan name per tenantFeatures switched on or off
Per seatActive members per tenantNumber of invitations allowed
Usage basedEvents such as API calls or documents processedMonthly allowance, then a warning or extra charge

Most products mix them: a plan with included seats and an allowance, then usage beyond it. Usage events need a tenant id and a unique key, so a retry never counts twice.

Enforcing limits inside the product

Keep a small entitlements record per tenant in your own database: plan, seat limit, allowance and status. Stripe webhooks update it, and a single function answers every question, such as whether this tenant can create another project. Calling Stripe on each page view makes the product slower and ties it to an outside service.

Warn when a tenant reaches most of its allowance, and block only at the limit with a clear message and an upgrade button. Webhook handlers must verify the signature and store event ids, because the same event can arrive more than once.

Handling failed payments and upgrades

A failed card should not lock out a customer on day one. Stripe retries payments on a schedule you configure and sends events such as invoice.payment_failed and customer.subscription.updated. Your product reads them and moves the tenant to a grace state: full access with a banner, then read-only, never deletion.

Upgrades should apply as soon as payment succeeds. Downgrades need a rule for tenants above the new limits, for example keeping existing data and blocking new items. Agree these rules in writing with your team, since support will apply them daily.

Migrations, backups and noisy neighbours

These are day-two problems, and they decide whether the product survives its own growth. A team with experience talks about them before launch.

Schema changes without downtime

Every release can change the database, and all tenants share the result. Safe changes follow an expand and contract pattern: add the new column as optional, deploy code that writes both, fill old rows in small batches, switch reads, then remove the old column in a later release. Indexes are built with create index concurrently so tables stay writable.

With one shared schema, that is one migration. With a schema per tenant, the same change runs once per tenant, with ordering, retries and a report of which tenants are done. Ask how that report looks.

Restoring one tenant, not everyone

Sooner or later a customer deletes something and asks you to bring it back. Point-in-time recovery restores the whole database, which would overwrite everyone else's work. In a shared schema, the usual route is to restore a copy into a scratch database and copy that one tenant's rows back by tenant id.

This should be a written, tested procedure. A good team has run it on real backups and can tell you how long it takes.

Rate limits, quotas and tenant-level monitoring

A noisy neighbour is one tenant whose heavy import or report slows everyone else. Practical controls are per-tenant rate limits, background queues with a concurrency cap per tenant, query timeouts and limits on database connections.

Tag logs, errors and traces with the tenant id. Then support can answer whether a problem affects one customer or all of them, and alerts can fire for a single tenant. Without that tag, you debug blind.

Team shape and cost: MVP versus scale

Cost follows scope more than headcount. A small senior team can ship a multi-tenant MVP in weeks to a few months, and scope is what stretches it: billing rules, roles, integrations and compliance. Here is how the work changes as you grow.

  1. Phase 1: MVP

    One app, one database

    A small senior team builds one Next.js app on one PostgreSQL database with row level security, sign-in, organisations and one or two plans.

  2. Phase 2: first customers

    Billing and observability

    Webhooks, entitlements, error tracking, tenant-tagged logs, tested backups and a disciplined migration routine arrive.

  3. Phase 3: scale

    Isolation upgrades and on-call

    Per-tenant quotas, dedicated databases for the few customers who need them, an on-call routine and regular restore drills.

What an MVP team looks like

Two to four people often cover it: a senior full-stack engineer who owns the architecture, a second engineer, a designer for the key flows and a product owner on your side. Keep one deployable app and one database. Our monolith vs microservices decision framework explains why, and when splitting starts to pay off.

What changes after the first customers

Real usage brings real questions: one customer asks for an export, another for single sign-on, a third for a restore. The team adds observability, billing edge cases and a support routine. This is when a retainer or care plan matters more than the first build, because someone must watch the product and answer quickly.

What drives the budget up or down

  • Roles and permissions: a few fixed roles cost less than custom roles per tenant.
  • Billing: flat plans are simple, while usage metering with proration takes more design.
  • Integrations: each CRM, ERP or identity provider adds work and tests.
  • Compliance: audit logs, data residency and certifications add scope.
  • Tenancy model: dedicated databases add operations cost for every customer.

For example (illustrative), take two products with the same tenancy model. Product A has email sign-in, one plan and no integrations. Product B adds single sign-on, three roles, usage billing and a CRM sync. Product B is far more than A plus a few items, because each addition touches roles, billing and tests. Ask any team to price these drivers separately so you can trade them against your deadline.

Questions that expose real multi-tenant experience

Ask these on your first call. Good answers are specific, mention trade-offs and refer to something the team has run. For process, references and contract questions, the Next.js agency selection guide covers the rest.

  • Ask which tenancy model they recommend for your stage and what would make them change it
  • Ask to see the row level security policy and the test that fails without it
  • Ask how the app role is set up so policies cannot be bypassed
  • Ask how a new migration reaches every tenant and how failures are reported
  • Ask how one tenant is restored without touching the others
  • Ask where plan limits live and what happens when a payment fails
  • Ask how a heavy tenant is kept from slowing the rest
  • Ask how support finds one tenant's errors and logs

Questions about isolation and data

Listen for the database. A strong answer says the tenant is enforced by row level security or by separate schemas, with the app connecting as a limited role, and that automated tests try to read another tenant's rows and must fail. A weak answer says every query has a where clause for the tenant. That works until the day someone forgets it.

Also ask about backups per tenant, data export and deletion. Customers will ask for each of these, often in a security questionnaire.

Questions about operations and billing

A strong answer names the entitlements record, the webhook events, the grace state and idempotent handlers. It describes the migration process in steps and says who is on call when something breaks after launch. If the team has restored one tenant, ask them to walk you through it. People who have done it remember the details.

Red flags in the answers

  • Tenant filtering only in application code, with no database safeguard
  • Billing described as a later phase, with limits written into the interface
  • No automated test that proves one tenant cannot read another's data
  • A fixed price and date with no discussion of roles, billing or compliance
  • No plan for backups, restores or monitoring after launch

One red flag is a prompt to ask more. Several together are a reason to look at another team.

How werun.dev builds and runs SaaS products

You set the goal and we build the way there. For a SaaS, that goal might be the first paying customers, a smoother onboarding or fewer support hours. We build custom applications with Next.js, React and PostgreSQL and stay beside you after launch. The custom apps service and our solutions show the kind of products and outcomes we work on.

From your goal to a first release

We start with your goal and a one-page tenancy brief: who a tenant is, what plans limit and which customers need stronger isolation. Then we build one Next.js app on one PostgreSQL database, with the tenant enforced in the database, billing wired to plan limits and isolation tests running on every change. Releases stay small, so you see working software early and can change direction cheaply.

We write down what would make you move to a different tenancy model, so the choice stays yours. WelcomeAI, our own RAG chatbot SaaS, is a product we run ourselves, so we know the work of operating one as well as building one.

Staying beside you after launch

After launch you get a reply in under 2 hours on business days, a client panel for projects, requests and invoices, and a written monthly report. Client Feedback lets your team click anything on a page and ask for a change, so requests reach us with context. A Care plan or a Retainer keeps migrations, updates and monitoring in steady hands.

Once tenancy is solid, AI features per tenant become the natural next step, and data isolation matters even more for retrieval and agents. Our guide to AI integration in SaaS platforms shows what comes next.

Where to start this week

Write a one-page tenancy brief. It takes about an hour and makes every proposal you receive easier to compare. Answer four questions:

  1. Who is a tenant (a company, a team or a person), and how many members does one have?
  2. Which customers might need stronger isolation, a region or an audit trail?
  3. What do your plans limit: seats, usage or features?
  4. What should happen when a payment fails?

Send us the page together with your goal through the contact page. We reply in under 2 hours on business days and tell you which tenancy model we would start with, and why.

Frequently asked questions

What is multi-tenant architecture in SaaS?

Multi-tenant architecture means one running application serves many customer organisations, called tenants, while each tenant sees only its own data. Tenants share code and infrastructure, and the database, the application logic or both keep their data apart.

Is PostgreSQL row level security enough to isolate tenants?

It is a strong base, because PostgreSQL applies the rule to every query, but it needs setup. The table must force row level security, the app must connect as a role that is not a superuser and has no bypass right, and automated tests must try to cross tenants.

Should I use one database or one schema per tenant?

Most B2B MVPs should start with shared tables and row level security, because it has the lowest cost and the simplest migrations. Choose a schema or database per tenant when customers require stronger isolation, separate restores or data residency. A tenant id on every table makes a later move easier.

How do you do subdomain routing for tenants in Next.js?

Point a wildcard DNS record at your deployment, read the Host header in the request interception file (middleware or proxy, depending on your Next.js version) and extract the subdomain. Pass it on as a trusted header, then look up the tenant and check the signed-in member's role.

How much does it cost to build a multi-tenant SaaS MVP?

Cost depends on scope more than on the tenancy model. A small senior team can often build an MVP in weeks to a few months, and billing rules, roles, integrations and compliance are what move the budget. Ask for a price that lists those drivers separately.

How do I choose a multi-tenant SaaS development company?

Ask for evidence on isolation, billing, migrations and per-tenant restores, beyond portfolio screens. Strong teams show a row level security policy and an isolation test, explain how plan limits are enforced and describe who supports the product after launch.

Running into this on your own stack?

Tell us the platform and the problem. We'll tell you what it takes and who does it.

No commitment. Just a conversation.