# Workspaces, invitations and permissions

URL: https://ship-and-scale.vercel.app/docs/core/workspaces

Understand tenant ownership, roles, switching and lifecycle rules before extending product data.

## One organization-based model [#one-organization-based-model]

Each workspace is an organization row with memberships. Ordinary signup creates one personal workspace automatically; a valid invited signup joins only the invited workspace. Users can belong to multiple workspaces. There is no separate single-player data model or `SINGLE_PLAYER_MODE` switch.

Workspaces own product data and subscriptions. Paying for workspace A does not pay for B. Switching changes which workspace the dashboard displays, while account settings and onboarding remain tied to the person. A surviving account retains a membership through guarded fallback behavior when necessary.

## Selected workspace and explicit targets [#selected-workspace-and-explicit-targets]

The sidebar switcher remains usable with one workspace and offers creation. `getWorkspace()` reads the selected workspace from the validated `forma-workspace` HTTP-only cookie, falling back to an available membership. `getWorkspace(id)` verifies an explicit target against current memberships.

Every workspace mutation carries its intended ID so a stale tab does not silently write to a newly selected workspace. The server then validates that ID. In the [notes action](/docs/build/first-feature#create-through-the-signed-in-user), `getWorkspace(org_id)` checks access and SQL independently enforces the role.

## Role matrix [#role-matrix]

| Operation                             | Owner                              | Admin                    | Member |
| ------------------------------------- | ---------------------------------- | ------------------------ | ------ |
| Read permitted workspace data         | Yes                                | Yes                      | Yes    |
| Rename workspace / manage logo        | Yes                                | Yes                      | No     |
| Invite Admin or Member                | Yes                                | Yes                      | No     |
| Manage ordinary teammates/invitations | Yes                                | Within server role rules | No     |
| Manage billing and API keys           | Yes                                | Yes                      | No     |
| Transfer ownership                    | Yes                                | No                       | No     |
| Delete workspace                      | Yes, with guards                   | No                       | No     |
| Leave                                 | Transfer first if teammates remain | Yes                      | Yes    |

Exactly one Owner exists. Owner transfer is a specific operation, not a normal role dropdown option. The database remains authoritative if the UI is stale or a request is forged.

## Invitations [#invitations]

Owners/admins invite an email address as Member by default or explicitly as Admin. One pending invitation exists per workspace/email. Invitations expire after seven days. Resending retains the offered role, rotates the secret and expiry, and invalidates the old link. Merely opening a link does not consume it.

Team filters show Active, Pending, Expired, Revoked and All states. A send failure can leave a valid saved invitation with an actionable delivery status. Inspect the row before retrying; a later Resend is a new attempt with a new secret. Provider acceptance is not inbox receipt.

For new users, the validated recipient flow prevents a duplicate personal workspace. Existing users keep their other workspaces when accepting. Reject wrong-email, expired, revoked and forged context. Do not accept a role or invitation identity from editable Auth user metadata.

The existing abuse policy limits explicit workspace creation to five/account/hour, invitations to 30/workspace/hour and repeated recipient sends to a 60-second cooldown. These are not subscription or seat limits. Change the private policy through a new versioned migration, not a second UI-only constant.

## Transfer ownership [#transfer-ownership]

The current Owner selects an accepted, verified teammate. A pending invitation or unverified account cannot receive ownership. Confirmation makes that teammate Owner and the previous owner Admin atomically. Verify both role views after reload; a successful button click alone does not establish the invariant.

The lifecycle code coordinates locks before organization row locks. Preserve that ordering when adding data cleanup or lifecycle hooks. Competing owner departures and invitation acceptance need transactional verification rather than separate client checks.

## Leave and fallback [#leave-and-fallback]

An owner with teammates must transfer before leaving. A solo owner's Leave action opens the password-and-name-confirmed workspace deletion flow, including any pending invitations. Admins/members can leave under the server rules.

If leaving, removal or workspace deletion would leave a surviving account without membership, a personal fallback is created atomically. Account deletion is different and never creates a replacement workspace. Removing or demoting a user revokes their workspace API keys and outstanding issued invitations; rejoining does not revive old credentials.

## Delete with billing protection [#delete-with-billing-protection]

Workspace deletion preserves accounts and other workspaces. It removes workspace-owned records and its database-backed logo. Pending Checkout or a nonterminal subscription blocks deletion. Cancel, wait for the paid period to end, refresh billing, and only then retry the explicit deletion flow.

Account deletion also checks any workspaces it would delete. Transferred/shared workspaces and their subscriptions remain. Never remove the billing trigger to make deletion appear successful. [Subscriptions and deletion](/docs/billing/subscriptions#deletion-guards) describes the financial-state boundary.

## Extend workspace data [#extend-workspace-data]

New tables should carry `org_id`, a foreign key, appropriate cleanup behavior, RLS and explicit grants. Ordinary dashboard queries use the signed-in user's client and an explicit workspace filter. API/MCP access is a separate grant: existing workspace-only credentials do not automatically authorize new customer data.

The [workspace-notes recipe](/docs/build/first-feature) demonstrates create/list, role checks, stale-form targeting and retry IDs. If hiding team controls for a simpler product, preserve organizations and automatic membership. Hiding a route does not disable its server actions; remove a capability explicitly if the product should not permit it.

## Verify lifecycle behavior [#verify-lifecycle-behavior]

Test cross-workspace reads/writes, Member denial, owner transfer, last-membership fallback and stale tabs. Exercise received invitations for both new and existing accounts. Hosted SQL tests validate transactional boundaries; browser tests validate forms and navigation. Real inbox receipt and acceptance remain separate evidence and were not performed for this manual.