Managing roles

This guide covers how to create roles, assign them to users, and check role membership. You will learn how to:

  • Set up the Role model and pivot table
  • Create roles and assign permissions to them
  • Assign, sync, and revoke roles on users
  • Aggregate permissions from a user's roles

Overview

Roles are the recommended way to manage permissions. Instead of assigning individual permissions to each user, you create roles like "editor", "admin", or "billing manager", assign permissions to those roles, and then assign roles to users. When a user's responsibilities change, you update their role assignments rather than editing individual permissions.

The permissions package provides two mixins for this workflow:

  • withPermissions goes on the Role model and adds a JSON column for storing permission strings.
  • withRoles goes on the User model and adds a many-to-many relationship to the Role model through a pivot table.

Setup

You need a Role model with a permissions column, a pivot table linking users to roles, and both mixins applied to their respective models.

  1. Create the migrations

    Run the following commands to generate migration files for the roles table and the pivot table.

    node ace make:migration roles
    node ace make:migration user_roles
  2. Define the roles table

    Open the generated roles migration and define the table schema. The permissions column stores a JSON array of permission keys assigned to the role.

    database/migrations/xxxx_create_roles.ts
    import { BaseSchema } from '@adonisjs/lucid/schema'
    
    export default class extends BaseSchema {
      protected tableName = 'roles'
    
      async up() {
        this.schema.createTable(this.tableName, (table) => {
          table.increments('id')
          table.string('name').unique().notNullable()
          table.text('permissions').defaultTo('[]')
          table.timestamp('created_at')
          table.timestamp('updated_at')
        })
      }
    
      async down() {
        this.schema.dropTable(this.tableName)
      }
    }
  3. Define the user_roles pivot table

    Open the generated user_roles migration and define the pivot table schema. The unique constraint on user_id and role_id prevents duplicate role assignments.

    database/migrations/xxxx_create_user_roles.ts
    import { BaseSchema } from '@adonisjs/lucid/schema'
    
    export default class extends BaseSchema {
      protected tableName = 'user_roles'
    
      async up() {
        this.schema.createTable(this.tableName, (table) => {
          table.increments('id')
          table.integer('user_id').notNullable().unsigned()
          table.integer('role_id').notNullable().unsigned()
          table.unique(['user_id', 'role_id'])
        })
      }
    
      async down() {
        this.schema.dropTable(this.tableName)
      }
    }
  4. Run the migrations

    node ace migration:run
  5. Apply the mixins

    Apply withPermissions to your Role model. This mixin adds methods for reading and writing the permissions JSON column.

    app/models/role.ts
    import { RoleSchema } from '#database/schema'
    import { compose } from '@adonisjs/core/helpers'
    import { withPermissions } from '@adonisplus/permissions'
    
    export default class Role extends compose(
      RoleSchema,
      withPermissions() 
    ) {}

    Apply withRoles to your User model. The config requires the Role model and the pivot table name.

    app/models/user.ts
    import { UserSchema } from '#database/schema'
    import { compose } from '@adonisjs/core/helpers'
    import { withRoles } from '@adonisplus/permissions'
    import Role from '#models/role'
    
    export default class User extends compose(
      UserSchema,
      withRoles({
        roleModel: () => Role,
        pivotTable: 'user_roles',
      })
    ) {}
roleModel
() => RoleModel

A function that returns the Role model class.

pivotTable
string

The name of the pivot table linking users to roles.

pivotForeignKey
string

The foreign key column on the pivot table for the user. Defaults to Lucid's convention (user_id).

pivotRelatedForeignKey
string

The foreign key column on the pivot table for the role. Defaults to Lucid's convention (role_id).

Creating roles with permissions

Create a role and assign permissions to it. Use permissions.getKey() for type-safe permission keys. Your editor will autocomplete the available keys and catch typos at compile time.

import { permissions } from '#start/permissions'

const editor = await Role.create({ name: 'editor' })
await editor.givePermissions([
  permissions.getKey('product.create'),
  permissions.getKey('product.update'),
])

const admin = await Role.create({ name: 'admin' })
await admin.syncPermissions(permissions.active())

For a full reference on managing the permissions column (giving, syncing, revoking), see the Managing permissions guide.

Assigning roles to users

You can pass role instances or role IDs to all assignment methods.

await user.assignRole(editorRole)
await user.assignRoles([editorRole, moderatorRole])

Syncing roles

Use syncRoles to replace all of a user's role assignments with a new list. Roles not in the provided list are removed.

await user.syncRoles([adminRole])

Revoking roles

await user.revokeRole(editorRole)
await user.revokeRoles([editorRole, moderatorRole])

Checking role membership

await user.hasRole('editor') // true or false

Getting a user's roles

const roles = await user.getRoles()

If roles are already preloaded on the model, getRoles returns them directly without an additional query.

Aggregating permissions

The getPermissions method on the user collects permissions from all assigned roles into a single deduplicated array. This is the method used by the Access class and Bouncer abilities to determine what a user can do.

const allPermissions = await user.getPermissions()
// Combines permissions from all roles
Terms & License Agreement