# Laravel policies on a Vue page

> A policy decides whether this user can update this post. The Vue page receives that boolean. The controller still calls authorize.

- URL: https://laravel-vuejs.space/laravel-vue-policies
- Author: Anass Ez-zouaine (https://laravel-vuejs.space/author/anass-ez-zouaine)
- Published: 2026-10-10
- Topics: Laravel, VueJs, Permissions

“Only the author can edit this post” sounds like one `v-if`. It is three pieces that have to agree: an owner column, a policy, and a controller that calls it. Here they are, plus the boolean that keeps the Vue link honest.

> **TL;DR**
>
> -   Add `user_id`, set it from `$request->user()`, never from the request body.
> -   `PostPolicy::update` returns `$user->id === $post->user_id`; Laravel discovers it by name.
> -   The controller calls `$this->authorize('update', $post)`; the page gets `canUpdate`.
> -   Test with `assertForbidden()` on the endpoint.
>
> **What you'll learn**
>
> -   How to add an owner column safely
> -   How to write and call a policy
> -   How to pass the same answer to the Vue page and test it
>
> **Who it's for:** Laravel + Vue developers adding per-record authorization
>
> **Applies to:** Laravel 13 · Inertia 3 · Vue 3 · Pest

The posts in the [setup guide](https://laravel-vuejs.space/application-setup) have no owner. A policy needs one. This page adds `user_id`, then allows update only for that user. A permission such as “editor” is a different question and stays in [permissions](https://laravel-vuejs.space/laravel-vue-permissions). The screen is still the [CRUD guide](https://laravel-vuejs.space/laravel-inertia-vue-crud). The app is [Inertia, a SPA, or Nuxt](https://laravel-vuejs.space/laravel-vue-in-2026).

## The column

`database/migrations/xxxx_add_user_id_to_posts_table.php`
```php
Schema::table('posts', function (Blueprint $table) {
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
});
```

`app/Models/Post.php`
```php
public function user(): BelongsTo
{
    return $this->belongsTo(User::class);
}
```

> **Gotcha: user\_id never comes from the form**
>
> Add `user_id` to `$fillable`, and set it in `store` from the logged-in user. Do not take `user_id` from the request body. The form would then let anyone assign the post to someone else.

`app/Http/Controllers/PostController.php`
```php
Post::create([
    ...$data,
    'user_id' => $request->user()->id,
    'slug' => str($data['title'])->slug(),
    'published_at' => now(),
]);
```

## The policy

Laravel discovers `PostPolicy` for `Post` when the names match. You do not register it by hand.

```bash
php artisan make:policy PostPolicy --model=Post
```

`app/Policies/PostPolicy.php`
```php
public function update(User $user, Post $post): bool
{
    return $user->id === $post->user_id;
}
```

> **Gotcha: an empty method returns false**
>
> `update` and `destroy` can share that line. `viewAny` can return `true` while the index is still a list of every post. Write the method you mean. An empty policy method that falls through to `false` hides every button and then looks like a Vue bug.

Call it from the controller. `authorize` throws 403 before the update runs.

```php
public function update(Request $request, Post $post): RedirectResponse
{
    $this->authorize('update', $post);

    // validation from the CRUD guide
}
```

Pass the same answer to the page. The link and the endpoint then agree.

**The page guesses:**

`resources/js/pages/posts/Form.vue`
```vue
<Link v-if="post.user_id === $page.props.auth.user.id" :href="`/posts/${post.id}/edit`">Edit</Link>
```

**The policy decides:**

`resources/js/pages/posts/Form.vue`
```vue
<Link v-if="canUpdate" :href="`/posts/${post.id}/edit`">Edit</Link>
```

```php
return Inertia::render('posts/Form', [
    'post' => $post->only(['id', 'title', 'excerpt']),
    'canUpdate' => $request->user()?->can('update', $post) ?? false,
]);
```

```vue
<Link v-if="canUpdate" :href="`/posts/${post.id}/edit`">Edit</Link>
```

> **Tip: per-row booleans on the index**
>
> On the index, that boolean is per row. Compute it in the query’s map or share a list of ids the user can edit. Do not ship every user’s id to the browser and decide there.

## A test

> **Prove the endpoint is closed**
>
> `tests/Feature/PostPolicyTest.php`
> ```php
> it('forbids a user who does not own the post', function () {
>     $owner = User::factory()->create();
>     $other = User::factory()->create();
>     $post = Post::factory()->for($owner)->create();
>
>     $this->actingAs($other)
>         ->put("/posts/{$post->id}", [
>             'title' => 'Taken',
>             'excerpt' => 'Not yours.',
>         ])
>         ->assertForbidden();
> });
> ```

The factory needs `user_id` once the column exists. `assertForbidden` is the assertion that matters. A test that only checks the missing link does not close the endpoint.

## Where this stops

Spatie permissions and this policy can both exist. An editor permission can allow `update` even when the user is not the owner, inside the policy, with `$user->can('posts.update')`. Put that combination in the policy so there is still one method the controller calls. Do not check the role in the Vue page.

## Recap

- Owner column from the logged-in user, never from input.
- One policy method per ability; the controller calls it.
- The page receives the policy’s answer as a boolean.
- Combine permissions inside the policy, not in Vue.

## FAQ

**Q: How do I hide the Edit button for users who do not own a post in Inertia?**

Compute `$request->user()?->can('update', $post)` in the controller, pass it as `canUpdate`, and use `v-if`. The controller still calls `authorize`.

**Q: Do I need to register a Laravel policy?**

No. Laravel discovers `App\\Policies\\PostPolicy` for `App\\Models\\Post` when the names match.

**Q: Can an editor role override the owner check?**

Yes. Inside the policy, return true when `$user->can('posts.update')` or the user owns the post, so the controller still calls one method.

## Next steps

- [Laravel permissions on a Vue page](https://laravel-vuejs.space/laravel-vue-permissions)
- [CRUD with Inertia and Vue](https://laravel-vuejs.space/laravel-inertia-vue-crud)
- [Test a Laravel and Vue app](https://laravel-vuejs.space/test-laravel-vue)

## Sources

- [Laravel 13: Authorization, policies](https://laravel.com/docs/13.x/authorization#creating-policies)
- [Inertia v3: Authorization](https://inertiajs.com/docs/v3/security/authorization)
