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.
The UI follows the role
Write controls are only shown to roles that may use them, so a Viewer or Reviewer is not offered an Edit button they could only learn was refused at save time. Content editing (and vocabulary editing) is shown to callers who have the save_content tool, and for members limited to specific models only on the models they are allowed. Model management (add or delete a model, title field), form and comment settings, and moderation are shown to workspace Owners and Admins. The server routes remain the authority and check the same permissions.
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 |
list_comments | Yes | Yes | Yes | Yes | Yes |
| Content Operations | |||||
save_content | -- | -- | Yes | Yes | Yes |
replace_in_field | -- | -- | 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 |
request_changes | -- | Yes | -- | Yes | Yes |
approve_submission | -- | Yes | -- | Yes | Yes |
reject_submission | -- | Yes | -- | Yes | Yes |
approve_comment | -- | Yes | -- | Yes | Yes |
reject_comment | -- | Yes | -- | Yes | Yes |
reply_comment | -- | Yes | -- | Yes | Yes |
| Admin Operations | |||||
save_model | -- | -- | -- | Yes | Yes |
delete_model | -- | -- | -- | Yes | Yes |
init_project | -- | -- | -- | Yes | Yes |
add_locale | -- | -- | -- | Yes | Yes |
* An Editor's update_status moves entries to draft or archived only. Setting published -- with update_status, or a save_content that asks for published -- is limited to Owners and Admins, the same rule as the status badge and the remote contentrain_content_status tool. On a plan without review, an Editor's chat saves stay drafts instead of being published automatically.
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? → a stored reviewer/viewer role is kept as-is on
| every plan, including after a downgrade (assigning
| the role is what the plan gates). 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
Under the review workflow, whether a write merges itself is decided from what the change does, not who made it. The agent's write is classified by risk (for example low-risk content, bulk content, or a destructive schema change) and by scope, and the project's approval policy (.contentrain/approval-policies.json) decides how many approvals it needs. A project without a policy file uses the default policy: every write above read-only is held on its cr/* branch for one approval, and the author may give it.
| Role | Write Behavior |
|---|---|
| Editor | Writes create cr/* branches. They merge once the approvals the policy asks for are collected. |
| Reviewer | Can merge or reject branches and give approvals in the branch review panel (anyone with the merge_branch tool can; a policy may narrow which roles count). |
| Owner / Admin | Hold every tool, so they can merge, reject and approve too. Their role does not exempt their own writes from the policy. |
Calling merge_branch is not an approval: on a review-workflow project the branch must first collect the approvals its policy asks for, otherwise the merge is refused and the branch stays pending.
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 |
trial_active | Full access |
past_due | Full access (payment overdue warning) |
trial_expired | 402 blocked |
grace_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 Credits/Month | 0 | 300 | 1,600 | Unlimited |
| Media Storage | — | 5 GB | 25 GB | 100 GB |
| CDN API Keys | 0 | 3 | 25 | Unlimited |
| CDN Bandwidth | 0 | 3 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 Credits/Month | 0 | 0 | 450 | 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