Skip to main content

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

  1. Define roles in informer.yaml (shipped with the app)
  2. Assign roles to users/teams via the share endpoint
  3. 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:

FieldTypeRequiredDescription
idstringYesUnique identifier (used in code and API calls)
namestringYesHuman-readable display name
descriptionstringNoWhat this role allows

Role Resolution Rules

When an app is viewed, Informer resolves the current user's roles:

User TypeRoles Received
SuperuserAll defined roles
Publisher+ on owning teamAll defined roles
Shared user/teamOnly roles assigned in their share record
User without shareEmpty array []
No roles defined in YAMLEmpty 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:

ParameterTypeDescription
idstringApp 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:

FieldTypeDescription
rolesarrayList of role definitions from informer.yaml
roles[].idstringRole identifier
roles[].namestringDisplay name
roles[].descriptionstringOptional description

Error Responses:

  • 404 Not Found - App doesn't exist or user lacks access

Notes:

  • Returns an empty roles array if no informer.yaml exists or no roles are defined
  • Invalid role entries (missing id or name) 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] };
}