10+ AI SaaS templates for web & mobile
home

How to build multi-tenant SaaS - complete guide

·11 min read

How to build multi-tenant SaaS: pick shared DB + org_id, wire auth, RBAC, billing, and isolation. Decision table, steps, and when a starter wins.

If you are figuring out how to build multi-tenant SaaS, you are choosing how one app serves many customers without mixing their data, billing, or permissions. Get this wrong early and you rewrite the core of the product. Get it right and teams, invites, and plan gates become boring infrastructure.

Short answer

Default to a shared PostgreSQL database and shared schema with an organizationId (tenant key) on every business table. Resolve the active organization from the session or URL slug, enforce membership + RBAC on every API route, and bill against the organization as referenceId. Escalate to schema-per-tenant or database-per-tenant only when a contract or regulator demands physical isolation. For most B2B SaaS, a kit like TurboStarter already ships orgs, invites, roles, and billing scoped to the tenant so you build product features instead of tenancy plumbing.

Key takeaways

  1. Multi-tenancy means one app instance, many customers, with hard data and permission boundaries.
  2. Shared DB + organizationId is the right default for indie and early B2B SaaS in 2026.
  3. Isolation must live in auth context + query filters + role checks, not only UI hiding.
  4. Wire invites, active org, and org-scoped billing before you scale marketing.
  5. Skip RLS-as-primary if your stack is Hono/Drizzle with app-level guards (common Next.js pattern); add RLS later if auditors demand it.

What is multi-tenant SaaS?

Multi-tenant SaaS is software where a single deployed application serves multiple customers (tenants). Each tenant sees only its own data and settings, while you operate one codebase, one deploy pipeline, and usually one database.

Quotable definition: A tenant is the isolation boundary (usually an organization or workspace). Users are people; they can belong to many tenants through memberships with roles.

This is different from:

ModelWhat it meansWhen you see it
Single-tenant appOne customer per deploy or databaseRegulated enterprise, white-label deals
Multi-user B2CUsers share the product, not company workspacesConsumer apps without teams
Multi-tenant B2BOrgs own data; members collaborate inside the orgMost SaaS dashboards

If you sell to companies, you almost always need multi-tenancy even if day one is "just me and a co-founder."

Choose the tenancy model (before you write features)

ModelIsolationOps costMigrationsBest for
Shared DB + shared schemaLogical (organizationId + auth)LowestOne schemaDefault for most SaaS
Schema-per-tenantSchema boundaryMediumPer tenantRare; strong isolation without new DBs
Database-per-tenantFull physicalHighestPer tenantCompliance, residency, noisy-neighbor enterprise

Decision rule: start shared. Design your app so the tenant key is always explicit. Later you can route a VIP customer to a dedicated database behind the same API without rewriting product code.

Industry guides from Shopify and Postgres-focused architecture writeups converge on the same default: shared schema first, silo only when required.

TurboStarter follows shared schema: one Postgres database, organizationId as the tenant separator, users joined via memberships.

Prerequisites

You need:

  • A web app (typically Next.js App Router)
  • PostgreSQL
  • Session-based auth you control (or a self-hosted auth library such as Better Auth)
  • An API layer that can run middleware (Hono, tRPC, or route handlers)
  • Clear product language: Organization / Team / Workspace (pick one for UI; keep one internal name)

Optional but recommended:

  • Email provider for invites
  • Billing provider that supports a customer reference per org (Stripe, Lemon Squeezy, Polar, etc.)
  • Wildcard DNS if you want acme.yourapp.com routing (subdomain recipe)

Build multi-tenant SaaS in seven steps

Create three core entities:

  1. Organization - tenant with name, unique slug, optional logo/metadata
  2. Member - (userId, organizationId, role) join row
  3. Invitation - email + intended role + expiry + status

Rules that prevent pain later:

  • Unique (organizationId, userId) on memberships
  • Globally unique slug for URLs and subdomains
  • Cascade delete memberships/invites when an org is removed
  • Keep global user roles (e.g. super-admin) separate from org roles (owner/admin/member)

This matches the Better Auth organization plugin and TurboStarter's data model.

Every project, document, API key, file metadata row, and usage event that belongs to a customer gets:

organizationId: text("organization_id")
  .notNull()
  .references(() => organization.id, { onDelete: "cascade" }),

Then:

  • Lead composite indexes with organizationId
  • Never accept organizationId from the client as the sole authority; derive it from the verified session or a slug lookup you already authorized
  • Pass organizationId into background jobs explicitly (do not rely on ambient request state)

Missing this column on one table is how cross-tenant leaks start.

Pick a primary signal and stick to it:

SignalExampleProsCons
Path slug/dashboard/acme/...Explicit, cache-friendlyLonger URLs
Session activeOrganizationIdUser switches workspace in UISimple for SPA-style appsEasy to desync from URL
Subdomainacme.yourapp.comFeels white-labelDNS + cookie complexity

Best practice for dashboards: URL slug is source of truth, then sync session activeOrganizationId so APIs and clients agree. TurboStarter documents this as active organization and optional subdomain multi-tenancy.

Pseudocode for a layout:

