Multi-tenant SaaS developer

Multi-tenant Software Developer with Data Architecture expertise.

Multi-tenant B2B SaaS on vibe code or Bubble.io and Supabase: Privacy Rules and Row Level Security at the data type, schema-first tenant boundaries, role hierarchies that scale, reporting that holds at 120k+ rows. Plus white-label sub-app distribution for partner channels. GoBD and HR Portal are the shipped proof.

Multi-tenant SaaS development is the discipline of building products where every tenant’s data is isolated by the database layer, not by a UI filter. Privacy Rules on Bubble.io, Row Level Security on Postgres (Preferably Supabase), the same idea on either stack: the tenant boundary lives on the data type, scoped per request, structurally enforced. A tenant boundary in Conditional Visibility is one missed check from a cross-tenant leak. A tenant boundary on the data type holds whether the request comes from the UI, the API Connector, or a future client you have not built yet.

Most multi-tenant builds I see arrive with a tenant ID column and the hope that everything that reads from the database remembers to filter on it. That works until the first cross-tenant query is written and the wrong customer sees the wrong row. There is no half-measure version of multi-tenant. Either the data model, the Privacy Rules, and the backend workflow shapes all hold together at once, or the product has a quiet leak that surfaces on a Friday afternoon. The version that ships with the tenant model right from day one is cheaper, faster to extend, and easier to scale than the version that gets retrofitted after the first scare.


What multi-tenant means engineering-wise

Multi-tenant SaaS is a set of structural disciplines, not a feature. The framework decides what data is sensitive, what isolation guarantee the customer expects, what scale the reporting layer has to hold at. The engineering disciplines underneath are the same across stacks.

Schema-first tenant boundary. Every data type that carries customer data has a Tenant reference. The Privacy Rule on the data type says “current user must belong to the Tenant of this record.” That single rule, repeated structurally, makes the tenant boundary survive every read path the product will ever have. The UI is a thin layer over the database; the database decides what gets returned.

Role hierarchy at the data type. A user belongs to a Tenant, has Roles scoped per Tenant, has Permissions derived from Roles. The Privacy Rules read the Role to decide what fields the user can see. The dashboard, the forms, the export surface all read from this foundation. A new role is a data row plus a Privacy Rule update, not a code refactor.

Reporting that scales. The 120k-row reporting dataset on HR Portal was the stress test that exposed every lazy query the team had written. Server-side constraints, paginated repeating groups, scheduled workflows scoped to actual change windows, and search-index usage where it actually helped. The same patterns hold across Bubble.io and Postgres (Preferably Supabase): cheap reads come from cheap queries; cheap queries come from the right indexes and the right constraint shapes.

Tenant onboarding as a first-class workflow. Spinning up a new tenant is a workflow with steps: create the Tenant record, create the admin User, seed the role definitions, configure the per-tenant branding, send the welcome email. The workflow is idempotent, observable, and rollback-safe. A tenant is a customer; the customer’s first ten minutes shape their entire relationship with the product.

Privacy Rules at every layer. The data type is the source of truth. Conditional Visibility on the UI handles client-side state, but the database is what gates access. Backend workflows, API endpoints, scheduled tasks, and exports all run through the same Privacy Rules. The compliance posture lives in the database, not in a UI filter.


How the build runs

The pattern is the same across stacks (Bubble.io Privacy Rules, Postgres RLS) and across tenancy models (shared-database with tenant column, schema-per-tenant, sub-app-per-tenant for white-label distribution). Four phases, run in order, with a human gate between each.

The first week is a tenant model that the rest of the product can hang off. What is a Tenant. Who is a User. How does a User belong to a Tenant. Can a User belong to multiple Tenants. Are there sub-tenant hierarchies (a parent company with children). Does the customer want shared-database, schema-per-tenant, or sub-app-per-tenant for white-label resale. The brief that lands at the schema phase is precise enough that the Privacy Rules write themselves.

The schema is where multi-tenant lives or dies. Every data type that carries customer data has a Tenant reference. Privacy Rules at the data type level make the tenant boundary structural. The role hierarchy lives in data: a User has Roles scoped per Tenant, and Privacy Rules read the Role to decide what fields the user can see. The audit log captures who changed what on which Tenant’s record, so cross-tenant questions never have to be answered by guesswork.

The features render off the tenant-scoped schema. Each save writes through a workflow that respects the Privacy Rules. Dashboard queries are constrained at the database, not filtered in memory. Reports that have to hold at 100k+ rows use paginated repeating groups with server-side filters and indexed sort columns. The export surface produces per-tenant CSV/PDF that respects the tenant’s role-scoped data, every time.

Production deployment with cost monitoring configured before the first tenant, not after the first WU bill. The tenant-onboarding workflow runs idempotent and observable. The schema is designed to accept the next tenant variant (per-tenant white-label, per-tenant subdomain, per-tenant feature flags) as a configuration change, not a rewrite. The retainer that follows handles the scale window where reporting starts to slow, new tenant types, and the feature work the next milestone needs.


