Roles
Apps support custom roles defined in informer.yaml. Roles provide fine-grained access control beyond the standard team permission levels, letting app authors control which features and data individual users can access.
How Roles Work
- Define roles in
informer.yaml(shipped with the app) - Assign roles to users/teams via the share endpoint
- Check roles in client-side code (
window.__INFORMER__.roles) or server route handlers (request.roles)
Role Definition
Define roles in your app's informer.yaml file:
# informer.yaml
roles:
- id: viewer
name: Viewer
description: Read-only access to dashboards
- id: editor
name: Editor
description: Can modify records and settings
- id: approver
name: Approver
description: Can approve submitted items
Each role requires:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier (used in code and API calls) |
name | string | Yes | Human-readable display name |
description | string | No | What this role allows |
Role Resolution Rules
When an app is viewed, Informer resolves the current user's roles:
| User Type | Roles Received |
|---|---|
| Superuser | All defined roles |
| Publisher+ on owning team | All defined roles |
| Shared user/team | Only roles assigned in their share record |
| User without share | Empty array [] |
| No roles defined in YAML | Empty array [] for everyone |
Publishers and above on the owning team automatically receive all roles — they don't need explicit role assignments. Regular shared users only receive the roles listed in their AppShare.roles array.
GET /api/apps/{id}/roles
Returns the role definitions from the app's informer.yaml. This returns the available roles, not the current user's assigned roles.
Authentication: Required
Permissions Required: None (any user who can read the app)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | App UUID or natural ID |
Response:
{
"_links": {
"self": { "href": "/api/apps/analytics:sales-dashboard/roles" }
},
"roles": [
{
"id": "viewer",
"name": "Viewer",
"description": "Read-only access to dashboards"
},
{
"id": "editor",
"name": "Editor",
"description": "Can modify records and settings"
},
{
"id": "approver",
"name": "Approver",
"description": "Can approve submitted items"
}
]
}
Response Fields:
| Field | Type | Description |
|---|---|---|
roles | array | List of role definitions from informer.yaml |
roles[].id | string | Role identifier |
roles[].name | string | Display name |
roles[].description | string | Optional description |
Error Responses:
404 Not Found- App doesn't exist or user lacks access
Notes:
- Returns an empty
rolesarray if noinformer.yamlexists or no roles are defined - Invalid role entries (missing
idorname) are silently filtered out
Roles in Shares
When sharing an app, you can assign roles to the share recipient. Use the roles field in the PUT /api/apps/{id}/shares/{principalId} endpoint:
PUT /api/apps/analytics:sales-dashboard/shares/john.doe
{
"accessLevel": 1,
"roles": ["viewer", "approver"]
}
The roles array contains role IDs that must match definitions in informer.yaml. Role IDs that don't match a defined role are stored but ignored at view time.
See Ownership & Sharing for the full share endpoint documentation.
Roles in View Context
When an app is viewed, the resolved roles are injected into the browser context:
window.__INFORMER__ = {
report: { id: 'app-uuid', name: 'Sales Dashboard' },
theme: 'light',
roles: ['viewer', 'approver']
};
Use window.__INFORMER__.roles to conditionally render UI:
const roles = window.__INFORMER__?.roles || [];
if (roles.includes('approver')) {
document.getElementById('approveBtn').style.display = 'block';
}
if (roles.includes('editor')) {
enableEditMode();
}
Roles in Server Routes
Server route handlers receive the user's roles via request.roles and can declare required roles in their config:
Checking Roles in Handler Code
// server/dashboard.js
export async function GET({ request, query }) {
const isEditor = request.roles.includes('editor');
const rows = await query('SELECT * FROM items ORDER BY id');
return {
items: rows,
canEdit: isEditor
};
}
Restricting Routes by Role
Use config.roles to restrict an entire route to specific roles. Users without a matching role receive 403 Forbidden:
// server/approve.js
export const config = {
roles: ['approver']
};
export async function POST({ request, query }) {
const { itemId } = request.body;
await query('UPDATE items SET status = $1 WHERE id = $2', ['approved', itemId]);
return { status: 200, body: { approved: true } };
}
The config.roles check uses OR logic — the user needs any one of the listed roles.
Common Patterns
Role-Based UI
const roles = window.__INFORMER__?.roles || [];
// Show/hide sections based on roles
document.getElementById('admin-panel').hidden = !roles.includes('editor');
document.getElementById('approve-section').hidden = !roles.includes('approver');
// Disable inputs for viewers
if (!roles.includes('editor')) {
document.querySelectorAll('input, select, textarea').forEach(el => {
el.disabled = true;
});
}
Share with Roles
// Share app with a user and assign roles
await fetch('/api/apps/analytics:sales-dashboard/shares/john.doe', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
accessLevel: 1,
roles: ['viewer', 'approver']
})
});
Server Route with Role Restriction
// server/items/[id]/approve.js
export const config = {
roles: ['approver'],
timeout: 10000
};
export async function POST({ request, query }) {
const rows = await query(
'UPDATE items SET status = $1, approved_at = NOW() WHERE id = $2 RETURNING *',
['approved', request.params.id]
);
if (rows.length === 0) {
return { status: 404, body: { error: 'Item not found' } };
}
return { status: 200, body: rows[0] };
}