Build the product

Organizations, Teams, and Tenancy

Decide how customers collaborate, then adapt the shipped organization roles, invitations, seat limits, and tenant boundaries.

Verified against starter commit 2a1a04a.

Use this page to answer a product question before a coding question: does your SaaS sell to one person, to a shared workspace, or to several workspaces per person? Sushi SaaS ships the common “team of one that can grow” model. Every customer starts in a personal organization, and all billable resources belong to that organization.

The Shipped Collaboration Model

BehaviorDefaultOption you may choose later
Resource ownershipOrganization-wideAdd creator-only or per-resource sharing through can()
Rolesowner, admin, memberAdd product-specific roles or Better Auth dynamic access control
BillingFlat price per organizationAdd per-seat quantity and a tested proration policy
NavigationQuery-based workspace switcher using ?org=<slug>Add organization-scoped path segments if URLs should express a stronger hierarchy
TeamsOrganization membership and invitationsEnable nested teams only if your product needs subgroups

Every Account Has an Organization

Signup creates a personal organization and owner membership. There is no separate user-owned resource path: files, tasks, orders, subscriptions, credits, and reservations use org_uuid.

Better Auth supplies the organization membership tables and invitation mechanics. Sushi adds tenant scoping, application permissions, seat enforcement, pooled balances, and concurrency guards.

Roles and Capabilities

Roles form a hierarchy: owneradminmember.

ActionMemberAdminOwner
Read organization data, create/delete files, spend credits
Invite, remove, and change member roles
Update the organization
Manage billing or delete the organization

Call getOrgContext(request, optionalSlug) to resolve the current user, organization UUID, and role. Call can(ctx, action, resource) for role authorization. Never accept org_uuid from a request body.

Tenant Scoping

Only models query the database. Every tenant-table query uses scopedToOrg(column, ctx.orgUuid), and every insert writes the organization UUID. Architecture tests scan these models so a new unscoped query fails the build.

The app uses the application's organizations.uuid; Better Auth's internal organizations.id is only for its own relationships. OrgUuid is branded so a user UUID cannot be passed accidentally to an entitlement or tenant query.

Invitations and Seat Limits

Invitations expire after 72 hours, are bound to the invited email, and are accepted or declined with separate endpoints. Re-inviting supersedes the old pending invitation.

The plan limit counts accepted members plus live pending invitations:

PlanTotal seats
Free1
Plus5
Max20

Capacity is checked both when sending and accepting. Both checks share a per-organization PostgreSQL advisory lock so two requests cannot claim the final seat. Support may set a temporary or permanent audited seat override from the admin console.

Safety Invariants

  • An organization cannot lose its final owner.
  • A user cannot leave their only organization.
  • A member cannot grant a role above their own.
  • Downgrades never remove existing members; they block new invitations and acceptances until usage is within the new limit.
  • Credits and Stripe customers belong to the organization, not to the member who clicked checkout.

The customer UI is under /{locale}/account/team. The API surface is under /api/account/team/*. Organization support controls live in the separate admin application.

The starter ships a query-based workspace switcher. It does not yet ship organization-slug route segments, custom roles, nested teams, or per-seat Stripe quantities. These are additive product decisions rather than hidden partial implementations.

Before changing this model, write down who owns customer data, who may invite or remove people, whether members may delete shared content, and who pays. You are done when two organizations cannot read each other's rows, the last owner cannot be removed, concurrent invitations cannot exceed the final seat, and a downgrade preserves existing data and members.

Related: Use Plans and Entitlements to set seat limits, and Anatomy of a Modern SaaS to decide whether organization-first ownership fits your product.

Organizations, Teams, and Tenancy · Sushi SaaS