const slug = (await params).organization;
const organization = await getOrganization({ slug });
if (!organization) notFound();

const membership = await getMembership({
  organizationId: organization.id,
  userId: session.user.id,
});
if (!membership) redirect("/unauthorized");

UI checks are for UX. Server checks are for security.

Two questions on every mutation:

  1. Can this user access this tenant? (membership exists)
  2. Can they perform this action? (role/permission)

Example permission check shape (Better Auth style):

await auth.api.hasPermission({
  headers: await headers(),
  body: {
    permissions: {
      invitation: ["create"],
    },
  },
});

Default role set that works for most B2B products:

RoleTypical powers
ownerBilling, delete org, transfer ownership
adminInvite/remove members, manage settings
memberCreate/edit product resources inside the org

Document the difference between org admin and platform super-admin so you never confuse them. See RBAC docs and security overview.

On shared schema, every read/write must filter by the resolved organizationId.

const projects = await db.query.project.findMany({
  where: eq(project.organizationId, organization.id),
});

Defense in depth options:

  • Application middleware that injects tenant context and rejects missing membership (TurboStarter's default for Hono + Drizzle)
  • Postgres Row Level Security (RLS) as a safety net when auditors demand database-enforced isolation

Both are valid. Do not mix half-implemented RLS with half-implemented app filters. Pick a primary enforcement path and test it. Many architecture posts push RLS-first; that is excellent on Supabase-style stacks. On an owned Postgres + Hono API, consistent server authorization is the non-negotiable, and RLS is optional hardening.

B2B pricing almost always needs a tenant-owned customer:

  • Checkout referenceId = organization id
  • Webhooks update entitlements for that org
  • Seat sync when members join/leave (per-seat billing)
  • Feature gates check the org's plan, not the individual user's personal sub

If you only bill users, you will rebuild org billing the week your first team plan sells.

Also decide early:

PatternUse when
Flat team planSimple pricing, unlimited seats or soft caps
Per-seatValue scales with headcount
Metered / creditsUsage-heavy AI or API products

Before launch:

  1. Invite email + accept flow that creates membership idempotently (invitations)
  2. Prevent removing the last owner
  3. Operator path: find a user, see their orgs, impersonate carefully (admin)
  4. Automated tests that assert user A cannot read org B's rows by ID guessing
  5. Cache keys and storage prefixes include organizationId or slug

Verification checklist:

  • Create two orgs, two users, swap sessions, confirm empty cross-tenant lists
  • Hit APIs with another org's UUID in the body; expect 403/404
  • Accept an invite twice; still one membership
  • Cancel a subscription for org A; org B features untouched

Architecture cheat sheet

LayerResponsibility
AuthIdentity, sessions, org plugin, active org id
RoutingSlug or subdomain → organization
API middlewareAuth + membership + permission
Data accessAlways filter by organizationId
BillingOrg as customer reference; seats/entitlements
Jobs / webhooksCarry tenant id; re-check membership for user-triggered jobs
ClientsWeb / mobile / extension share the same org contracts

For a full production stack around this model, see best SaaS stack 2026. For whether to DIY the scaffolding, see boilerplate vs building from scratch.

Common mistakes

  1. Trusting the client organizationId: always resolve tenant from session/slug, then authorize.
  2. Filtering in the UI only: hidden buttons do not stop crafted API calls.
  3. Forgetting background jobs: cron and queues need an explicit tenant id and the same guards.
  4. Billing users while selling teams: you will rewrite checkout within months.
  5. Schema-per-tenant too early: ops cost explodes before product-market fit.
  6. Colliding global admin and org admin: platform operators and workspace admins are different roles.
  7. Caches without tenant keys: stale CDN or React Query keys can leak across orgs.
  8. Allowing reserved slugs (www, api, admin) when using subdomains.

When to use a starter instead of DIY

Build tenancy yourself when isolation is the product (compliance platform, multi-DB broker) or auditors require you to author every control.

Use a production starter when:

  • You need paying B2B users in weeks, not months
  • Orgs, invites, RBAC, and org billing are table stakes
  • You want the same tenant model on web and later mobile/extension

TurboStarter ships organizations powered by Better Auth, path-based active org, invitations, RBAC, org-aware billing (including per-seat), admin tooling, and a documented path to subdomain tenancy. You still design your domain tables and product permissions; you skip rebuilding membership graphs and invite emails.

Frequently asked questions

Sounds good?Now let's make it real. In minutes.
Try TurboStarter

Final verdict: how to build multi-tenant SaaS

Ship multi-tenant SaaS by treating the organization as the tenant, putting organizationId on every business row, resolving the active org from the URL or session, and enforcing membership + RBAC + billing on the server. Keep a shared database until a real customer pays for stronger isolation.

If you want that foundation already wired in a TypeScript monorepo (web, optional mobile and extension), start from TurboStarter organizations, skim the SaaS stack guide, and spend your first sprint on the workflow only your product can own.

world map
Community

Connect with like-minded people

Join our community to get feedback, support, and grow together with 600+ builders on board, let's ship it!

Join us

Ship your startup everywhere. In minutes.

Skip the complex setups and start building features on day one.

Get TurboStarter