logo light

Implementing Row-Level Security in Filament Tables with applyRowAccessPolicy

Image Description
By: André Domingues
  • Published: 07 May 2025 Updated: 07 May 2025

Learn how to implement row-level security in Filament tables using the applyRowAccessPolicy macro that filters query results based on user permissions through Laravel's policy system.

Implementing Row-Level Security in Filament Tables with applyRowAccessPolicy

Understanding the applyRowAccessPolicy Macro

The applyRowAccessPolicy macro provides an elegant solution for implementing row-level security in Filament tables. This macro integrates with Laravel's policy system to filter table data based on user permissions.

1Table::macro('applyViewRowScope', function (\Closure | bool $condition = true): Table {
2 if ($condition instanceof \Closure) {
3 $condition = $this->evaluate($condition);
4 }
5
6 /**
7 * Store the view row scope condition in Laravel's service container.
8 *
9 * How this works:
10 * 1. We generate a unique ID for each Table instance using spl_object_hash()
11 * 2. We create a container binding with a key specific to this Table instance
12 * 3. The condition value (true/false) is stored in the container
13 *
14 * The container binding will be automatically cleaned up when the Table instance
15 * is garbage collected, as the binding key is unique to this instance.
16 */
17 $tableId = spl_object_hash($this);
18 app()->instance('filament.table.viewRowScope.' . $tableId, $condition);
19
20 return $this->modifyQueryUsing(function (\Illuminate\Database\Eloquent\Builder $query) use ($tableId): \Illuminate\Database\Eloquent\Builder {
21 // Check the container for this Table's specific viewRowScope setting
22 // Defaults to true if no binding exists (fallback for safety)
23 $shouldApply = app()->bound('filament.table.viewRowScope.' . $tableId)
24 ? app()->make('filament.table.viewRowScope.' . $tableId)
25 : true;
26
27 if (! $shouldApply) {
28 return $query;
29 }
30
31 $model = $query->getModel();
32
33 if (! $model) {
34 return $query;
35 }
36
37 $policy = \Gate::getPolicyFor($model);
38
39 if (! $policy || ! method_exists($policy, 'viewRow')) {
40 return $query;
41 }
42
43 $modifiedQuery = $policy->viewRow(user(), $query);
44
45 if (! $modifiedQuery instanceof \Illuminate\Database\Eloquent\Builder) {
46 throw new \RuntimeException("The viewRow method for " . $policy::class . " must return an instance of Illuminate\Database\Eloquent\Builder.");
47 }
48
49 return $modifiedQuery;
50 });
51 });

When applied to a table, this macro modifies the underlying query by checking for an applyRowAccessPolicy method in your model's Policy class. It then uses this method to filter records the current user is allowed to view.

How It Works

  1. The macro accepts an optional condition parameter that determines whether the policy should be applied

  2. It stores this condition in Laravel's service container with a unique identifier for each table instance

  3. It modifies the query by checking if a policy exists for the model and if it contains an applyRowAccessPolicy method

  4. If found, it calls this method, passing the current user and the query builder

  5. The policy method returns a modified query with appropriate row-level filters applied

Implementation Example

Define the method in your model's Policy:

1// PostPolicy.php
2public function applyRowAccessPolicy(User $user, Builder $query): Builder
3{
4 // Users can only see their own posts unless they're admins
5 return $user->isAdmin() ? $query : $query->where('user_id', $user->id);
6}

Apply the macro to your Filament table:

1// PostResource
2public static function table(Table $table): Table
3{
4 return $table
5 ->applyRowAccessPolicy()
6 ->columns([
7 // your columns here
8 ]);
9}

This simple implementation ensures users only see rows they're authorized to access, providing robust, reusable row-level security across your Filament application.

Back to Tricks