How to Build·15 min read

How to Build a Multi-Tenant SaaS Admin Dashboard From Scratch

Every B2B SaaS product eventually needs an admin dashboard that works across multiple tenants without leaking data between them. Getting the architecture right from the start saves you months of painful refactoring later.

Nate Laquis

Nate Laquis

Founder & CEO

Why Multi-Tenant Admin Dashboards Are Hard to Get Right

Single-tenant admin panels are straightforward. You have one database, one set of users, one billing account. But the moment you add a second tenant, everything changes. Suddenly every query needs scoping, every API endpoint needs authorization checks, and every UI component needs to respect organizational boundaries. Get any of these wrong and you have a data leak that erodes customer trust overnight.

We have built admin dashboards for B2B SaaS companies ranging from 10 tenants to over 2,000. The pattern is always the same: teams start with a simple admin panel, bolt on multi-tenancy as an afterthought, and then spend six months untangling the mess when a customer reports seeing another customer's data. The fix is always a partial rewrite.

The good news is that the tooling has caught up. In 2031, you can build a production-grade multi-tenant admin dashboard in 6 to 10 weeks with the right stack. PostgreSQL row-level security, Next.js App Router, Clerk or WorkOS for organization-aware auth, and Stripe for per-tenant billing. This guide walks through each layer, with real costs, real code patterns, and the specific decisions that matter.

If you are still evaluating whether to use a shared database or separate databases per tenant, read our deep dive on multi-tenant SaaS architecture first. This article assumes you have made that decision and are ready to build the dashboard layer on top.

developer writing code for a multi-tenant SaaS admin dashboard on a laptop

Choosing Your Tenant Isolation Strategy

Your isolation strategy dictates everything downstream: your database schema, your ORM configuration, your deployment model, and your operational costs. There are three real options, and each has sharp tradeoffs.

Option 1: Shared Database with tenant_id Column

This is the most common approach and the right default for most teams. Every table has a tenant_id column. Every query includes a WHERE clause filtering by tenant. PostgreSQL row-level security (RLS) acts as a safety net to prevent cross-tenant data access even if your application code has a bug.

  • Pros: Cheapest to operate (one database instance), simplest to deploy, easiest to run migrations, works well up to several thousand tenants
  • Cons: Requires disciplined query scoping, noisy neighbor risk if one tenant generates disproportionate load, harder to meet data residency requirements for specific tenants
  • Cost: A single Supabase Pro instance ($25/month) or RDS db.t4g.medium ($65/month) handles 500+ tenants comfortably

Option 2: Schema-per-Tenant

Each tenant gets their own PostgreSQL schema within a shared database. Tables are identical across schemas, but queries are scoped by setting the search_path at the connection level. This provides stronger isolation than a tenant_id column without the operational overhead of separate databases.

  • Pros: Stronger isolation, easier per-tenant backups, can customize indexes per tenant
  • Cons: Migrations must run across all schemas (slow at 500+ tenants), connection pooling gets complicated, most ORMs do not support this natively
  • Cost: Same database cost, but 20% to 40% more engineering time for migration tooling

Option 3: Database-per-Tenant

Full isolation. Each tenant has their own database instance. This is the right choice when you have enterprise customers with strict compliance requirements, data residency laws, or contractual obligations for physical data separation.

  • Pros: Maximum isolation, easy per-tenant backups and restores, eliminates noisy neighbor problems, simplest compliance story
  • Cons: Expensive (each RDS instance costs $50 to $200/month), complex connection management, migrations must deploy to every instance, operational burden grows linearly with tenant count
  • Cost: At 100 tenants with RDS db.t4g.micro, you are looking at $5,000 to $7,000/month just for databases

Our recommendation: start with shared database plus RLS for your first 200 tenants. Offer database-per-tenant as a premium add-on for enterprise customers willing to pay $500+/month. This hybrid approach keeps costs low while giving you an upsell path.

Implementing Row-Level Security in PostgreSQL

Row-level security is your last line of defense against cross-tenant data leaks. Even if your application code forgets a WHERE clause, RLS ensures that a query running in tenant A's context physically cannot return tenant B's rows. Here is how to implement it properly.

Setting Up RLS Policies

First, enable RLS on every tenant-scoped table. Then create a policy that checks the current session's tenant context against the row's tenant_id. In PostgreSQL, you set the tenant context using a session variable that your application middleware sets at the start of every request.

The pattern looks like this: your API middleware extracts the tenant_id from the authenticated user's JWT, calls SET app.current_tenant_id = 'tenant_xyz' on the database connection, and then all subsequent queries on that connection are automatically filtered. The RLS policy on each table checks current_setting('app.current_tenant_id') = tenant_id::text.

