cartalyst/permissions
Cartalyst Permissions is a PHP package for role-based access control, letting you define roles and permissions and check user access in Laravel applications. It provides a simple API for assigning, inheriting, and validating permissions.
What the package actually is. cartalyst/permissions is a small, framework-agnostic permission-string resolution container. It stores permission definitions, groups them into roles, and answers one question: given a set of granted permission slugs, does ability X pass? Wildcard matching (users.*, *.view, *) and inheritance are the genuinely fiddly logic worth borrowing.
Architecture fit — and the expectation gap. The description promises "Laravel integration and configuration" and role-based access control for Laravel apps. The v2.1.0 evaluation found no service provider, no facade, no publishable config, no migrations, no middleware, no Gate bridge. Treat the package as a library you compose around, not an RBAC feature you install. For a TPM this is the single most important framing correction: the delivery is not "turn on permissions," it is "adopt a resolution primitive and build the authorization layer."
Integration feasibility. Composer install is trivial and unopinionated — which cuts both ways. Zero Laravel coupling means it will never conflict with your framework version, but it also means it will never tell you it's incompatible. All compatibility signal must come from your own test suite. Realistic build-out, all owned by us:
Ability registry so abilities are constants, not string literalsroles, permissions, permission_role, role_user (+ tenant_id if scoped)Gate::before bridge so @can, authorize(), and can: middleware work unchangedexplain artisan commandThat is a spike plus roughly one to two engineer-weeks of glue and tests — not a sprint drop-in.
Technical risk.
composer.json reports NOASSERTION, and Cartalyst's historical model has been commercial/proprietary. Legal sign-off or a defensible vendoring decision must precede any code. This is the most common reason this dependency dies mid-project.Key questions to put in front of engineering and the sponsor.
spatie/laravel-permission install cover us?Stack fit. PHP 8.x (v2.1.0 is the first release declaring a PHP 8 constraint) with any Laravel version, precisely because it binds to none. It belongs in app/Authorization/ behind a gateway — never called from a controller, view, or job directly. That single discipline is what makes the eventual swap to native policies or spatie/laravel-permission a one-class change rather than a grep across 200 files.
Migration path.
if ($user->isAdmin()) checks): wrap first, move data second. Stand up the gateway and the Gate::before bridge so existing checks keep their current behavior, then migrate role/permission records behind it. Every call site should end up going through Gate / @can / the gateway. Never app(Permissions::class) inline.Compatibility.
^2.1 with a committed composer.lock; vendor the source into packages/ within a quarter and document ownership.error_reporting(E_ALL) so deprecations surface in CI, not production.Role/Permission value objects or only the evaluator — the answer changes how much schema and domain modeling we own.Sequencing (proposed milestones).
PermissionResolutionTest of [granted[], asked, expected] covering *, users.*, *.view, nested wildcards, and unknown abilities. This test is the spec — write it before a single role exists.Ability constants, config/permissions.php definitions. Bind the singleton; make abilities reviewable in one file.Ability::* constant exists in the table. Decide tenant scoping in the pivot before writing migrations.Gate::before returning true for super-admins, null for policy-backed or undefined abilities, and the gateway verdict otherwise. Avoid naive Gate::define('*', ...) — it interacts poorly with policies and authorize().php artisan permissions:explain [email protected] export.reports — prints roles, raw slugs, resolved array, and the container's verdict. Ship this at rollout, not after the first escalation.plan:pro -> export.reports, users.create). A pricing change becomes a migration + seeder, not a release.Maintenance — we own the lifecycle. Assume no upstream. Budget a fork inside the monorepo, a named owner in the runbook, and a quarterly review tied to PHP/Laravel upgrade planning. The package is small enough to read in an afternoon; that readability is the main thing we're buying.
Support surface. No vendor, no CVE feed, no community. Internal documentation and the source are the support channel. Every new engineer learns this from us, not from the internet — so the explain command and the resolution test matrix are onboarding artifacts, not just debugging aids.
Scaling. Evaluation itself is cheap: in-memory array matching against a hydrated permission set, no query per check. The cost sits in hydration and cache. Cache the resolved array per user (keyed by tenant where scoped), not query results, and invalidate on write only — on Role/Permission save, pivot attach/detach, and user role changes, via observers with Cache::forget after commit (DB::afterCommit()). Centralize in a PermissionCache::flushFor() so tinker, seeders, and queued imports can't bypass it.
Failure modes, ranked by likelihood and blast radius.
NOASSERTION surfacing in a procurement or diligence review after the code has shipped.Ramp-up. The source is a fast read; the surrounding design is what takes time. Allocate: a spike to read the source and write the resolution spec, an afternoon for any engineer on the gateway pattern, and a half-day for whoever owns the Gate bridge — that's the piece with the subtlest interaction with policies. Expect a two-to-three-week calendar window from license clearance to a trustworthy first rollout, mostly test-writing.
TPM leverage beyond the code. Even in the scenario where we don't install it, this package is a cheap forcing function: it gives product and engineering a shared vocabulary (roles vs. permissions vs. entitlements vs. plan features), it draws a deliberate line between policy storage and policy enforcement, and it surfaces the multi-tenancy and audit-trail questions that SOC 2 questionnaires will ask later. Run that conversation in the spike. If the outcome is "native Gate/Policy plus a documented entitlement table," that is a successful use of this evaluation — not a failed adoption.
How can I help you explore Laravel packages today?