Architecture
Roles & permissions
OpsDash uses a four-role RBAC system layered on top of subscription-based feature access. Roles are per-org — a user can be an admin in one workspace and a viewer in another.
The four roles
| Role | Who | What they can do |
|---|---|---|
| owner | Created the workspace (or received transferred ownership) | Everything: billing, members, feature flags, org settings, delete org, transfer ownership |
| admin | Trusted team member | Same as owner except cannot delete org or transfer ownership |
| member | Standard team member | Full CRUD in CRM, Projects, Forms, Support. No org settings or billing. |
| viewer | Read-only stakeholder | View all accessible modules. No create, edit, delete, or export. Enterprise only. |
The viewer role is an Enterprise-tier feature (platform:rbac-viewer). On Free and Pro, all team members have full CRUD access.
Action-level gating
| Action | Allowed roles |
|---|---|
| Export (CSV, PDF) | owner, admin, member (viewer blocked) |
| Billing (upgrade, manage subscription) | owner, admin only |
| Feature flags (enable/disable, allowed roles) | owner, admin only |
| Invite members, change roles, suspend members | owner, admin only |
| Create, edit, delete (CRM, Projects, etc.) | owner, admin, member (viewer blocked) |
| API keys (create, revoke) | owner, admin only |
Restricting access per feature
Org admins can narrow access per feature via Settings → Admin → Feature Flags. Each feature has an Allowed Roles setting:
- All roles (default):
allowed_roles = null— every role with the feature enabled can access it - Specific roles: e.g.
allowed_roles = ['owner', 'admin']— only those roles can access
Server-side enforcement
// Block viewer role from all mutations
const { supabase, org, user } = await withOrg(slug);
await requireNonViewer(supabase, org.id, user.id);
// Block non-admin roles from admin actions
const { supabase, org, user } = await withOrgAdmin(slug);
// Block access if feature flag is disabled or role excluded
await requireFeatureAccess(supabase, org.id, user.id, 'crm:export');Platform admin
Users listed in PLATFORM_ADMIN_EMAILS have cross-org visibility via the /admin panel. They can view all orgs, users, and subscriptions. This is separate from the org-level admin role and bypasses RLS through the service role client.