“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::updatereturns$user->id === $post->user_id; Laravel discovers it by name.- The controller calls
$this->authorize('update', $post); the page getscanUpdate. - Test with
assertForbidden()on the endpoint.
The posts in the setup guide 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. The screen is still the CRUD guide. The app is Inertia, a SPA, or Nuxt.
The column#
Schema::table('posts', function (Blueprint $table) {
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
});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.
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.
php artisan make:policy PostPolicy --model=Postpublic 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.
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, then The policy decides:
<Link v-if="post.user_id === $page.props.auth.user.id" :href="`/posts/${post.id}/edit`">Edit</Link><Link v-if="canUpdate" :href="`/posts/${post.id}/edit`">Edit</Link>return Inertia::render('posts/Form', [
'post' => $post->only(['id', 'title', 'excerpt']),
'canUpdate' => $request->user()?->can('update', $post) ?? false,
]);<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
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#
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.
Do I need to register a Laravel policy?
No. Laravel discovers App\\Policies\\PostPolicy for App\\Models\\Post when the names match.
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.



