Here’s a clean way to implement tags for posts so that clicking on a tag shows all posts with that tag:
Create a Tag model and migration
php artisan make:model Tag -m
Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->timestamps();
});
Now, create a pivot table for posts and tags
php artisan make:migration create_post_tag_table --create=post_tag
Schema::create('post_tag', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id')->constrained()->onDelete('cascade');
$table->foreignId('tag_id')->constrained()->onDelete('cascade');
});
php artisan migrate
2. Define relationships in models
In Post.php
public function tags()
{
return $this->belongsToMany(Tag::class);
}
In Tag.php:
public function posts()
{
return $this->belongsToMany(Post::class);
}
3. Update your Post form (create/edit)
Add a multi-select field for tags (assuming you already seed tags or create them elsewhere):
@extends('adminlayouts.app')
@section('content')
<div class="container mt-5">
<h2>Create New Post</h2>
@if($tags->isEmpty())
<div class="alert alert-warning">
No tags available. Please create some tags first.
</div>
@endif
<form action="{{ route('posts.store') }}" method="POST">
@csrf
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" name="title" id="title" class="form-control" required>
</div>
<div class="mb-3">
<label for="content" class="form-label">Content</label>
<textarea name="content" id="content" rows="5" class="form-control" required></textarea>
</div>
<div class="mb-3">
<label for="tags" class="form-label">Tags</label>
<select name="tags[]" id="tags" class="form-control" multiple
{{ $tags->isEmpty() ? 'disabled' : '' }}>
@foreach($tags as $tag)
<option value="{{ $tag->id }}">{{ $tag->name }}</option>
@endforeach
</select>
@if($tags->isEmpty())
<small class="text-danger">No tags available to select</small>
@endif
</div>
<button type="submit" class="btn btn-primary" {{ $tags->isEmpty() ? 'disabled' : '' }}>
Create Post
</button>
</form>
</div>
@endsection
Store tags with posts
In your PostController@store:
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$posts = Post::all();
return view('posts.index', compact('posts'));
}
public function dashboard()
{
return view('dashboard');
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
// Get all tags to display in the form
$tags = \App\Models\Tag::all();
return view('posts.create', compact('tags'));
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
// Validate the incoming request data
$validatedData = $request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string',
// Tags validation:
// - Must be an array (when using multiple select)
// - Each tag ID must exist in the tags table
'tags' => 'array',
'tags.*' => 'exists:tags,id',
]);
// Create the post with basic fields
$post = Post::create([
'title' => $validatedData['title'],
'content' => $validatedData['content']
]);
// Attach tags to the post if any were selected
// This handles the many-to-many relationship between posts and tags
if ($request->has('tags')) {
$post->tags()->attach($validatedData['tags']);
}
return redirect()->route('posts.index')->with('success', 'Post created successfully.');
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
//
}
}
. Show tags in post views
In posts/index.blade.php or posts/show.blade.php:
@extends('userlayouts.app')
@section('content')
<br>
<br>
<div class="container">
@foreach ($posts as $post)
<h2>{{ $post->title }}</h2>
<p>{{ $post->content }}</p>
{{--
TAG DISPLAY SECTION
------------------
This section displays all tags associated with the current post
- We loop through $post->tags (using the many-to-many relationship)
- Each tag is displayed as a clickable badge/link
- The link routes to a tag details page (tags.show)
- The badge uses Bootstrap's bg-primary class for styling
--}}
<div class="mt-2 mb-3">
@foreach($post->tags as $tag)
<a href="{{ route('tags.show', $tag->id) }}" class="badge bg-primary text-decoration-none me-1">
{{ $tag->name }}
</a>
@endforeach
</div>
<hr>
@endforeach
</div>
@endsection
Tag controller to show all posts with a tag
php artisan make:controller TagController
public function show(Tag $tag)
{
$posts = $tag->posts()->latest()->get();
return view('tags.show', compact('tag', 'posts'));
}
Add route
Route::get('/tags/{tag}', [TagController::class, 'show'])->name('tags.show');
View for tags (resources/views/tags/show.blade.php)
<x-app-layout>
<div class="container mt-5">
<h2>Posts tagged with: {{ $tag->name }}</h2>
<ul class="list-group mt-3">
@forelse($posts as $post)
<li class="list-group-item">
<a href="{{ route('posts.show', $post) }}">{{ $post->title }}</a>
</li>
@empty
<li class="list-group-item">No posts found for this tag.</li>
@endforelse
</ul>
</div>
</x-app-layout>
