In a B2B app, data belongs to a company, not to a person. Ada and Grace both work at Acme and should see the same projects; Bob at Globex must never see them. Some people belong to several companies and switch between them. You’ll build that: organizations, invitations with roles, a projects table scoped to the active organization, with admins allowed to delete and members only to read and edit. You’ll use: Organizations, Invitations, access rules, named rules. Before you start: do An app with signed-in users or be comfortable with sign-in and table rules.

How it works

Every session acts in one organization at a time. The access token carries it, and your rules read it: Each team table has an org column. The rule compares $record.org with $auth.org. Switching organization refreshes the token, and every rule follows.

1. Create the table

Data → Tables → Create table projects:

2. Write the rules once, as named rules

Open Policies → New policy and create three named rules: team_member: someone in the record’s organization.
  • All of these rules pass:
    • Check a value: $auth.org is set
    • Compare values: $record.org equals $auth.org
team_create: creating a record in your own organization.
  • All of these rules pass:
    • Check a value: $auth.org is set
    • Compare values: $input.org equals $auth.org
team_admin: an admin or owner of the record’s organization.
  • All of these rules pass:
    • Check a value: $auth.org is set
    • Compare values: $record.org equals $auth.org
    • Compare values: $auth.org_role is one of admin, owner
As JSON, for the management client or infrastructure as code:
Never leave out the “is set” check. If someone has no active organization and a record has no org either, “$record.org equals $auth.org” compares two empty values, which is true, and they’d see every unassigned record. Requiring $auth.org closes that.

3. Apply them to the table

In projects → Access: Because the edit rule reads org, members can’t move a project to another organization through the app.

4. Let a company sign up

When someone signs up, create their organization and switch into it:
src/onboarding.ts
After switchOrg, the session’s org and org_role are set and TOKEN_REFRESHED fires.

5. Invite colleagues

An admin or owner invites by email, with a role:
The email links to your accept-invite page with a token. The person signs in or signs up there, then your page accepts:
src/accept-invite.ts
redirectTo must be one of your allowed addresses under Users & auth → Domains. See Invitations.

6. Work inside the organization

Create records with the active organization, and list without filters, since the rule filters for you:
src/projects.ts

7. Switch between organizations

src/switcher.ts
Listen for TOKEN_REFRESHED in onAuthStateChange and reload lists, so nothing from the previous organization stays on screen. switchOrg(null) leaves organization mode. To sign straight into one: signInWithPassword({ email, password, org: "acme" }).

8. Files and live updates per organization

Use the same idea everywhere:
  • Files: put the organization first in the key (acme/contracts/2026.pdf) and use the rule object.segments.0equalsobject.segments.0 equals auth.org (plus $auth.org is set) on the bucket. See Rules on storage.
  • Channels: name them org:acme:… and add a channel rule that requires the organization. See Rules on realtime.

9. Test the boundary

test/tenancy.test.ts
The last check catches the “two empty values” trap.

Server-side and staff access

  • Your own backend acting for a person: pass their token, so the rules apply (Node.js).
  • Your support staff across all organizations: use a separate admin tool with a secret key, rather than adding a global role to customer-facing rules. If you must, add Any of: Has role platform_support; team_member.

Troubleshooting

Organizations

Members, roles, teams and metadata.

Recipes: teams

More multi-tenant rules.