PHPackages                             modularize-rbac/laravel - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. modularize-rbac/laravel

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

modularize-rbac/laravel
=======================

Laravel bridge for modularize-rbac/core: Eloquent repositories, HTTP controllers, migrations, and optional Spatie permissions adapter.

v2.8.1(2w ago)030MITPHPPHP ^8.2CI passing

Since May 22Pushed 2w agoCompare

[ Source](https://github.com/chrisdjst/access-laravel)[ Packagist](https://packagist.org/packages/modularize-rbac/laravel)[ GitHub Sponsors](https://github.com/sponsors/chrisdjst)[ RSS](/packages/modularize-rbac-laravel/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (9)Dependencies (7)Versions (85)Used By (0)

modularize-rbac/laravel
=======================

[](#modularize-rbaclaravel)

Laravel bridge for [`modularize-rbac/core`](https://github.com/chrisdjst/access-core). Ships Eloquent repositories, HTTP controllers, FormRequests, migrations, an audit log pipeline, console commands, and an optional Spatie permission adapter.

[![CI](https://github.com/chrisdjst/access-laravel/actions/workflows/ci.yml/badge.svg)](https://github.com/chrisdjst/access-laravel/actions/workflows/ci.yml)[![Packagist](https://camo.githubusercontent.com/a15918000dcdb55afcc6c54a430ef5d5c77eaee77d9bef4829da4b58215f4a66/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6d6f64756c6172697a652d726261632f6c61726176656c2e737667)](https://packagist.org/packages/modularize-rbac/laravel)[![npm sdk-ts](https://camo.githubusercontent.com/30ee2c99eda528df1683e7a9262c88b34d63206a2ce896bc7ec04c3feb7a01ab/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f406d6f64756c6172697a652d726261632f73646b2d74732e7376673f6c6162656c3d2534306d6f64756c6172697a652d7262616325324673646b2d7473)](https://www.npmjs.com/package/@modularize-rbac/sdk-ts)[![npm admin-react](https://camo.githubusercontent.com/0bccd066832020d04f8dbb6c4aa2ed53b5f2ce563ec520f948497d5cc41d0846/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f406d6f64756c6172697a652d726261632f61646d696e2d72656163742e7376673f6c6162656c3d2534306d6f64756c6172697a652d7262616325324661646d696e2d7265616374)](https://www.npmjs.com/package/@modularize-rbac/admin-react)[![License: MIT](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](LICENSE)

What v2.0 ships
---------------

[](#what-v20-ships)

A drop-in admin RBAC layer with:

- **Modules** — feature catalog with hierarchy, soft-delete, sort order, i18n.
- **Roles** — guard-scoped, tenant-aware, level-ordered, system-flag protected.
- **Permissions** — `{slug}.{action}` names, package-owned (Spatie is optional).
- **Role × Module matrix** — flag-based UI translated to action names by a domain service.
- **Languages + Translations** — polymorphic translations with locale fallback.
- **REST API** — `/api/admin/modules`, `/roles`, `/languages`, `/audit`.
- **Audit log** — every domain event is auto-persisted to `access_audit_log`.
- **`HasAccessPermissions` trait** — drop on your User to make `$user->can('events.view')` work **without Spatie**.
- **`AccessAdminPolicy`** — turn-key Gate::before for the package's `admin.*` abilities.
- **Console commands** — `access:diagnose`, `access:sync-spatie`, `access:audit`.
- **Spatie integration is opt-in** — the package works whether or not `spatie/laravel-permission` is installed.

Architecture
------------

[](#architecture)

```
┌──────────────────────────────────────────────────────────────┐
│  Infrastructure (this package)                               │
│  Eloquent · Mappers · Controllers · Resources · Audit        │
│  Console commands · Policies · Spatie adapter (opt-in)       │
└──────────────────────────┬───────────────────────────────────┘
                           │ implements ports
┌──────────────────────────▼───────────────────────────────────┐
│  modularize-rbac/core (framework-agnostic, PHP 8.2+)         │
│  Use-cases · Domain entities · Ports · Events · Read models  │
└──────────────────────────────────────────────────────────────┘

```

Quickstart
----------

[](#quickstart)

From a fresh Laravel 11 / 12 host to a first authorized request in roughly five minutes.

### 1. Install

[](#1-install)

```
composer require modularize-rbac/laravel
php artisan vendor:publish --tag=access-config
php artisan migrate

# Optional: get an example seeder you can edit + run
php artisan vendor:publish --tag=access-seeder
php artisan db:seed --class=AccessSeeder
```

The seeder demonstrates the canonical flow (CreateModule → CreateRole → SyncRoleModules use-cases) and creates three modules + admin/viewer roles wired up correctly. Edit it to match your app, or read it as documentation and write your own.

### 2. Wire the User model

[](#2-wire-the-user-model)

```
// app/Models/User.php
use ModularizeRbac\Laravel\Concerns\HasAccessPermissions;

class User extends Authenticatable
{
    use HasAccessPermissions;
}
```

### 3. Seed a module, a role, and a binding

[](#3-seed-a-module-a-role-and-a-binding)

```
// database/seeders/DatabaseSeeder.php (or a tinker session)
use ModularizeRbac\Core\Application\Module\CreateModule\CreateModule;
use ModularizeRbac\Core\Application\Module\CreateModule\CreateModuleInput;
use ModularizeRbac\Core\Application\Role\CreateRole\CreateRole;
use ModularizeRbac\Core\Application\Role\CreateRoleInput;
use ModularizeRbac\Core\Application\RoleModulePermission\SyncRoleModules\SyncRoleModules;
use ModularizeRbac\Core\Application\RoleModulePermission\SyncRoleModules\SyncRoleModulesInput;

$module = app(CreateModule::class)->execute(new CreateModuleInput(
    slug: 'events',
    name: 'Events',
    redirect: '/events',
    icon: 'calendar',
    rootModuleId: null,
    sortOrder: 10,
));

$role = app(CreateRole::class)->execute(new CreateRoleInput(
    name: 'event_viewer',
    displayName: 'Event Viewer',
    guardName: 'web',
    level: 100,
));

app(SyncRoleModules::class)->execute(new SyncRoleModulesInput(
    roleId: $role->id,
    modules: [
        ['module_id' => $module->id, 'is_reading_allowed' => true],
    ],
));

DB::table('role_user')->insert([
    'role_id' => $role->id,
    'user_id' => 1,
    'organization_id' => null,
    'created_at' => now(),
    'updated_at' => now(),
]);
```

### 4. Use it

[](#4-use-it)

```
// In any controller / Gate / Blade
if ($request->user()->can('events.view')) {
    // ✓ allowed via role_user → role_module_permission → module
}
```

### 5. (Optional) Hit the admin API

[](#5-optional-hit-the-admin-api)

The admin REST surface lives under `config('access.route_prefix')` (default `api/admin`). With a bearer token whose User has `admin.modules.view`:

```
curl -H "Authorization: Bearer $TOKEN" https://app.test/api/admin/modules
```

That's the full path. The rest of this README is configuration knobs, the full REST table, and architecture details.

Install
-------

[](#install)

```
composer require modularize-rbac/laravel
php artisan vendor:publish --tag=access-config
php artisan migrate
```

Edit `config/access.php` and point `tenant_model` at your tenant class or leave `null` for single-tenant setups.

Host wiring
-----------

[](#host-wiring)

### `config/auth.php`

[](#configauthphp)

Define the `admin` guard the package defaults to:

```
'guards' => [
    'admin' => [
        'driver' => 'sanctum',
        'provider' => 'admin_users',
    ],
],
```

### `HasAccessPermissions` on your User

[](#hasaccesspermissions-on-your-user)

```
use ModularizeRbac\Laravel\Concerns\HasAccessPermissions;

class User extends Authenticatable
{
    use HasAccessPermissions;
}
```

Provides:

- `$user->rbacRoles()` BelongsToMany via the `role_user` pivot
- `$user->canAccess('events.view')` — direct lookup against the package schema

The `AccessServiceProvider` registers `Gate::before` so `$user->can('events.view')` works through Laravel's normal authorization flow.

### Tenant context (optional)

[](#tenant-context-optional)

Multi-tenant hosts bind the current tenant id in the container from their tenant-resolution middleware:

```
$app->instance('access.current_tenant_id', (string) $request->user()->organization_id);
```

`TenantContext::currentTenantId()` reads this value. Single-tenant hosts never bind the key.

### Spatie integration (optional)

[](#spatie-integration-optional)

`spatie/laravel-permission` is in `suggest` since v2.0. Install it alongside if you want `role_has_permissions` kept in sync (so Spatie's `HasRoles` trait keeps working on a different User model):

```
composer require spatie/laravel-permission
```

```
// config/access.php
'spatie' => [
    'enabled' => null, // null = auto, true = force on, false = force off
],
```

REST API
--------

[](#rest-api)

All routes under `config('access.route_prefix')` (default `api/admin`):

MethodURLActionGET/modulesList modulesPOST/modulesCreateGET/modules/{id}ShowPUT/modules/{id}UpdateDELETE/modules/{id}Soft deleteGET/rolesList rolesGET/roles/{id}Show + matrixPUT/roles/{id}Update display\_name + translationsPUT/roles/{id}/modulesSync the role's permission matrixGET/languagesListPOST/languagesCreateGET/languages/{id}ShowPUT/languages/{id}UpdateDELETE/languages/{id}Delete (rejects default)PUT/languages/{id}/defaultMark as default**GET****/audit****List audit entries (`?event=&actor_id=&tenant_id=&since=&until=&limit=&offset=`)**Frontend &amp; SDK
------------------

[](#frontend--sdk)

The bridge ships an `openapi.json` at the repo root that is the source of truth for two companion npm packages and a Postman collection.

### TypeScript SDK — `@modularize-rbac/sdk-ts`

[](#typescript-sdk--modularize-rbacsdk-ts)

Spec-derived types + a thin `openapi-fetch` wrapper. Zero runtime cost when imported type-only.

```
npm i @modularize-rbac/sdk-ts
```

```
import { createClient } from '@modularize-rbac/sdk-ts';

const client = createClient({
  baseUrl: 'https://app.test/api/admin',
  headers: { Authorization: `Bearer ${token}` },
});

const { data, error } = await client.GET('/roles', {
  params: { query: { guard: 'admin', limit: 25 } },
});
```

Or use types only:

```
import type { paths, components } from '@modularize-rbac/sdk-ts';
type Role = components['schemas']['Role'];
```

### React admin components — `@modularize-rbac/admin-react`

[](#react-admin-components--modularize-rbacadmin-react)

Drop-in admin UI built on Radix Themes + React Query: ``, ``, ``, ``, ``. Each component renders against the same `openapi.json` so they always match the API the bridge exposes.

```
npm i @modularize-rbac/admin-react @modularize-rbac/sdk-ts @tanstack/react-query @radix-ui/themes
```

```
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Theme } from '@radix-ui/themes';
import { createClient } from '@modularize-rbac/sdk-ts';
import { RbacProvider, RolesPage } from '@modularize-rbac/admin-react';
import '@radix-ui/themes/styles.css';

const queryClient = new QueryClient();
const apiClient = createClient({ baseUrl: '/api/admin' });

export default function AdminApp() {
  return (

  );
}
```

Storybook with mock data lives in [`frontend/`](frontend/) — every component has a story.

### Postman collection

[](#postman-collection)

Regenerated from the same spec, committed at [`postman.json`](postman.json). Drag it into Postman or Insomnia to get all endpoints with example bodies. The `sdk-ts-drift` CI gate keeps both the TS types and the Postman collection in lockstep with `openapi.json`.

Console commands
----------------

[](#console-commands)

- `php artisan access:diagnose` — pre-deploy health check.
- `php artisan access:sync-spatie [--dry-run]` — force resync of every role-module binding into Spatie's pivot.
- `php artisan access:audit [--event= --actor= --tenant= --since= --until= --limit= --format=table|json]` — query the audit log.

Authorization model
-------------------

[](#authorization-model)

Two layers:

1. **User layer** — `Gate::before` (registered by the ServiceProvider) calls `$user->canAccess($ability)` when the User has the `HasAccessPermissions` trait. Resolves `events.view`-style abilities directly from `role_user` + `role_module_permission` + `module_permissions`.
2. **Admin layer** — `AccessAdminPolicy` (the default `config('access.policies.admin')`) wraps the same `canAccess()` check but scoped to `admin.*` abilities the package's use-cases consult (`admin.modules.view`, `admin.audit.view`, ...). Hosts override via config.

To grant `admin.modules.view`, create a module with slug `admin.modules`, bind it to a role with `is_reading_allowed = true`, and assign the role to the user via `role_user`.

Calling use-cases directly
--------------------------

[](#calling-use-cases-directly)

Every use-case is container-resolvable:

```
use ModularizeRbac\Core\Application\Module\CreateModule\CreateModule;
use ModularizeRbac\Core\Application\Module\CreateModule\CreateModuleInput;

$module = app(CreateModule::class)->execute(new CreateModuleInput(
    slug: 'billing',
    name: 'Billing',
    redirect: '/billing',
    icon: 'receipt',
    rootModuleId: null,
    sortOrder: 10,
));
```

Telemetry recipes
-----------------

[](#telemetry-recipes)

The package dispatches two Laravel events for hosts that want observability hooks without patching the bridge:

- `ModularizeRbac\Laravel\Events\Telemetry\AbilityResolved` — fires at the end of every `$user->can(...)` call with `ability`, `allowed`, `source` (`direct|ancestor|inheritance|none|malformed`), and `durationMicros`.
- `ModularizeRbac\Laravel\Events\Telemetry\CacheLookup` — fires on every read through the language + module read-cache decorators with `namespace`, `key`, `hit`, and `version`.

Listener exceptions are caught by the package, so a faulty telemetry listener can't break authorization or cache reads.

### Sentry spans

[](#sentry-spans)

```
// app/Providers/EventServiceProvider.php
use ModularizeRbac\Laravel\Events\Telemetry\AbilityResolved;
use Sentry\State\Scope;

Event::listen(AbilityResolved::class, function (AbilityResolved $e): void {
    \Sentry\configureScope(function (Scope $scope) use ($e): void {
        $scope->setExtra('rbac.ability', $e->ability);
        $scope->setExtra('rbac.source', $e->source);
        $scope->setExtra('rbac.duration_us', $e->durationMicros);
    });
    if ($e->durationMicros > 10_000) {
        \Sentry\captureMessage('Slow access check', \Sentry\Severity::warning());
    }
});
```

### Prometheus via `spatie/laravel-prometheus`

[](#prometheus-via-spatielaravel-prometheus)

```
use ModularizeRbac\Laravel\Events\Telemetry\AbilityResolved;
use ModularizeRbac\Laravel\Events\Telemetry\CacheLookup;
use Spatie\Prometheus\Facades\Prometheus;

Event::listen(AbilityResolved::class, function (AbilityResolved $e): void {
    Prometheus::addHistogram('access_check_duration_us')
        ->labels(['source', 'allowed'])
        ->observe($e->durationMicros, [$e->source, $e->allowed ? '1' : '0']);
});

Event::listen(CacheLookup::class, function (CacheLookup $e): void {
    Prometheus::addCounter('access_cache_lookups_total')
        ->labels(['namespace', 'hit'])
        ->incBy(1, [$e->namespace, $e->hit ? '1' : '0']);
});
```

### Structured JSON log (Logstash / OpenSearch)

[](#structured-json-log-logstash--opensearch)

```
use Illuminate\Support\Facades\Log;
use ModularizeRbac\Laravel\Events\Telemetry\AbilityResolved;

Event::listen(AbilityResolved::class, function (AbilityResolved $e): void {
    Log::channel('telemetry')->info('rbac.ability.resolved', [
        'ability' => $e->ability,
        'allowed' => $e->allowed,
        'source' => $e->source,
        'duration_us' => $e->durationMicros,
    ]);
});
```

### Audit log failure level

[](#audit-log-failure-level)

The audit listener catches persistence failures (DB down, encoding quirk) so the main domain flow always completes. The level at which those failures land in the Laravel log is configurable:

```
// config/access.php
'audit' => [
    'enabled' => true,
    'log_failures' => 'error',  // warning (default) | error | critical | false
],
```

Set to `false` to swallow the failure silently for hosts that already trap audit issues upstream.

Upgrading
---------

[](#upgrading)

- [UPGRADING.md](./UPGRADING.md) — consolidated upgrade guide for v2.0 → v2.1, v1.x → v2.0, and `casamento/rbac` → v1.0.
- [CHANGELOG.md](./CHANGELOG.md) — full history with all additive changes and bugfixes.

Layout
------

[](#layout)

```
.
├── composer.json
├── config/access.php
├── database/migrations/        # v2.0 schema (idempotent)
├── routes/api.php
├── src/
│   ├── AccessServiceProvider.php
│   ├── Audit/                  # AuditingListener
│   ├── Authorization/          # GateAuthorizer, AccessAdminPolicy
│   ├── Concerns/               # HasAccessPermissions trait
│   ├── Console/                # diagnose / sync-spatie / audit
│   ├── Eloquent/
│   │   ├── Mappers/            # Entity  Eloquent
│   │   └── Repositories/       # Implement core ports
│   ├── Events/                 # LaravelEventDispatcher
│   ├── Http/                   # Controllers / FormRequests / Resources
│   ├── Localization/           # LaravelLocaleResolver
│   ├── Models/                 # Persistence DTOs
│   ├── Persistence/            # Clock / IdGenerator / UnitOfWork
│   ├── Spatie/                 # Optional permission gateway
│   ├── Tenant/                 # LaravelTenantContext
│   └── Translations/           # TranslationApplier
└── tests/                      # Pest + Testbench (matrix: with/without Spatie)

```

###  Health Score

47

—

FairBetter than 93% of packages

Maintenance97

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity61

Established project with proven stability

 Bus Factor1

Top contributor holds 94.9% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

12

Last Release

15d ago

Major Versions

v1.0.0 → v2.0.02026-05-23

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/30989810?v=4)[Christopher Steffens Theilacher ](/maintainers/chrisdjst)[@chrisdjst](https://github.com/chrisdjst)

---

Top Contributors

[![chrisdjst](https://avatars.githubusercontent.com/u/30989810?v=4)](https://github.com/chrisdjst "chrisdjst (131 commits)")[![claude](https://avatars.githubusercontent.com/u/81847?v=4)](https://github.com/claude "claude (7 commits)")

---

Tags

spatielaraveli18nrolespermissionsrbacmodulesaccess-control

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/modularize-rbac-laravel/health.svg)

```
[![Health](https://phpackages.com/badges/modularize-rbac-laravel/health.svg)](https://phpackages.com/packages/modularize-rbac-laravel)
```

###  Alternatives

[spatie/laravel-permission

Permission handling for Laravel 12 and up

12.9k98.0M1.3k](/packages/spatie-laravel-permission)[santigarcor/laratrust

This package provides a flexible way to add Role-based Permissions to Laravel

2.3k5.6M46](/packages/santigarcor-laratrust)[erag/laravel-role-permission

A simple and easy-to-install role and permission management package for Laravel, supporting versions 10.x and 11.x

404.2k](/packages/erag-laravel-role-permission)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