If you are using Prisma, note that Prisma does not natively support RLS session variables. You need to use Prisma's $executeRaw to set the session variable before running queries, or use Prisma's preview feature for PostgreSQL extensions. Drizzle ORM handles this more cleanly with its transaction API where you can run raw SQL to set the session context before your queries execute within the same connection.

Critical Mistakes to Avoid

  • Forgetting superuser bypass: PostgreSQL superusers bypass RLS by default. Your application database user must NOT be a superuser. Create a dedicated app role with limited privileges.
  • Missing indexes on tenant_id: RLS adds a filter to every query. Without a composite index on (tenant_id, id) or (tenant_id, created_at), your queries will table-scan. Add indexes on tenant_id for every table, plus composite indexes for your most common query patterns.
  • Not testing the bypass case: Write integration tests that explicitly try to access tenant B's data while authenticated as tenant A. These tests should fail with zero rows returned, not with an error. If your test suite does not include cross-tenant access tests, you do not have test coverage where it matters most.

RLS adds roughly 2% to 5% overhead on query execution time. On a table with 10 million rows and proper indexes, the difference between a tenant-scoped query with and without RLS is typically under 3 milliseconds. That is a negligible cost for the security guarantee it provides.

Building the RBAC Permission System

Multi-tenant dashboards need at least four roles. Fewer than that and you will find yourself adding roles within three months. More than six and your permission logic becomes impossible to reason about. Here is the hierarchy that works for 90% of B2B SaaS products.

The Four Essential Roles

  • Super Admin: Your internal team. Can access all tenants, impersonate users, manage billing across the platform, toggle feature flags, and view aggregate analytics. This role should be limited to 3 to 5 people in your organization.
  • Org Admin: The customer's account owner. Can manage users within their organization, update billing information, configure integrations, view audit logs for their tenant, and manage team-level settings. Typically 1 to 3 people per tenant.
  • Member: Regular team members within a tenant. Can use the product, view dashboards relevant to their work, and manage their own profile. Cannot invite users, change billing, or access admin settings.
  • Viewer: Read-only access. Can view dashboards and reports but cannot modify data, trigger actions, or change any settings. Useful for executives and external stakeholders.

Using Clerk or WorkOS for Organization-Aware Auth

Do not build auth from scratch. Clerk and WorkOS both support multi-tenant organizations natively, which saves you months of engineering time. Clerk charges $0.02 per monthly active user after the first 10,000. WorkOS charges $299/month for their base plan with unlimited users. For most early-stage SaaS companies, Clerk is cheaper until you pass 25,000 MAU.

Both platforms let you define organization roles and permissions, issue JWTs that include the user's org_id and role, and provide pre-built UI components for user management (invite flows, role assignment, SSO configuration). The JWT claims feed directly into your API middleware and your RLS tenant context.

When setting up Clerk organizations, define your roles and permissions in their dashboard and then enforce them at two levels: in your API middleware (for server-side protection) and in your UI components (for showing/hiding features). Never rely on UI-only permission checks. A determined user can bypass client-side checks in minutes.

kanban board showing project management workflow for a SaaS dashboard build

Permission Middleware Pattern

Create a middleware function that runs before every protected API route. It should extract the JWT from the request, verify it against Clerk's or WorkOS's public key, pull the org_id and role from the claims, set the RLS tenant context on the database connection, and then check the user's role against the required permission for that endpoint. If any step fails, return a 403 immediately. This middleware becomes the single enforcement point for both tenant isolation and role-based access, which makes auditing straightforward.

For a more detailed breakdown of building customer-facing portals with role-based access, check our guide on building B2B customer portals.

Core Dashboard Features and How to Build Them

A multi-tenant admin dashboard needs six core features to be useful. Shipping anything less means your support team will be fielding requests that the dashboard should handle. Here is what to build and how.

1. User Management

Every org admin needs to invite team members, assign roles, deactivate accounts, and view login activity. If you are using Clerk, their pre-built <OrganizationProfile /> component handles 80% of this. For the remaining 20% (custom fields, usage limits per user, team groupings), build a thin wrapper around Clerk's API that writes additional metadata to your own database.

2. Billing and Subscription Management

Integrate Stripe Billing with per-tenant subscriptions. Each tenant maps to a Stripe Customer, and each subscription tier maps to a Stripe Price. Use Stripe's Customer Portal for self-service billing management (payment method updates, invoice history, plan changes). Embed the portal via an iframe or redirect. Stripe charges 0.5% on recurring revenue through Billing, on top of their standard 2.9% + $0.30 per transaction.

