Upgrading

From adonis-lucid-permission

Step by step migration from adonis-lucid-permission (AdonisJS v6) to @eienjs/adonisjs-permission (AdonisJS v7).

This guide migrates an app from adonis-lucid-permission to @eienjs/adonisjs-permission.

Back up your database and work on a branch. Make sure your app is already on AdonisJS v7 (Node >= 24) before starting.

What changed

BeforeNow
withAuthorizable({ rolesPivotTable, permissionsPivotTable })HasAuthorizable mixin, no arguments
adonis-lucid-permission/services/role and /services/permissionYour own Role and Permission models using Roleable / Permissible
One pivot table per model type (user_has_roles, ...)Shared polymorphic model_has_roles and model_has_permissions
node ace permissions:pivot-tableRemoved
Config only had tableNamesModels, morph map, guards, teams, wildcard, catalog...
adonis-lucid-permission/role_middleware@eienjs/adonisjs-permission/middleware/role_middleware

1. Replace the package

npm uninstall adonis-lucid-permission
node ace add @eienjs/adonisjs-permission

When prompted, scaffold the models and middlewares, but say no to migrations (you will adapt your existing tables in step 3).

Then remove the old entries in adonisrc.ts (adonis-lucid-permission/commands and adonis-lucid-permission/permissions_provider) if they are still there.

2. Update the models

import { withAuthorizable } from 'adonis-lucid-permission'

const HasAuthorizable = withAuthorizable({
  rolesPivotTable: 'user_has_roles',
  permissionsPivotTable: 'user_has_permissions',
})

export default class User extends compose(BaseModel, HasAuthorizable) {}

Replace the old service imports with your own models:

// Before
import { Role } from 'adonis-lucid-permission/services/role'
import { Permission } from 'adonis-lucid-permission/services/permission'

// After
import Role from '#models/role'
import Permission from '#models/permission'

Register a morphMap alias for every model that has roles or permissions in config/permissions.ts:

config/permissions.ts
export default defineConfig({
  models: {
    role: () => import('#models/role'),
    permission: () => import('#models/permission'),
  },
  morphMap: {
    user: () => import('#models/user'),
  },
})

3. Migrate the database

The new package stores assignments for all models in two polymorphic tables. Create a migration that adapts your schema and copies the data. Adjust names to your project:

database/migrations/xxxx_migrate_to_eienjs_permission.ts
import { BaseSchema } from '@adonisjs/lucid/schema'

export default class extends BaseSchema {
  async up() {
    // roles / permissions: add guard column
    this.schema.alterTable('roles', (table) => {
      table.string('guard_name').notNullable().defaultTo('web')
    })
    this.schema.alterTable('permissions', (table) => {
      table.string('guard_name').notNullable().defaultTo('web')
    })

    // polymorphic pivots
    this.schema.createTable('model_has_roles', (table) => {
      table.integer('role_id').unsigned().references('id').inTable('roles').onDelete('CASCADE')
      table.string('model_type').notNullable()
      table.string('model_id').notNullable()
      table.index(['model_id', 'model_type'])
      table.index(['role_id', 'model_id', 'model_type'])
    })
    this.schema.createTable('model_has_permissions', (table) => {
      table.integer('permission_id').unsigned().references('id').inTable('permissions').onDelete('CASCADE')
      table.string('model_type').notNullable()
      table.string('model_id').notNullable()
      table.index(['model_id', 'model_type'])
      table.index(['permission_id', 'model_id', 'model_type'])
    })

    // copy data from the old pivots ("user" must match your morphMap alias)
    this.defer(async (db) => {
      const roles = await db.from('user_has_roles').select('*')
      for (const row of roles) {
        await db.table('model_has_roles').insert({
          role_id: row.role_id,
          model_type: 'user',
          model_id: String(row.user_id),
        })
      }
      const permissions = await db.from('user_has_permissions').select('*')
      for (const row of permissions) {
        await db.table('model_has_permissions').insert({
          permission_id: row.permission_id,
          model_type: 'user',
          model_id: String(row.user_id),
        })
      }
    })

    this.schema.dropTable('user_has_roles')
    this.schema.dropTable('user_has_permissions')
  }

  async down() {
    // restore the old tables if you need a rollback path
  }
}
The foreign key column names in the old pivots (role_id, user_id, ...) may differ in your app. Check your existing tables first. Column types must match your primary keys: use uuid / bigInteger instead of integer where needed. If you used custom table names, set them in tableNames instead of renaming tables.

Your role_has_permissions table is kept as it is (the default name is the same), as long as the columns are role_id and permission_id. Otherwise map them with columnNames.

node ace migration:run

4. Rename the methods

BeforeNow
role.hasAnyRoles(...)hasAnyRole(...)
user.withPermissionTo(p)user.hasPermissionTo(p) (user.can(p) is an alias)
user.canAnyPermission(...)user.hasAnyPermission(...)
revokeRole, syncRoles, assignRoleunchanged
givePermissionTo, revokePermissionTo, syncPermissionsunchanged
hasRole, hasAllRoles, getRoleNamesunchanged
hasDirectPermission, hasPermissionViaRole, getDirectPermissions, getPermissionsViaRoles, getAllPermissions, getPermissionNamesunchanged
checkPermissionTo, hasAllPermissionsunchanged

Search your project for the old names:

grep -rnE "hasAnyRoles|withPermissionTo|canAnyPermission|adonis-lucid-permission" app start config

5. Update the middlewares

In start/kernel.ts, change the import paths:

export const middleware = router.named({
  role: () => import('@eienjs/adonisjs-permission/middleware/role_middleware'),
  permission: () => import('@eienjs/adonisjs-permission/middleware/permission_middleware'),
  roleOrPermission: () => import('@eienjs/adonisjs-permission/middleware/role_or_permission_middleware'),
})

Route usage (middleware.role({ roles }), middleware.permission({ permissions }), middleware.roleOrPermission({ roleOrPermission })) is the same.

6. Verify

node ace permissions:show
node ace permissions:cache-reset

Run your test suite and confirm that roles and permissions of an existing user are still resolved:

await user.getRoleNames()
await user.getAllPermissions()

New features you can adopt