Roles & Permissions
Studio uses a two-tier role system: workspace roles determine organizational access, and project roles control content operations.
Workspace Roles
| Role | Description | Access Level |
|---|---|---|
| Owner | Creator of the workspace. Full control. | Implicit access to all projects. Can manage billing, members, settings. |
| Admin | Trusted team lead. Nearly full control. | Implicit access to all projects. Can manage members and settings. Cannot transfer ownership or delete workspace. |
| Member | Standard team member. | No implicit project access. Needs explicit project_members assignment. |
Workspace Role Hierarchy
Owner > Admin > Member- A workspace has exactly one owner (transferable)
- Admins and members are added via invitation (email)
- Invited members must accept before gaining access
Project Roles
Project roles are assigned per project to workspace members:
| Role | Description | Content Access |
|---|---|---|
| Editor | Content creator. | Can create, update, delete content and media. Cannot merge/reject branches. |
| Reviewer | Content approver. | Can read all content, merge/reject branches, approve/reject form submissions. Cannot create content. |
| Viewer | Read-only observer. | Can read content, models, and submissions. Cannot modify anything. |
Role Resolution
Workspace Owner and Admin get all project permissions regardless of project_members assignment. Only workspace Members need explicit project roles.
Tool Access Matrix
This matrix shows which roles can use each agent tool:
| Tool | Viewer | Reviewer | Editor | Admin | Owner |
|---|---|---|---|---|---|
| Read Operations | |||||
list_models | Yes | Yes | Yes | Yes | Yes |
get_content | Yes | Yes | Yes | Yes | Yes |
brain_query | Yes | Yes | Yes | Yes | Yes |
brain_search | Yes | Yes | Yes | Yes | Yes |
brain_analyze | Yes | Yes | Yes | Yes | Yes |
validate | Yes | Yes | Yes | Yes | Yes |
validate_schema | Yes | Yes | Yes | Yes | Yes |
list_branches | Yes | Yes | Yes | Yes | Yes |
branch_health | Yes | Yes | Yes | Yes | Yes |
relation_expand | Yes | Yes | Yes | Yes | Yes |
search_media | Yes | Yes | Yes | Yes | Yes |
get_media | Yes | Yes | Yes | Yes | Yes |
list_submissions | Yes | Yes | Yes | Yes | Yes |
| Content Operations | |||||
save_content | -- | -- | Yes | Yes | Yes |
delete_content | -- | -- | Yes | Yes | Yes |
copy_locale | -- | -- | Yes | Yes | Yes |
update_status | -- | -- | Yes | Yes | Yes |
vocabulary | -- | -- | Yes | Yes | Yes |
upload_media | -- | -- | Yes | Yes | Yes |
update_media | -- | -- | Yes | Yes | Yes |
delete_media | -- | -- | Yes | Yes | Yes |
| Review Operations | |||||
merge_branch | -- | Yes | -- | Yes | Yes |
reject_branch | -- | Yes | -- | Yes | Yes |
approve_submission | -- | Yes | -- | Yes | Yes |
reject_submission | -- | Yes | -- | Yes | Yes |
| Admin Operations | |||||
save_model | -- | -- | -- | Yes | Yes |
delete_model | -- | -- | -- | Yes | Yes |
init_project | -- | -- | -- | Yes | Yes |
add_locale | -- | -- | -- | Yes | Yes |
Permission Resolution
async function resolveAgentPermissions(
userId: string,
workspaceId: string,
projectId: string,
accessToken: string,
): Promise<AgentPermissions>Resolution Flow
1. Get workspace member role
|
+-- Owner/Admin? → Full access (all tools)
|
+-- Member? → Continue to step 2
|
2. Get project member record
|
+-- No record? → No access (empty tools list)
|
+-- Has role? → Continue to step 3
|
3. Normalize role (EE bridge gate)
|
+-- EE bridge present? → reviewer/viewer kept as-is on all paid plans
| (Starter, Pro, Enterprise). specificModels is
| honored only when the plan grants it (Pro+).
|
+-- No EE bridge (Community Edition)? → reviewer/viewer downgrade to
| editor, specificModels forced false
|
4. Filter tools by effective roleAgentPermissions Object
interface AgentPermissions {
workspaceRole: 'owner' | 'admin' | 'member'
projectRole: 'editor' | 'reviewer' | 'viewer' | null
specificModels: boolean // Model-level restriction active?
allowedModels: string[] // Restricted model IDs
allowedLocales: string[] // Restricted locale codes
availableTools: string[] // Tools this user can invoke
}Model-Specific Access
On Pro and Enterprise plans, project members can be restricted to specific content models:
{
role: 'editor',
specificModels: true,
allowedModels: ['blog-post', 'team-members']
}When specificModels is true:
- The agent only allows tools on models in
allowedModels - Content reads for other models return access denied errors
- The system prompt informs the agent of the restriction
Pro feature, EE-gated
Model-specific access requires the roles.specific_models feature flag, granted on Pro and Enterprise plans. The flag is also requires_ee, so it only functions with the Enterprise Edition bridge present. On Free and Starter plans, and in a self-hosted Community Edition deployment, specificModels is always treated as false (full model access).
Workflow Interaction
Permissions interact with the workflow configuration:
Auto-Merge Workflow
All write operations (from any permitted role) are automatically merged through the branch pipeline.
Review Workflow
| Role | Write Behavior |
|---|---|
| Owner / Admin | Writes auto-merge (authorized to approve their own work) |
| Editor | Writes create cr/* branches that wait for review |
| Reviewer | Can merge/reject branches created by editors |
function shouldAutoMerge(workflow: string, permissions: AgentPermissions): boolean {
if (workflow === 'auto-merge') return true
return permissions.workspaceRole === 'owner' || permissions.workspaceRole === 'admin'
}API Route Enforcement
Permissions are enforced at multiple levels:
Server Middleware
The auth middleware (01.auth.ts) validates session tokens on every request. Public endpoints are explicitly allowlisted.
Route-Level Checks
// In a route handler
const session = requireAuth(event)
const db = useDatabaseProvider()
// Verify workspace membership with required role
await db.requireWorkspaceRole(
session.accessToken,
session.user.id,
workspaceId,
['owner', 'admin'],
)Agent-Level Enforcement
The agent system filters available tools based on resolved permissions before sending them to the LLM:
const permissions = await resolveAgentPermissions(userId, workspaceId, projectId, accessToken)
const tools = filterToolsByPermissions(STUDIO_TOOLS, permissions.availableTools)
const phaseTools = filterToolsByPhase(tools, phase)Billing Enforcement
The billing middleware (03.billing.ts) checks workspace billing state and blocks requests when subscriptions are locked:
| Billing State | Effect |
|---|---|
subscribed | Full access |
trialing | Full access |
grace_period | Full access (payment overdue warning) |
trial_expired | 402 blocked |
canceled_expired | 402 blocked |
Plan Limits
Permissions are further constrained by plan-based numeric limits:
| Limit | Free | Starter | Pro | Enterprise |
|---|---|---|---|---|
| Team Members | 1 | 3 | 25 | Unlimited |
| AI Messages/Month | 0 | 150 | 1,500 | Unlimited |
| Media Storage | — | 1 GB | 15 GB | 100 GB |
| CDN API Keys | 0 | 3 | 25 | Unlimited |
| CDN Bandwidth | 0 | 2 GB | 60 GB | Unlimited |
| Form Models | 0 | 1 | 15 | Unlimited |
| Form Submissions/Month | 0 | 100 | 3,000 | Unlimited |
| Outbound Webhooks | 0 | 3 | 25 | Unlimited |
| Conversation API Keys | 0 | 0 | 15 | Unlimited |
| API Messages/Month | 0 | 100 | 3,000 | Unlimited |
| MCP Cloud Keys | 0 | 1 | 15 | Unlimited |
| MCP Cloud Calls/Month | 0 | 5,000 | 150,000 | Unlimited |
Related Pages
- Agent System -- how permissions flow into the agent
- Enterprise Edition -- advanced roles and model access
- API Reference -- API route authentication