givanov95 / laravel-roles-permissions-crud
Admin CRUD for spatie/laravel-permission roles & permissions on Laravel + Inertia, with a matching Vue 3 frontend (@givanov95/vue-roles-permissions-crud).
Package info
github.com/givanov95/laravel-roles-permissions-crud
pkg:composer/givanov95/laravel-roles-permissions-crud
Requires
- php: ^8.3
- illuminate/contracts: ^11.0|^12.0|^13.0
- illuminate/http: ^11.0|^12.0|^13.0
- illuminate/routing: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
- illuminate/validation: ^11.0|^12.0|^13.0
- inertiajs/inertia-laravel: ^3.0
- spatie/laravel-permission: ^6.0|^7.0|^8.0
Requires (Dev)
- givanov95/laravel-git-hooks: ^1.1
- larastan/larastan: ^3.0
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^11.0|^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Admin CRUD for spatie/laravel-permission roles & permissions on Laravel + Inertia, with a matching Vue 3 frontend.
The PHP package (givanov95/laravel-roles-permissions-crud) ships the controllers,
form requests and a route macro. The npm package (@givanov95/vue-roles-permissions-crud)
ships the Vue components you drop into thin page shells inside your own admin layout.
Why thin shells?
Full Inertia pages cannot be resolved from a package (the Inertia resolver only globs
the host app's resources/js/pages). So — exactly like @givanov95/vue-data-table —
the package ships components; each app keeps a ~10-line page shell that wraps the
component in its own layout. The shared logic lives in the package; only the layout
stays local.
Install
composer require givanov95/laravel-roles-permissions-crud npm install @givanov95/vue-roles-permissions-crud
Local development (path / file: link)
To work on the package alongside an application, link both from the application instead of installing the released versions.
composer.json:
"repositories": [ { "type": "path", "url": "../laravel-roles-permissions-crud" } ], "require": { "givanov95/laravel-roles-permissions-crud": "*" }
package.json:
"dependencies": { "@givanov95/vue-roles-permissions-crud": "file:../laravel-roles-permissions-crud" }
Vite must bundle the package source (it ships .ts/.vue, not a build):
// vite.config.ts — ssr.noExternal noExternal: ['@givanov95/vue-roles-permissions-crud']
Backend wiring
-
(Optional) publish & tune the config:
php artisan vendor:publish --tag=roles-permissions-crud-config
Keys:
guard,authorize_permissions,prevent_privilege_escalation,protected_roles,protected_permissions,prefix,route_name_prefix,middleware,page_prefix. Defaults gate the admin area by four permissions (/admin/roles, route namesadmin.roles.*, Inertia pagesAdmin/Roles/*):view-rolesandview-permissionsto look,manage-rolesandmanage-permissionsto change. Seed them and grant them to your admin role; whoever edits needs both theview-*and themanage-*permission. -
Register the routes in
routes/web.php:Route::rolesPermissionsCrud();This creates
admin.roles.*andadmin.permissions.*resource routes (minusshow) under the configured prefix and middleware, with per-resourcepermission:middleware fromauthorize_permissions.
Security
What an application gets without configuring anything:
- The routes sit behind the configured
middleware(web,auth,verifiedby default). Listing needsview-roles/view-permissions; opening a form or changing anything needsmanage-roles/manage-permissions(authorize_permissions, see below). The form requests and the controllers check the same permissions themselves on every action, so routes you register by hand instead of with the macro are covered too. permissionis a middleware alias that spatie/laravel-permission does not register for you. Register it in your application (Laravel 11+:$middleware->alias(['permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class])inbootstrap/app.php), otherwise the routes cannot resolve it and requests fail.authorize_permissionstakes, per resource, a['view' => ..., 'manage' => ...]pair (the default), a string (one permission for reading and writing, as before they were split) ornull. A null part is not checked, andnulldrops the check everywhere (route middleware, form requests and controllers), which leaves onlymiddleware; do that only when you guard the routes yourself.- Nobody can hand out more than they hold (
prevent_privilege_escalation, on by default). A permission can be added to a role only by a user who holds it; a role can be opened for editing, edited or deleted only by a user who holds every permission it carries; the forms offer only the permissions the user holds, and the roles list says which roles are theirs to change (can_manage). A user counts as holding what they can do, so a super admin let through byGate::beforeholds everything. protected_roles(empty by default; list the roles your application's access depends on, e.g.['Administrator']) cannot be renamed or deleted and, while escalation is prevented, cannot lose permissions.protected_permissions(empty by default) lists permissions that cannot be renamed or deleted. The ones named inauthorize_permissionsare always protected, because deleting one would lock out whoever manages access, and so, while escalation is prevented, are the permissions a protected role holds. Add the permissions your own routes check.- A permission given to a role must exist for the configured
guard; one from another guard is a validation error.
Frontend wiring
-
Register the runtime config once in your app entrypoint:
import { RolesPermissionsPlugin, RolesPlugin, PermissionsPlugin } from '@givanov95/vue-roles-permissions-crud'; app.use(RolesPermissionsPlugin, { translator: (key, replacements) => (window as any).__(key, replacements), route: (...args) => (window as any).route(...args), routeNamePrefix: 'admin.', // must match config('roles-permissions-crud.route_name_prefix') }); // Optional global $hasRole / $can helpers: app.use(RolesPlugin).use(PermissionsPlugin);
-
Add thin page shells under
resources/js/pages/Admin/:<!-- Admin/Roles/Index.vue --> <script setup lang="ts"> import { RolesManager } from '@givanov95/vue-roles-permissions-crud'; import AdminLayout from '@/layouts/AdminLayout.vue'; defineProps<{ roles: any[] }>(); </script> <template> <AdminLayout :title="__('Roles')"> <template #header><h1 class="text-xl font-semibold text-gray-800">{{ __('Roles') }}</h1></template> <RolesManager :roles="roles" /> </AdminLayout> </template>
Components & the props each shell forwards:
Page Component Props Admin/Roles/IndexRolesManagerrolesAdmin/Roles/CreateRoleFormpermissionsAdmin/Roles/EditRoleFormrole,permissionsAdmin/Permissions/IndexPermissionsManagerpermissionsAdmin/Permissions/CreatePermissionForm— Admin/Permissions/EditPermissionFormpermission
Tests
composer test # PHPUnit on Orchestra Testbench composer analyse # PHPStan (Larastan)
TestCase switches the middleware, authorize_permissions and prevent_privilege_escalation off so the controllers can be tested on their own; DefaultsTestCase runs with the package defaults (access control, validation, protected roles, mass assignment, the permission cache). CI runs both commands on PHP 8.3 and 8.4 for every push to main and every pull request, and publishing to npm waits for it. It uses the newest Laravel that composer.json allows; Laravel 11 and 12 are not tested separately.
License
MIT © Georgi Ivanov