Documentation Index
Fetch the complete documentation index at: https://docs.laravelshopper.dev/llms.txt
Use this file to discover all available pages before exploring further.
Tags are simple labels you attach to products for cross-cutting organization. Unlike categories (hierarchical) or collections (rule-based), tags are flat and flexible, ideal for seasonal labels (“Summer 2026”), marketing campaigns (“Flash Sale”), or storefront filtering (“New Arrival”, “Best Seller”).
Model
The model used is Shopper\Core\Models\ProductTag. Unlike products, categories, or brands, the tag model has no admin wrapper and is not configurable via config/shopper/models.php. It uses the HasSlug trait, which generates unique, URL-friendly slugs with collision handling.
Database Schema
| Column | Type | Nullable | Default | Description |
|---|
id | bigint | no | auto | Primary key |
name | string | no | - | Tag name |
slug | string | yes | auto | URL-friendly identifier (unique) |
created_at | timestamp | yes | null | Creation timestamp |
updated_at | timestamp | yes | null | Last update timestamp |
Pivot Table
Tags use the shared polymorphic product_has_relations pivot table. This is the same table used by categories, collections, channels, and related products. The polymorphic pattern lets all these models share a single pivot table instead of requiring a dedicated join table for each relationship type.
| Column | Type | Nullable | Description |
|---|
product_id | bigint | no | Foreign key to product |
productable_type | string | no | Morph type (e.g. product_tag) |
productable_id | bigint | no | Foreign key to the related model |
Slug Generation
The HasSlug trait provides automatic slug generation with collision handling. When you set the slug attribute, the trait runs the value through Str::slug() and checks for uniqueness. If a slug already exists, it appends an incrementing suffix (-1, -2, etc.) until it finds a unique one.
use Shopper\Core\Models\ProductTag;
$tag1 = ProductTag::query()->create(['name' => 'New Arrival', 'slug' => 'new-arrival']);
$tag2 = ProductTag::query()->create(['name' => 'New Arrival 2', 'slug' => 'new-arrival']);
// $tag2->slug === 'new-arrival-1' (auto-incremented to avoid collision)
The trait also provides a findBySlug() static method that looks up a tag by its slug and throws a ModelNotFoundException if not found:
$tag = ProductTag::findBySlug('summer-2026');
Relationships
Products
Tags connect to products through a morphToMany / morphedByMany polymorphic relationship via the product_has_relations table.
From the tag side:
$tag->products;
$tag->products()->count();
$tag->products()->attach([$productId1, $productId2]);
$tag->products()->detach($productId);
$tag->products()->sync($productIds);
From the product side:
$product->tags;
$product->tags()->attach([$tagId1, $tagId2]);
$product->tags()->sync($tagIds);
To create a tag, provide a name and a slug. The HasSlug trait ensures slug uniqueness automatically:
use Shopper\Core\Models\ProductTag;
$tag = ProductTag::query()->create([
'name' => 'Summer 2026',
'slug' => 'summer-2026',
]);
$tag->products()->attach([$product1->id, $product2->id]);
Tags can also be created inline from the product edit form in the admin panel. The tag selection field includes a “Create” option that opens a modal form, so administrators can add new tags without leaving the product page.
To get all tags ordered by name with their product count:
$tags = ProductTag::query()
->withCount('products')
->orderBy('name')
->get();
To find a tag by slug with its products:
$tag = ProductTag::findBySlug('summer-2026');
$products = $tag->products()->publish()->paginate(12);
To get all tags for a specific product:
Configuration
If you don’t need tags in your store, disable the feature in config/shopper/features.php:
use Shopper\Enum\FeatureState;
return [
'tag' => FeatureState::Disabled,
];
When disabled, the tag menu item is hidden from the sidebar, tag-related routes are not registered, and the tag selection field is removed from product forms.
Permissions
The admin panel generates four permissions for tag management:
| Permission | Description |
|---|
browse_tags | View the tags list |
read_tags | View a single tag |
add_tags | Create new tags |
edit_tags | Edit existing tags |
delete_tags | Delete tags |
Components
Tags are managed under the product components configuration. To customize the admin UI:
php artisan shopper:component:publish product
The tag page is registered in config/shopper/components/product.php:
return [
'pages' => [
'tag-index' => \Shopper\Livewire\Pages\Tag\Index::class,
],
];
Storefront Example
This example shows a tag listing page and a tag detail page that displays all published products for a given tag.
namespace App\Http\Controllers;
use Shopper\Core\Models\ProductTag;
use Shopper\Models\Product;
class TagController extends Controller
{
public function index()
{
$tags = ProductTag::query()
->withCount('products')
->orderBy('name')
->get();
return view('tags.index', compact('tags'));
}
public function show(string $slug)
{
$tag = ProductTag::findBySlug($slug);
$products = $tag->products()
->publish()
->paginate(12);
return view('tags.show', compact('tag', 'products'));
}
}
To get products that have a specific tag:
$products = Product::query()
->whereHas('tags', fn ($query) => $query->where('slug', 'summer-2026'))
->publish()
->get();
To get products that have any of the given tags:
$products = Product::query()
->whereHas('tags', fn ($query) => $query->whereIn('slug', ['new-arrival', 'best-seller']))
->publish()
->paginate(12);