Engineering

When one client asks for their own login

Most apps start with one tenant because one tenant is all there is. You are the customer, your team is the user list, and nobody has to ask who owns a record. Then a client asks for their own login for their own staff, and the shape of the thing changes. Every record needs an owner. Every query needs a filter. The interesting question stops being what the app does and becomes who is allowed to see which rows. On Bubble you answer that question one data type at a time, by hand, with nothing checking your work.

Isolation you have to remember

Bubble's own guide to SaaS data structures lays out the pattern. Add a Company data type, hang a Related Company field off every other type, then write privacy rules that compare the current user's Related Company against the record's. The guide suggests three rules per type: full access for a system admin, full access when the companies match, and an Everyone else rule that grants nothing. It is a sound design. It is also entirely manual. Tenant isolation is not a property of the database here, it is a property of your having remembered to add the field and the rules to every new type you create.

Privacy rules work field by field. Each one carries separate toggles for viewing, for use as a search constraint, for viewing attached files, and for saving with auto-bind. Miss a single field on a single type and that field goes out to every logged-in user of every tenant. Bubble's documentation is honest about the stakes: once data reaches a user's device it is no longer secure, and client-side operations like filtering and sorting run on data that has already been returned, so they trigger no additional privacy checks. A filtered repeating group gives you a screen that looks right over data that already shipped.

The part that gets me is that nothing tells you when you are wrong. You add a data type on a Tuesday, you ship it, and the rule you did not write is not an error. It is just an absence.

The same rule, written where the database can enforce it

Postgres has had row level security since version 9.5, and Supabase exposes it directly. A policy is a WHERE clause the database staples onto every query touching the table, so `using ( (select auth.uid()) = user_id )` means a client asking for every row gets back only its own, whatever the client code believes it asked for. Supabase's docs stress that this holds through third-party tooling too, because the rule lives in the database rather than in the layer that queries it.

The difference that matters for a multi-tenant app is not expressiveness. Bubble's privacy rules can express tenant scoping, and the SaaS guide shows how. The difference is that a Postgres policy is text in a migration file, which means you can write a test that asserts tenant B cannot read tenant A's rows. Supabase's own procedure for securing a table is four steps, and the third is creating a .sql file under supabase/tests that asserts both allow and deny for select, insert, update and delete, for signed-out and signed-in roles. Their words: until the suite passes, you do not know whether the policies do what you intended.

That sentence is the whole argument. Isolation on Bubble is a thing you believe. Isolation under RLS is a thing CI fails on. When a junior adds a table and forgets the policy, `supabase test db` goes red before the branch merges, and no customer finds out instead.

What the escape hatch costs

Bubble does offer real isolation. Sub-apps give each client a copy of the main app with its own database, which is what people usually picture when they imagine giving a client their own login on their own subdomain. The docs are specific about the trade: pushing the main app to its sub-apps overwrites any change made to a sub-app on its own, custom domains and favicons and OAuth settings do not propagate, plugin subscriptions do not carry over and have to be bought again per sub-app.

The pricing shapes the decision more than the mechanics do. As of today Bubble lists sub-apps on the Team plan at $549 a month billed annually, against $209 for Growth and $59 for Starter. Each sub-app then carries its own subscription, though it does not itself need to be on Team. So the first client who wants isolation moves your floor from $209 to $549, and every client after that adds a line item. Workload units are also pooled per project rather than per customer, with 500K a month on Team and overage billing after that, so one heavy tenant spends the budget the quiet ones were going to use.

A Postgres table with a tenant_id column and an RLS policy costs the same at one tenant and at forty. That is not a knock on Bubble's pricing, which is coherent for the product it is. It is that per-tenant billing and per-tenant app copies are the shape of the answer available inside a platform where the database is not yours to partition.

When staying is the right call

If you have one tenant, or five tenants who all work for the same parent company, this entire post is theoretical for you. Bubble privacy rules scoped by a Related Company field handle that case, and the labour of writing them is smaller than the labour of running your own stack. Ship, charge, come back to this when a prospect asks about SOC 2 or a client demands their data sit in a separate database.

The case for moving is narrower than vendors on either side pretend. It is strong when tenant count is growing, when the data is regulated or contractually isolated, or when the number of data types has outgrown anyone's ability to audit the rules by reading them. We moved Stay World Class off Webflow and Xano onto Next.js, NestJS and Supabase for reasons that included this one, and the migration ran as a four stage ETL that mapped legacy IDs to Postgres UUIDs so nothing broke on cutover. Lighthouse went from 55.91 to 91 out of 100 and LCP came down 77%, which was the visible half. The invisible half was that access rules became code with tests around them.

Nobody should migrate because an agency blog told them to. Migrate when you can name the specific rule you are afraid you forgot.

Sources

Is your platform outgrowing its stack?

Book a call and we'll walk through how we'd approach your platform, with an estimate grounded in a working prototype.