Organisations and teams
Multi-tenant apps where every organisation sees only its own data.
In a B2B app, rows belong to an organisation and only its members may see them. Mark one table as the membership table, give organisation-owned tables an org_field, and use two more access scopes: org for members and org_admin for the people who run the organisation. Postgres enforces the boundary, and verify proves it against a second organisation.
| role | organisation | membership | project | invoice |
|---|---|---|---|---|
| member | org | org | org | org |
| org admin | org_admin | org_admin | org | org_admin |
| staff | all | all | all | all |
- A member can't read or write another organisation's rows
- Enforced by Postgres row-level security
- org and org_admin
- Two scopes next to own, all and none
- Enforced by Postgres
- Row-level security per organisation
- Proven by verify
- A second tenant, probed and rolled back
- Template ready
- organisations-and-teams sets it up
How organisation scope is set up
Mark one table with org_membership: which ref is the organisation and which is the member, and optionally which role values make an admin and which status values count as active.
Give every other organisation-owned table an org_field and set its access cells to org or org_admin per role. Staff roles can keep "all" to see every organisation.
Apply and verify. The checker refuses a half-finished setup, and verify builds a second organisation in a rolled-back transaction to prove your members can't read, change, delete or create rows in it.
{ "name": "membership",
"org_membership": { "org_ref": "organisation_id",
"member_ref": "member_id",
"role_field": "role", "admin_values": ["owner", "admin"],
"status_field": "status", "active_values": ["active"] } }
{ "name": "project", "org_field": "organisation_id",
"access": { "member": { "read": "org", "create": true,
"update": "org", "delete": "org_admin" } } }
// verify, in the role matrix
✓ a member can't read, change, delete or create
rows in another organisation
Tenancy you don't have to hand-roll
Org scope in Postgres
"org" means rows of an organisation the user belongs to, "org_admin" rows of one they administer. Both become row-level security policies.
Admins and paused members
role_field and admin_values say who administers an organisation; status_field and active_values let you pause a membership without deleting it.
Writes stay inside
Members create rows only in their own organisations and can't move a row to another. With exactly one organisation, a new row lands in it automatically.
Checked before apply
An org cell without a membership table, a table with no ref to the organisation or org_admin without a role field is refused with the reason.
A cross-tenant test
Verify copies one of your organisations, moves a row into the copy and proves a member of yours can't touch it. Nothing is kept.
Team admin and invites
The TeamAdmin block adds people with a role, changes roles and switches accounts off. The template adds email invitations that join on sign-up.
Organisations and teams, as a template
The organisations-and-teams template adds organisations, teams, memberships with owner, admin and member roles, and email invitations. Its attach_to param makes one of your tables, such as projects, private to each organisation. Comments, notifications and booking-calendar take an org_field param to join in.
Organisations and teams: common questions
Can I build a multi-tenant SaaS app on Appbricx?
Yes. Rows belong to an organisation, members see only their organisations' rows, and Postgres row-level security enforces it for every caller, not just your screens.
Who counts as an organisation admin?
The membership table's role_field decides: members whose role is one of admin_values get org_admin access. Rules can add more, such as one owner per organisation.
Can my own staff see every organisation?
Yes. Organisation scope applies to the roles you give org cells. A staff role with "read": "all" keeps seeing everything.
How do people join an organisation?
With the organisations-and-teams template, owners and admins invite by email. Someone who already has an account joins straight away; anyone else joins when they sign up with that address.