PHPackages                             adamczykpiotr/laravel-simple-scopes-and-permissions - 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. adamczykpiotr/laravel-simple-scopes-and-permissions

ActiveLibrary

adamczykpiotr/laravel-simple-scopes-and-permissions
===================================================

Simple, explicit permission checks and row-level query scopes constraining what data a user can access

1.1.0(1mo ago)12↓66.7%MITPHPPHP ^8.3CI passing

Since Jul 18Pushed 1mo agoCompare

[ Source](https://github.com/adamczykpiotr/laravel-simple-scopes-and-permissions)[ Packagist](https://packagist.org/packages/adamczykpiotr/laravel-simple-scopes-and-permissions)[ Docs](https://github.com/adamczykpiotr/laravel-simple-scopes-and-permissions)[ GitHub Sponsors](https://github.com/AdamczykPiotr)[ RSS](/packages/adamczykpiotr-laravel-simple-scopes-and-permissions/feed)WikiDiscussions main Synced 2w ago

READMEChangelog (2)Dependencies (10)Versions (7)Used By (0)

Simple Scopes &amp; Permissions for Laravel
===========================================

[](#simple-scopes--permissions-for-laravel)

[![Latest Version on Packagist](https://camo.githubusercontent.com/2ebd5627d939daf59c9a2e2edd583787738cc61b958a451c06c02931dab29014/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6164616d637a796b70696f74722f6c61726176656c2d73696d706c652d73636f7065732d616e642d7065726d697373696f6e732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/adamczykpiotr/laravel-simple-scopes-and-permissions)[![GitHub Tests Action Status](https://camo.githubusercontent.com/f3737463deffb791f770f7fa73b0060505c778f13b615e0d37584f1155452a96/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6164616d637a796b70696f74722f6c61726176656c2d73696d706c652d73636f7065732d616e642d7065726d697373696f6e732f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/adamczykpiotr/laravel-simple-scopes-and-permissions/actions?query=workflow%3Arun-tests+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/41db7f2bcdd572b0b68549f13c70c9336b883b0bc3e9cc51f89684b6c2081824/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6164616d637a796b70696f74722f6c61726176656c2d73696d706c652d73636f7065732d616e642d7065726d697373696f6e732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/adamczykpiotr/laravel-simple-scopes-and-permissions)

Answer two questions in one place: **what may this user do**, and **which rows may they see** — with plain PHP enums, no magic, and a fail-closed guarantee.

```
// May they? (throws your 403 exception if not)
$document->requireEditable(performer: $user);

// Which rows? (query constrained to what the user may see)
Document::query()->tap(Document::applyQueryScope($user))->get();

// What should the UI show? (per-record action map for your frontend)
$document->getPermittedModelActions($user);
// ['view' => true, 'edit' => true, 'delete' => false, ...]
```

Why this package
----------------

[](#why-this-package)

Most permission packages answer "does the user have permission X?" and stop there. In real apps the harder question is row-level: a manager may edit *users*, but only users **in their own department**. This package treats that as a first-class concept:

- **Row-level scopes, enforced twice.** Every scope works as an instance check (*may they see this record?*) and as an Eloquent query constraint (*which records do we even fetch?*) — so lists and detail pages can't disagree.
- **Fails closed.** A missing or misconfigured scope yields `WHERE 1 = 0`. Mistakes hide rows; they never leak them.
- **Everything is an enum.** Actions, permissions, and scopes are string-backed enums *you* define in your app. No string tables sprinkled through the codebase, full IDE navigation, exhaustive `match` statements.
- **No magic.** No global scopes, no observers, no middleware guessing. Every check is an explicit call you can read at the call site.
- **Explicit performer.** Every check accepts the user it runs for. The "current user" fallback is a single resolver you control — which also makes impersonation trivial.
- **Frontend-friendly.** One call returns a `['action' => bool]` map per record, so the UI can enable/disable buttons without re-implementing your rules in JavaScript.

Installation
------------

[](#installation)

```
composer require adamczykpiotr/laravel-simple-scopes-and-permissions
php artisan vendor:publish --tag="simple-scopes-and-permissions-config"
```

Supports Laravel 10–13 on PHP 8.3+.

Quick start
-----------

[](#quick-start)

Wire four things in `config/simple-scopes-and-permissions.php`, then guard your models.

**1. Define your enums** — actions, permissions, and scopes are app-owned string enums implementing the package contracts:

```
enum Action: string implements \AdamczykPiotr\SimpleScopesAndPermissions\Contracts\Action
{
    case GENERIC_VIEW = 'view';
    case GENERIC_LIST = 'list';
    // ... + genericView()/genericList()/... accessors and modelCases()/modelClassCases()
}

enum Permission: string implements \AdamczykPiotr\SimpleScopesAndPermissions\Contracts\Permissionable
{
    case DOCUMENT_VIEW = 'permission.document.view';
    case DOCUMENT_UPDATE = 'permission.document.update';
    // ...
}

enum Scope: string implements \AdamczykPiotr\SimpleScopesAndPermissions\Contracts\ScopePermission
{
    case DOCUMENT_ALL = 'scope.document.all';
    case DOCUMENT_OWN = 'scope.document.own';
    // ... each mapping to a ScopeType (ALL / DEPARTMENT / OWN by default)
}
```

**2. Tell the package who is acting** — a resolver for when no explicit performer is passed:

```
class AuthenticatedPerformerResolver implements PerformerResolver
{
    public function resolve(): Performer
    {
        return auth()->user() ?? throw new AuthenticationException();
    }
}
```

**3. Make your user model a performer:**

```
class User extends Authenticatable implements Performer
{
    use HasUserPermissions; // combines role permissions + direct per-user grants
}
```

**4. Guard your models** with three small hooks:

```
class Document extends Model
{
    use HasPermittedActions, HasPermittedScopes;

    public function providePermittedActions(Action $action, User $performer): ?bool
    {
        return match ($action) {
            Action::GENERIC_LIST => $performer->hasModelPermission(Permission::DOCUMENT_VIEW),
            Action::GENERIC_VIEW => $this->hasValidScope($performer)
                && $performer->hasModelPermission(Permission::DOCUMENT_VIEW),
            Action::GENERIC_EDIT => $this->hasValidScope($performer)
                && $performer->hasModelPermission(Permission::DOCUMENT_UPDATE),
            default => null,
        };
    }

    public function provideScopeVerifier(Scope $scope, User $performer): bool
    {
        return match ($scope) {
            Scope::DOCUMENT_ALL => true,
            Scope::DOCUMENT_OWN => $this->user_id === $performer->id,
            default => false,
        };
    }

    public static function provideScopeQueryModifier(Scope $scope, User $performer): ?Closure
    {
        return match ($scope) {
            Scope::DOCUMENT_ALL => fn($query) => $query,
            Scope::DOCUMENT_OWN => fn($query) => $query->where('user_id', $performer->id),
            default => null,
        };
    }
}
```

That's it. Controllers become one-liners:

```
public function index(): JsonResponse
{
    Document::requireIndexable();

    $documents = Document::query()
        ->tap(Document::applyQueryScope())
        ->get();

    return DocumentResource::collection($documents)->response();
}
```

Going further
-------------

[](#going-further)

The [integration guide](docs/INTEGRATION.md) walks through a complete real-world setup:

- [Authentication &amp; impersonation](docs/INTEGRATION.md#2-the-performer-resolver) — resolving the performer from the auth guard, and impersonating other users via a request header
- [Designing the enums](docs/INTEGRATION.md#3-the-permission-enums) — prerequisite permissions, scope inheritance, custom scope types beyond ALL/DEPARTMENT/OWN
- [Guarded models](docs/INTEGRATION.md#5-a-guarded-scoped-model) — a department-tree scope, soft delete rules, models with actions but no scoping
- [Returning permissions to the frontend](docs/INTEGRATION.md#7-returning-permissions-to-the-frontend)— per-record action maps in API resources and a class-level action map for menus and buttons
- [Scoped validation rules](docs/INTEGRATION.md#6-enforcement-in-controllers) — stopping users from referencing records they can't see

Testing
-------

[](#testing)

```
composer test
composer analyse
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance90

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity53

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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

2

Last Release

46d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/20736260?v=4)[Piotr Adamczyk](/maintainers/adamczykpiotr)[@adamczykpiotr](https://github.com/adamczykpiotr)

---

Top Contributors

[![adamczykpiotr](https://avatars.githubusercontent.com/u/20736260?v=4)](https://github.com/adamczykpiotr "adamczykpiotr (5 commits)")

---

Tags

laraveladamczykpiotrsimple-scopes-and-permissions

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/adamczykpiotr-laravel-simple-scopes-and-permissions/health.svg)

```
[![Health](https://phpackages.com/badges/adamczykpiotr-laravel-simple-scopes-and-permissions/health.svg)](https://phpackages.com/packages/adamczykpiotr-laravel-simple-scopes-and-permissions)
```

###  Alternatives

[dedoc/scramble

Automatic generation of API documentation for Laravel applications.

2.2k14.2M151](/packages/dedoc-scramble)[spatie/laravel-pdf

Create PDFs in Laravel apps

1.0k6.1M53](/packages/spatie-laravel-pdf)[codewithdennis/filament-select-tree

The multi-level select field enables you to make single selections from a predefined list of options that are organized into multiple levels or depths.

331634.0k38](/packages/codewithdennis-filament-select-tree)[harris21/laravel-fuse

Circuit breaker for Laravel queue jobs. Protect your workers from cascading failures.

24795.1k](/packages/harris21-laravel-fuse)[vormkracht10/laravel-mails

Laravel Mails can collect everything you might want to track about the mails that has been sent by your Laravel app.

25263.1k](/packages/vormkracht10-laravel-mails)[rawilk/profile-filament-plugin

Profile &amp; MFA starter kit for filament.

3915.5k](/packages/rawilk-profile-filament-plugin)

PHPackages © 2026

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