Workspaces & Teams
A workspace is the tenant. Products, members, billing, and audit rows all carry the same workspace_id. People can join many workspaces and hold different roles in each.
Workspace lifecycle
Creating a workspace
- After sign-in, users land on
/select-workspace - They can create a new workspace from
/create-workspace - A workspace is created with a name and auto-generated URL slug
- The creator is automatically assigned the OWNER role
- An onboarding wizard can guide them through initial setup
Switching workspaces
Users who belong to multiple workspaces see them listed on /select-workspace. Clicking one navigates to /w/[workspaceSlug]/dashboard.
Workspace settings
OWNERs can manage workspace settings at /w/[slug]/settings:
- Rename the workspace
- Transfer ownership to an ADMIN member
- Delete the workspace (destructive, requires confirmation)
Where to customize
- Workspace creation:
src/lib/workspace/get-workspace.tsand thecreate-workspacepage - Settings page:
src/app/(app)/w/[workspaceSlug]/settings/ - Settings client:
src/app/(app)/w/[workspaceSlug]/settings/workspace-settings-client.tsx - Settings actions:
src/app/(app)/w/[workspaceSlug]/settings/settings-actions.ts
Roles and permissions
Every workspace member has one of 4 roles. Roles determine what actions a user can perform.
Role hierarchy
| Role | Description |
|---|---|
| OWNER | Full control over the workspace. Manages billing, SSO, IP allowlist, and can transfer ownership. |
| ADMIN | Manages members, products, settings, API keys, webhooks, and audit logs. Cannot manage billing or SSO. |
| MEMBER | Creates and edits products, comments, bookmarks, and saved views. Cannot manage members or settings. |
| VIEWER | Read-only access to all workspace data. Cannot create or modify anything. |
Full permission matrix
| Permission | OWNER | ADMIN | MEMBER | VIEWER |
|---|---|---|---|---|
| Manage billing | yes | -- | -- | -- |
| Invite members | yes | yes | -- | -- |
| Manage members | yes | yes | -- | -- |
| Manage products | yes | yes | yes | -- |
| View audit logs | yes | yes | -- | -- |
| Read data | yes | yes | yes | yes |
| Manage custom fields | yes | yes | -- | -- |
| Manage SSO | yes | -- | -- | -- |
| Manage scheduled actions | yes | yes | -- | -- |
| Manage IP allowlist | yes | -- | -- | -- |
| Impersonate users | yes | -- | -- | -- |
Where to customize
Roles and permissions are defined in src/lib/rbac/roles.ts. To add a new permission:
- Add the permission key to
ROLE_PERMISSIONS:
// src/lib/rbac/roles.ts
export const ROLE_PERMISSIONS = {
// ... existing permissions
myNewPermission: { OWNER: true, ADMIN: true, MEMBER: false, VIEWER: false },
};
- Use it in your server actions:
import { hasPermission } from "@/lib/rbac/roles";
// In a server action:
const membership = await requireRole(workspaceId, user.id, ["OWNER", "ADMIN"]);
// Or check a specific permission:
if (!hasPermission(membership.role, "myNewPermission")) {
return { error: "Permission denied" };
}
Member management
OWNERs and ADMINs can manage workspace members from the Members page at /w/[slug]/members.
Available actions
- Invite new members by email with a role assignment
- Change a member's role (promotion/demotion)
- Remove a member from the workspace
- Leave workspace (available to any member, except the last OWNER)
Role change rules
- ADMINs cannot assign the OWNER role (only OWNERs can)
- The last OWNER cannot leave or be removed
- OWNERs can promote any member to any role
- ADMINs can change MEMBER and VIEWER roles
Invitations
Workspace invitations allow OWNERs and ADMINs to invite new members by email.
How invitations work
- OWNER/ADMIN opens the Members page and clicks Invite Member
- They enter an email and select a role
- The
sendInvitation()server action:- Validates the email and role
- Checks for duplicate active invitations
- Generates a secure 32-byte random token
- Inserts an
invitationsrow with 7-day expiry - Sends an email via Resend with the invitation link
- Records an audit log entry
- The invitee receives an email with a link to
/invite/[token] - If already signed in, they're added to the workspace immediately
- If not, they sign up/sign in and are redirected to complete acceptance
- The invitation's
accepted_atis stamped on acceptance
Managing pending invitations
From the Members page, OWNERs and ADMINs can:
- Revoke a pending invitation (deletes it)
- Resend an invitation (generates a new token and sends a new email)
Where to customize
- Invitation email template:
src/lib/email/templates/invite-email.tsx - Invitation expiry: Change the default 7-day expiry in the invitation creation logic
- Email provider: Replace Resend with another provider by updating
src/lib/email/
See Resend Setup for email configuration.
Ownership transfer
Workspace OWNERs can transfer ownership to an ADMIN member from Settings > Transfer Ownership.
How it works
- OWNER selects an ADMIN member from the dropdown
- Confirms the transfer
- The selected member becomes the new OWNER
- The previous OWNER is demoted to ADMIN
- The action is recorded in the audit log
Where to customize
- Transfer UI:
src/app/(app)/w/[workspaceSlug]/settings/transfer-ownership.tsx
Onboarding
New workspaces can optionally go through an onboarding flow.
How it works
- The
workspacestable has asetup_completeboolean flag (defaultfalse) - When a workspace is created, an onboarding wizard can guide the user through initial setup tasks
- Once setup is complete, the flag is set to
trueand the wizard is dismissed
Database
-- supabase/004_onboarding.sql
ALTER TABLE public.workspaces ADD COLUMN IF NOT EXISTS setup_complete BOOLEAN DEFAULT FALSE;
Where to customize
- Onboarding steps: Define your own setup checklist items
- Completion logic: Update the
setup_completeflag when the user finishes onboarding