For your super admin view, build a billing overview that shows MRR, churn rate, failed payments, and upcoming renewals across all tenants. Stripe's API provides all of this data. Pull it into a server-side endpoint and cache it with a 5-minute TTL since billing data does not need real-time accuracy.

3. Usage Analytics

Track API calls, storage usage, active users, and feature adoption per tenant. Store raw events in a time-series format (tenant_id, event_type, timestamp, metadata) and aggregate them into daily/weekly/monthly rollups via a scheduled job. For the dashboard UI, use Tremor or Recharts to render line charts, bar charts, and KPI cards. Pre-aggregate the data server-side so your frontend queries return in under 200 milliseconds.

4. Audit Logs

Every action an org admin or member takes should be logged: user invited, role changed, settings updated, data exported, API key created. Store audit logs in a separate table with columns for tenant_id, actor_id, action, resource_type, resource_id, metadata (JSONB), and timestamp. Make them immutable by not granting UPDATE or DELETE permissions to your app database role on the audit log table. Org admins should be able to view, search, and export their own tenant's audit logs.

5. Feature Flags per Tenant

Not every tenant should see every feature. Use a feature flag system scoped to tenant_id. You can build a simple one with a flags table (tenant_id, flag_name, enabled, config JSONB) or use LaunchDarkly ($10/month for their starter plan) with their multi-environment support. The super admin dashboard should let your team toggle flags per tenant, per plan tier, or globally. This is how you do gradual rollouts, beta programs, and plan-based gating.

6. White-Labeling and Custom Branding

Enterprise customers will ask for custom logos, brand colors, and custom domains. Store branding config per tenant (logo_url, primary_color, accent_color, custom_domain) and apply it at render time using CSS custom properties. For custom domains, use Vercel's Domains API or Cloudflare for SaaS to map customer domains to your app. This feature alone can justify a 2x to 3x price increase on your enterprise tier.

Tech Stack, Performance Patterns, and Connection Management

The stack we recommend for multi-tenant admin dashboards in 2031 is Next.js 15+ with App Router, Drizzle ORM (or Prisma if your team already knows it), PostgreSQL 16 on Supabase or RDS, Clerk for auth, and Stripe for billing. Here is why each piece matters and how to handle the performance challenges that come with multi-tenancy.

Why Next.js App Router

Server Components let you fetch tenant-scoped data on the server without exposing your database to the client. Parallel route segments let you load the sidebar, main content, and analytics panels simultaneously. Server Actions replace a huge number of API endpoints for mutations like updating settings, toggling feature flags, and managing users. The result is less client-side JavaScript, faster initial loads, and simpler code.

Drizzle vs. Prisma for Multi-Tenancy

Prisma is more mature and has better documentation, but Drizzle gives you more control over raw SQL, which matters for RLS. Drizzle's transaction API lets you run SET commands on the same connection as your queries, ensuring the RLS context is correct. Prisma requires $executeRaw workarounds for this. If your team has no preference, choose Drizzle. If your team already uses Prisma, stick with it and wrap your queries in a helper that manages the RLS context.

Connection Pooling

Multi-tenant apps open a lot of database connections because each request needs its own connection to set the tenant context via RLS session variables. Without pooling, a 100-request burst creates 100 connections. PostgreSQL starts struggling at around 200 concurrent connections on a standard instance.

Use PgBouncer in transaction mode (not session mode, because session mode holds connections open too long). Supabase includes PgBouncer by default. If you are on RDS, deploy PgBouncer on a small EC2 instance or use RDS Proxy ($0.015 per vCPU-hour). Set your pool size to 2x your expected peak concurrent requests, with a hard cap at 80% of your PostgreSQL max_connections.

Query Optimization with Tenant Indexes

Every table that has a tenant_id column needs a composite index with tenant_id as the leading column. For your most queried tables (users, audit_logs, usage_events), create composite indexes like (tenant_id, created_at DESC) for time-sorted queries and (tenant_id, status) for filtered queries. This ensures PostgreSQL uses index scans instead of sequential scans when RLS filters by tenant.

For analytics queries that aggregate across all tenants (super admin views), create separate materialized views that refresh on a schedule. Do not run aggregate queries against your transactional tables in real time. A materialized view that refreshes every 15 minutes is fast enough for admin analytics and avoids lock contention.

Caching Strategies

Cache at three levels. First, use Next.js built-in caching for static dashboard layouts and navigation. Second, use Redis (Upstash at $0.20 per 100K commands) to cache tenant configuration, feature flags, and billing status with a 60-second TTL. Third, use PostgreSQL materialized views for analytics aggregations. The combination keeps your p95 response time under 300 milliseconds for dashboard pages, even with 1,000+ tenants.

