Build Apps Without Code

Verified against Lovable · 2026-07-21

Scope a multi-tenant B2B SaaS with real tenant isolation and billing

A Lovable brief for a multi-tenant SaaS where the row-level-security policy, not the frontend, is what actually stops one organization from reading another's data, plus a billing flow routed through Stripe's own hosted portal.

Lovable7 fillable variables

The prompt

Ready to copy — highlighted parts are example details you can swap.

You are building Rosterly, a multi-tenant B2B SaaS where every customer signs up as its own isolated agency, not a shared account. Treat tenant isolation as the load-bearing requirement of this build, not a detail to add later.

PRODUCT
Rosterly is for small agencies scheduling shifts for hourly staff. Each agency has its own members, its own data, and its own subscription — no agency should ever be able to query, list, or see a row that belongs to another agency, even by guessing an ID in the URL.

DATA MODEL
- Organization: id, name, slug, subscription_tier (references the tiers below), stripe_customer_id, created_at.
- Membership: id, organization_id (references Organization), user_id (references auth user), role (owner, admin, or member).
- Invite: id, organization_id (references Organization), email, role, token, expires_at, accepted_at.
- Every other business table in this app — Shift, Employee, and TimeOffRequest — must carry an organization_id column and nothing else may scope access to it.

TENANT ISOLATION RULES
Write a row-level security policy on every business table that filters strictly on organization_id matching a membership row for auth.uid() — never on a client-supplied header, a URL parameter, or a value trusted from the frontend. A user with no accepted Membership row for an organization must get zero rows back from every table scoped to it, not a 403 — the query itself should simply return nothing, because a policy that leaks a count or an error message is still a leak. New Organizations are created only through a proper signup flow that creates the Organization and the creator's owner Membership in one transaction, never as two separate steps a signup could fail between.

BILLING
Subscription tiers: Starter ($29/mo, up to 10 employees), Growth ($89/mo, up to 50 employees). Show the current tier and usage against its limits on a Billing settings page, with a Stripe customer-portal link for upgrades and payment-method changes — do not hand-build a checkout form; route through Stripe's own hosted portal. When an organization tries to invite past its tier's employee limit, block the invite and show an inline upgrade prompt instead of silently allowing it.

SCREENS
1. Sign up, which creates the Organization and owner Membership together.
2. An invite flow: an admin enters an email and role, generates an Invite row, and the invited person accepts it into a Membership on their next login.
3. A main workspace view scoped to Shift, Employee, and TimeOffRequest.
4. Organization settings: members list with role management, and the Billing page above.
5. A personal-account switcher if a user's email is a member of more than one Organization.

CONSTRAINTS
Primary brand color: #1F6F5C (deep teal). Use Lovable's native Supabase integration for auth and the database — do not fake multi-tenancy in client-side state or a single shared table filtered only in the frontend query, since that leaves every row reachable by anyone who opens the network tab. Before calling this done, describe in plain language how you tested that a second Organization's data is actually unreachable, not just hidden by the UI.

Customize

Optional — swap in your own details for the highlighted parts above.

Why this works

Lovable's Supabase integration defaults to whatever access pattern the prompt actually specifies, and a vague "make it multi-tenant" request tends to produce filtering only in the frontend query — a .eq('organization_id', currentOrg) clause that is enforceable only if the client behaves itself. Anyone who opens their browser's network tab and edits that request can read every organization's rows, because the underlying Postgres table has no policy stopping them at the database layer; the frontend filter was never a security boundary, just a display convenience. Naming the exact row-level-security predicate — filtered on a real Membership join to auth.uid(), never a client-supplied value — is what forces the model to write the check where it actually matters. The "zero rows, not a 403" instruction targets how RLS is supposed to behave and why that's correct here: a well-formed policy makes unauthorized rows simply not exist in the result set, indistinguishable from "no data," rather than raising an application-level authorization error. That's the right behavior for tenant isolation specifically, because an error message that says "organization X has no employees" is itself a leak — it confirms organization X exists at all, which a properly scoped query would never reveal to someone outside it. Spelling this out stops the model from reaching for an easier-to-write 403 check that inadvertently confirms more than it should. Routing billing through Stripe's hosted customer portal rather than a hand-built checkout form is specific to what a Lovable-generated app can safely maintain: Lovable has no PCI-compliant payment form of its own, and an unscoped "build a checkout page" instruction produces either a static form with no real Stripe wiring behind it, or a half-implemented Stripe Elements integration that mishandles the webhook events subscription-state changes actually depend on. Directing the build at the portal sidesteps an entire class of billing bugs the tool has no way to get right unsupervised. Finally, naming the signup-transaction requirement closes a specific race: if Organization creation and owner Membership creation happen as two separate calls, a failure between them — a network blip, a retried duplicate-key insert — leaves an orphaned Organization with no owner and no way back in, a state that's easy to generate and hard to notice until a real signup hits it.

What you get back

A Lovable app where creating a second test organization and logging in as its owner shows zero shifts, employees, or time-off requests from the first organization — even when navigating directly to a first-organization record's URL by ID — and where the Billing page reflects Stripe test-mode subscription state and opens the real Stripe customer portal on click.

Verified against

Lovable Lovable web app (Supabase-integrated chat builder) · 2026-07-21

Changelog

  • 2026-07-21 Initial publish, verified against Lovable's Supabase-integrated builder with explicit RLS-policy and one-transaction-signup requirements.

Need this built into your business?

If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.

EXPLORE CUSTOM SOFTWARE
All Build Apps Without Code prompts

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY