Managing permissions

This guide covers assigning permissions directly to models. You will learn how to:

  • Apply the withPermissions mixin to a User model
  • Give, sync, and revoke direct permissions
  • Combine direct permissions with role-based permissions

Overview

The recommended approach is to manage permissions through roles. However, some applications need to assign extra permissions to specific users outside of their role. For example, a user might have the "editor" role but also need the billing.refund permission that editors do not normally have.

The withPermissions mixin can be applied to any Lucid model, including your User model, to store permissions directly as a JSON column.

Setup

The model needs a permissions column and the mixin applied using compose.

  1. Create the migration

    If your users table does not already have a permissions column, generate a migration to add one.

    node ace make:migration add_permissions_to_users
  2. Define the migration

    Open the generated migration and add the permissions column. This column stores a JSON array of permission keys assigned directly to the user.

    database/migrations/xxxx_add_permissions_to_users.ts
    import { BaseSchema } from '@adonisjs/lucid/schema'
    
    export default class extends BaseSchema {
      protected tableName = 'users'
    
      async up() {
        this.schema.alterTable(this.tableName, (table) => {
          table.text('permissions').defaultTo('[]')
        })
      }
    
      async down() {
        this.schema.alterTable(this.tableName, (table) => {
          table.dropColumn('permissions')
        })
      }
    }
  3. Run the migration

    node ace migration:run
  4. Apply the mixin

    Add withPermissions to your User model alongside withRoles if you are using both.

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

Giving permissions

Use permissions.getKey() for type-safe permission keys. Duplicates are ignored automatically. Both methods persist the change immediately.

import { permissions } from '#start/permissions'

await user.givePermission(permissions.getKey('billing.refund'))
await user.givePermissions([
  permissions.getKey('billing.refund'),
  permissions.getKey('product.delete'),
])

Syncing permissions

Use syncPermissions to replace all direct permissions with a new list. Any permissions not in the provided list are removed.

await user.syncPermissions([permissions.getKey('billing.refund')])

Revoking permissions

await user.revokePermission(permissions.getKey('billing.refund'))
await user.revokePermissions([
  permissions.getKey('billing.refund'),
  permissions.getKey('product.delete'),
])

Combining with roles

When the User model has both withPermissions and withRoles, calling getPermissions() aggregates permissions from all assigned roles and direct assignments into a single deduplicated array.

// User has "editor" role (with product.create, product.update)
// User also has direct permission: billing.refund

const allPermissions = await user.getPermissions()
// ['product.create', 'product.update', 'billing.refund']
Terms & License Agreement