Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/alineacms/alinea/llms.txt

Use this file to discover all available pages before exploring further.

Alinea ships with a built-in role-based access control system. Roles are defined alongside your CMS configuration and describe exactly what each class of editor can do — down to individual fields. The admin role is always present and grants full access; any additional roles you define stack on top of it for your specific use cases.

Defining a role

Use Config.role to create a role. It takes a display label and an options object whose permissions function receives a WriteablePolicy that you populate with policy.set(...) calls.
cms.config.ts
import {Config} from 'alinea'
import {createCMS} from 'alinea/next'

const editor = Config.role('Editor', {
  permissions(policy) {
    policy.set(
      // Grant read access to the whole primary workspace, but require
      // explicit permission for anything deeper
      {
        workspace: cms.workspaces.primary,
        allow: {read: true},
        grant: 'explicit'
      },
      // Grant read access to the fields root explicitly
      {
        root: cms.workspaces.primary.fields,
        allow: {read: true},
        grant: 'explicit'
      },
      // Full access to one specific entry by ID
      {
        id: '2dgfSWKFaEqxaimsO32A1sR9iMw',
        allow: {all: true}
      },
      // Make a specific field read-only for this role
      {
        field: schema.FieldPermissions.readOnlyByRole,
        deny: {update: true}
      },
      // Completely hide a field from this role
      {
        field: schema.FieldPermissions.hiddenByRole,
        deny: {read: true}
      }
    )
  }
})

export const cms = createCMS({
  schema,
  roles: {editor},
  workspaces: { /* ... */ }
})
The roles object is passed directly to createCMS. Alinea automatically includes the built-in admin role — you don’t need to add it yourself.

The Permissions interface

Every permission target uses an allow and/or deny map whose keys come from the Permissions interface.
PermissionControls
readWhether the editor can see this resource
createWhether the editor can create new entries
updateWhether the editor can edit existing entries or field values
deleteWhether the editor can delete entries
reorderWhether the editor can drag entries to a new position
moveWhether the editor can move entries between roots
publishWhether the editor can publish drafts
archiveWhether the editor can archive entries
uploadWhether the editor can upload media files
exploreWhether the editor can browse the media library
allShorthand that covers every permission above
All values are booleans.
Partial<Permissions> shape
{
  create?: boolean
  read?: boolean
  update?: boolean
  delete?: boolean
  reorder?: boolean
  move?: boolean
  publish?: boolean
  archive?: boolean
  upload?: boolean
  explore?: boolean
  all?: boolean
}

Targeting permissions with PermissionInput

Each call to policy.set() takes one or more PermissionInput objects. A PermissionInput specifies where the permission applies and what is allowed or denied. Permissions can be scoped to any of the following targets:
KeyTypeDescription
workspaceWorkspaceApplies to a whole workspace
rootRootApplies to a specific root
typeTypeApplies to all entries of a type
fieldFieldApplies to a single field
idstringApplies to one entry by its ID
localestring | nullApplies to entries in a specific locale
(none)Global — applies at the root policy level
Each target also accepts allow and deny maps and an optional grant strategy.

The grant strategy

grant
'inherit' | 'explicit'
default:"'inherit'"
Controls how higher-level permissions interact with this target.
  • inherit (default) — a permission granted at a higher level (e.g. workspace) is sufficient for this target too.
  • explicit — a permission must be set directly on this target; inherited permissions from a parent scope are ignored.
Use explicit when you want to lock down a resource and only open up individual sub-resources by hand.
Using grant: 'explicit' to lock down a workspace
policy.set(
  // Editors can read the workspace, but nothing inside is accessible
  // unless explicitly granted
  {
    workspace: cms.workspaces.primary,
    allow: {read: true},
    grant: 'explicit'
  },
  // Only this root gets read access
  {
    root: cms.workspaces.primary.pages,
    allow: {read: true, create: true, update: true}
  }
)

Field-level permissions

The most granular level of control is the individual field. Denying update on a field makes it read-only in the editor UI. Denying read hides it completely.
apps/dev/src/schema/example/FieldPermissions.tsx
import {Config, Field} from 'alinea'

export const FieldPermissions = Config.document('Field permissions', {
  fields: {
    editable: Field.text('Editable field', {
      help: 'Editor should be able to read and edit this field'
    }),
    readOnlyByRole: Field.text('Read-only by role', {
      help: 'Editor should see this field but not be able to edit it'
    }),
    hiddenByRole: Field.text('Hidden by role', {
      help: 'Editor should not see this field'
    })
  }
})
In the role definition, reference the field directly:
Field-level deny rules
policy.set(
  // This field renders as read-only for editors
  {
    field: schema.FieldPermissions.readOnlyByRole,
    deny: {update: true}
  },
  // This field is not visible to editors at all
  {
    field: schema.FieldPermissions.hiddenByRole,
    deny: {read: true}
  }
)
Field-level permission restrictions are enforced in the dashboard UI. They do not prevent API reads of the underlying data — use server-side guards for sensitive data security.

Config.role options

label
string
required
The display name of the role as it appears in the dashboard’s user management interface.
description
string
A short description explaining what this role is for. Shown in the dashboard alongside the role name.
permissions
(policy: WriteablePolicy, graph: Graph) => void | Promise<void>
required
A function that receives a mutable WriteablePolicy and (optionally) a Graph instance for data-driven permission rules. Call policy.set(...) with one or more PermissionInput objects, or policy.allowAll() to grant everything.

Registering roles in createCMS

roles
Record<string, Role>
A map of role keys to Role objects created with Config.role. The admin role is always included automatically. Users are assigned roles in the dashboard’s user management section.
Registering multiple roles
export const cms = createCMS({
  schema,
  roles: {
    editor,
    reviewer
  },
  workspaces: { /* ... */ }
})

The built-in admin role

Alinea always adds an admin role that calls policy.allowAll() internally. Admins can do everything without restriction. This role cannot be removed or overridden.

Build docs developers (and LLMs) love