The proof: GoBD and HR Portal

Two production multi-tenant builds, shipped, with the receipts.


White-label SaaS distribution

The multi-tenant pattern extends naturally to white-label distribution when partners need to resell the product under their own brand. GoBD V2 is the lived-experience proof.

Bubble.io sub-apps are the right primitive for partner-channel white-label. Each partner agency or reseller gets their own Bubble sub-app with their own database, their own users, their own domain, and their own brand. The partner’s end clients sign up inside the partner’s sub-app, not the parent app, and never see the platform vendor’s brand. The editor design, the workflows, and the data type schemas are pushed from the parent app, so a product feature ships once and lands across every partner’s instance.

The complexity is not in the sub-app creation; it is in living with sub-apps over time. The parent-child sync discipline (what gets pushed, what stays in the child, what survives a push). The per-partner domain DNS (each partner points a custom domain at the child via CNAME, with Bubble’s domain configuration done in the parent). The data-driven branding layer (so a parent push does not wipe each partner’s customisation). The per-sub-app API keys (the API Connector’s private keys are part of the pushed configuration with no per-sub-app override, so any sub-app that needs its own keys has them overwritten on every parent push and they have to be re-entered by hand). The partner licence model (a recurring rate to operate the instance, not metered per end-client action, so partners can build a predictable margin into the rate they charge their own customers).

The same architectural intent transfers to Postgres (Preferably Supabase) / RLS schema-multitenancy via different primitives. The decision of “sub-app vs schema-per-tenant vs shared-database with tenant column” is a question of how much isolation the partner needs vs how much shared infrastructure they tolerate. We make that decision on the call.


Engagement terms

Same as the rest of my practice. Hourly only. $60 per hour. Tracked via Upwork or similar; weekly invoices; no setup fee, no padding. Minimum is a few hours of scoping discovery; no commitment beyond that until you are confident. Multi-tenant builds typically run eight to sixteen weeks for the V1 production-shaped product, with a retainer for ongoing scale work and the next milestone.

Send your spec, your tenancy model question (shared-database, schema-per-tenant, sub-app), and your scale expectations when you book the call. I will tell you whether the build is one I should lead directly or one I should pair with a platform specialist on. The decision happens once, with you in the loop.

Before the call

Common questions.

Privacy Rules vs UI filters: what's the difference?
Privacy Rules live on the data type and run at the database layer. UI filters live on the page and run in the browser (Bubble) or in client code (React). The database always wins. A Privacy Rule that says 'current user must belong to the Tenant of this record' returns zero rows for the wrong tenant no matter how the request arrives. A UI filter that does the same thing returns the wrong rows the first time someone calls your API directly. Multi-tenant SaaS depends on the boundary being structural, not stylistic.
Do tenant boundaries hold via the API too?
Yes, if the Privacy Rules are at the data type level. The Bubble API Connector and the Bubble Data API both respect Privacy Rules. Postgres Row Level Security applies to every connection regardless of the client. The boundary is the database; the database is consistent across all access paths. The mistake is putting the boundary in UI Conditional Visibility (Bubble) or in middleware (Node), where a different access path bypasses it.
What are the scale ceilings at 100k+ rows per tenant?
On Bubble.io the practical ceiling is determined by query shape, not row count. A tenant with 120k response rows (HR Portal proof) is comfortable when reports use server-side constraints, paginated repeating groups, and indexed sort columns. The same tenant becomes unusable when reports run client-side filters over 'all rows.' The schema and the query patterns decide whether the product feels fast at 100k rows or whether it collapses at 10k. On Postgres (Preferably Supabase) the ceiling is much higher with the right indexes; the discipline is the same.
How does the WU pricing model affect multi-tenant cost?
WU is per-workflow-action, not per-capacity. A lazy multi-tenant query that walks all rows costs WU on every read; the same query with server-side constraints costs WU only on returned rows. Scheduled workflows scoped to actual change windows cost WU only when work happens. The pricing migration from capacity to WU exposed every lazy pattern that was free under capacity. The audit pass on the GoBD product replaced client-side filters with server-side constraints, swapped 'all rows' lists for paginated repeating groups, and scoped scheduled workflows to actual change windows. Same product, much lower WU bill.
Sub-app vs schema-per-tenant vs shared-database: which do I pick?
Depends on the isolation guarantee and the distribution model. Shared-database with tenant column (most products): cheapest infrastructure, requires strict Privacy Rules / RLS discipline. Schema-per-tenant on Postgres (Preferably Supabase): better isolation, harder cross-tenant queries, more operational overhead. Bubble.io sub-app per tenant: best for white-label partner distribution where each partner needs their own domain, brand, and database under their own resale relationship. The GoBD V2 build uses sub-apps for partner channel + shared-database (with Privacy Rules at data type) for end-client tenants inside each sub-app. We make this decision on the call after understanding the distribution model.