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

  1. After sign-in, users land on /select-workspace
  2. They can create a new workspace from /create-workspace
  3. A workspace is created with a name and auto-generated URL slug
  4. The creator is automatically assigned the OWNER role
  5. 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.ts and the create-workspace page
  • 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

RoleDescription
OWNERFull control over the workspace. Manages billing, SSO, IP allowlist, and can transfer ownership.
ADMINManages members, products, settings, API keys, webhooks, and audit logs. Cannot manage billing or SSO.
MEMBERCreates and edits products, comments, bookmarks, and saved views. Cannot manage members or settings.
VIEWERRead-only access to all workspace data. Cannot create or modify anything.

Full permission matrix

PermissionOWNERADMINMEMBERVIEWER
Manage billingyes------
Invite membersyesyes----
Manage membersyesyes----
Manage productsyesyesyes--
View audit logsyesyes----
Read datayesyesyesyes
Manage custom fieldsyesyes----
Manage SSOyes------
Manage scheduled actionsyesyes----
Manage IP allowlistyes------
Impersonate usersyes------

Where to customize

Roles and permissions are defined in src/lib/rbac/roles.ts. To add a new permission:

  1. 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 },
};
  1. 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

  1. OWNER/ADMIN opens the Members page and clicks Invite Member
  2. They enter an email and select a role
  3. The sendInvitation() server action:
    • Validates the email and role
    • Checks for duplicate active invitations
    • Generates a secure 32-byte random token
    • Inserts an invitations row with 7-day expiry
    • Sends an email via Resend with the invitation link
    • Records an audit log entry
  4. The invitee receives an email with a link to /invite/[token]
  5. If already signed in, they're added to the workspace immediately
  6. If not, they sign up/sign in and are redirected to complete acceptance
  7. The invitation's accepted_at is 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

  1. OWNER selects an ADMIN member from the dropdown
  2. Confirms the transfer
  3. The selected member becomes the new OWNER
  4. The previous OWNER is demoted to ADMIN
  5. 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 workspaces table has a setup_complete boolean flag (default false)
  • 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 true and 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_complete flag when the user finishes onboarding