security compliance and data protection for multi-tenant SaaS applications

Testing Tenant Isolation and Data Boundaries

Tenant isolation bugs are the most dangerous bugs in multi-tenant SaaS. A single data leak can trigger contract terminations, legal liability, and permanent reputation damage. Your test suite needs to specifically target cross-tenant boundaries. Here is how to structure it.

Integration Tests for Cross-Tenant Access

Create a test suite that sets up two tenants (Tenant A and Tenant B), creates resources in Tenant A, and then attempts to read, update, and delete those resources while authenticated as a user in Tenant B. Every one of these operations should return empty results or a 403 error. Run these tests against your actual database with RLS enabled, not against a mock.

Cover every API endpoint and every database table. If you have 30 endpoints, you need 30 cross-tenant tests at minimum. This is tedious, but automating it is straightforward. Write a helper function that takes an endpoint, creates a resource as Tenant A, and then hits the same endpoint as Tenant B. Loop through every endpoint in your route table.

RLS Policy Verification

Write database-level tests that connect directly to PostgreSQL as your app role (not as superuser), set the tenant context to Tenant A, and verify that SELECT, INSERT, UPDATE, and DELETE operations only affect Tenant A's rows. Then switch the context to Tenant B and verify the same. These tests catch RLS policy misconfigurations before they reach your API layer.

Automated Leak Detection in CI

Add a CI step that runs after your regular test suite. It queries every tenant-scoped table and checks that no row's tenant_id falls outside the expected set for that test run. This catches subtle issues like cascade deletes that create orphaned records or background jobs that run without a tenant context.

We have seen teams skip isolation testing because "the ORM handles it." The ORM handles the happy path. It does not handle the edge cases: bulk imports that skip middleware, webhook handlers that process events without tenant context, background jobs that operate across tenants, admin endpoints that accidentally return unscoped queries. Every one of these is a real bug we have encountered in client projects.

For teams building their first internal tools dashboard, testing isolation is the single most important quality gate. Do not ship without it.

Timeline, Costs, and Getting Started

Here is what a realistic multi-tenant admin dashboard project looks like from kickoff to production.

Phase 1: Foundation (Weeks 1 to 3)

  • Set up Next.js project with App Router and Drizzle ORM
  • Configure PostgreSQL with RLS policies on all tenant-scoped tables
  • Integrate Clerk with organization support and define roles
  • Build tenant-scoped API middleware
  • Create the authentication flow (sign-up, org creation, invite flow)

Phase 2: Core Features (Weeks 4 to 6)

  • User management UI (invite, role assignment, deactivation)
  • Stripe Billing integration (subscription creation, plan changes, Customer Portal)
  • Usage analytics dashboard with Tremor or Recharts
  • Audit log capture and display
  • Feature flag system with per-tenant toggles

Phase 3: Polish and Enterprise (Weeks 7 to 10)

  • Super admin views (cross-tenant analytics, billing overview, impersonation)
  • White-labeling and custom branding per tenant
  • Custom domain support via Vercel Domains API
  • Cross-tenant isolation test suite in CI
  • Load testing with simulated multi-tenant traffic
  • Security audit and penetration testing

Cost Breakdown

  • Development: $25,000 to $60,000 depending on feature scope and team size (solo senior dev vs. small team)
  • Monthly infrastructure (first 100 tenants): Supabase Pro $25, Vercel Pro $20, Clerk $50 to $200, Upstash Redis $10, Stripe fees variable. Total: roughly $100 to $300/month
  • Monthly infrastructure (1,000+ tenants): Supabase Team or RDS $200 to $500, Vercel Enterprise $400+, Clerk $400+, Redis $50+. Total: $1,000 to $2,000/month
  • Ongoing maintenance: $2,000 to $5,000/month for feature development, bug fixes, and infrastructure management

Compare this to building on a low-code platform. Retool or Appsmith might get you a prototype faster, but the moment you need tenant isolation, custom RBAC, or white-labeling, you hit walls that require custom code anyway. Starting with a proper multi-tenant architecture from day one costs more upfront but eliminates the most expensive rewrite you will ever face.

If you are planning a multi-tenant admin dashboard and want to avoid the architectural mistakes that cost teams months of rework, we can help. We have shipped these systems for B2B SaaS companies across fintech, healthcare, and logistics. Book a free strategy call and we will walk through your specific requirements, timeline, and budget.

Need help building this?

Our team has launched 50+ products for startups and ambitious brands. Let's talk about your project.

multi-tenant dashboardSaaS admin paneltenant isolationRBAC dashboardB2B SaaS development

Ready to build your product?

Book a free 15-minute strategy call. No pitch, just clarity on your next steps.

Get Started