Roles define what users can see and do within the CMS dashboard. Alinea automatically creates a default Admin role for every project with full access to every workspace, root, and entry. You do not need to define this manually, ensuring there is always an unrestricted management account available.
To create a custom role, use the Config.role helper and register it in your CMS configuration.
const myRole = Config.role('My role', {
async permissions(policy, graph) {
// Define permissions here
}
})
export const cms = createCMS({
roles: { myRole },
// ...other config
})The permissions function is where you define your access policies. You use allow to grant access to specific actions and deny to strictly forbid them.
Note: A deny rule is absolute and propagates downstream without exception. Once an action is denied at a higher level, it becomes "locked" for all child entries and cannot be overridden by an allow rule or contained by grant: 'explicit' further down the stream. Always apply deny restrictions at the most specific level possible to avoid unintentionally locking out sub-sections.
When configuring a policy, you can target these specific actions to create granular permissions:
read: View the entry in the dashboard.
create: Create new entries of a specific type.
update: Edit existing content.
delete: Remove entries from the CMS.
publish: Push changes to the live environment.
archive: Move entries to an archived state.
reorder: Change the sorting order within a parent.
move: Relocate entries to a different parent or root.
upload: Add new media files to the library.
explore: Browse through the workspace structure.
all: A shorthand to grant every possible permission at once.
The grant property specifically modifies how allow rules are applied across the content tree. By default, permissions use grant: 'implicit', meaning access trickles down to all children.
// (Default) Permissions automatically trickle down to all child entries and sub-pages.
grant: 'implicit'
// Permissions only apply to the specific entry or level defined.
// Access to children must be granted separately.
grant: 'explicit' In these examples, we define roles based on fixed paths or roots within your CMS. This is the most direct way to manage access for different user groups.
Viewer: This role is granted read-only access to the entire workspace by default but is specifically excluded from sensitive administrative roots such as configurations and databases.
// Block specified pages, only give read-rights
const viewer = Config.role('Viewer', {
async permissions(policy) {
policy.set(
{
allow: {read: true}
},
{
root: cms.workspaces.main.general,
deny: {read: true}
},
{
root: cms.workspaces.main.redirects,
deny: {read: true}
},
{
root: cms.workspaces.main.configurator,
deny: {read: true}
},
{
root: cms.workspaces.main.database,
deny: {read: true}
}
)
}
})Editor: Similar to the Viewer, but with the addition of editing rights (update) for all accessible sections.
// Block acces to certain roots, give read and update rights to everything else
const editor = Config.role('Editor', {
async permissions(policy) {
policy.set(
{
allow: {read: true, update: true}
},
{
root: cms.workspaces.main.general,
deny: {read: true}
},
{
root: cms.workspaces.main.redirects,
deny: {read: true}
},
{
root: cms.workspaces.main.configurator,
deny: {read: true}
},
{
root: cms.workspaces.main.database,
deny: {read: true}
}
)
}
})Sometimes you want to grant a user access to a specific item without immediately making the entire underlying tree structure visible. By using grant: 'explicit', you ensure that rights must be manually assigned at each level. This is ideal for moderators who only need to manage one specific page (like a particular bike) while keeping the rest of the navigation hidden.
const bikeId = '4d454f57636d735f323032365f7a'
// Grant explicit to make sure not all children are visible
const bikeModerator = Config.role('Bike Moderator', {
async permissions(policy) {
policy.set(
{
workspace: cms.workspaces.main,
allow: {read: true},
grant: 'explicit'
},
{
root: cms.workspaces.main.pages,
allow: {read: true},
grant: 'explicit'
},
{
id: bikeId,
allow: {all: true}
}
)
}
})Alinea offers the unique capability to base permissions on your data structure via the graph. Instead of hardcoding IDs, you can perform a query to retrieve, for example, all products belonging to a specific brand. The results of this query are then used to dynamically assign permissions. This means you don't have to update the role when new products are added to the brand; the permissions automatically grow along with your content.
bikeBrandId = '2o7I2ig8ry0ci1e0hZ5k1kKK3Qx'
// Use querying to show only specific products
const bikeBrandModerator = Config.role('Bike Brand Moderator', {
async permissions(policy, graph) {
const bikes = await graph.find({
type: ProductSchema,
filter: {
brand: {
has: {_entry: bikeBrandId}
}
}
})
policy.set({
workspace: cms.workspaces.main,
allow: {read: true},
grant: 'explicit'
},
{
root: cms.workspaces.main.products,
allow: {read: true},
grant: 'explicit'
})
for (const bike of bikes) {
policy.set({
id: bike._id,
allow: {read: true, update: true}
})
}
}
})