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.
Brands represent manufacturers or labels for your products. They help customers identify and filter products from their favorite manufacturers, and they give your storefront structured navigation (e.g., a “Shop by Brand” page or a brand filter in search results).
Model
The model used is Shopper\Models\Brand, which extends Shopper\Core\Models\Brand. The core model provides the business logic, relationships, scopes, and slug generation. The admin model adds media collections and conversions through Spatie MediaLibrary.
The core model implements Shopper\Core\Models\Contracts\Brand and uses the HasSlug trait for automatic slug generation with collision handling, and the HasMediaCollections trait for config-driven media support.
Extending the Model
To add custom behavior, extend the admin model and update your configuration:
namespace App\Models;
use Shopper\Models\Brand as ShopperBrand;
class Brand extends ShopperBrand
{
}
Update config/shopper/models.php:
return [
'brand' => \App\Models\Brand::class,
];
Database Schema
| Column | Type | Nullable | Default | Description |
|---|
id | bigint | no | auto | Primary key |
name | string | no | - | Brand name |
slug | string | yes | auto | URL-friendly identifier (unique) |
website | string | yes | null | Brand’s official website URL |
description | longtext | yes | null | Brand description (supports rich text) |
position | smallint (unsigned) | no | 0 | Display order position |
is_enabled | boolean | no | false | Brand visibility status |
seo_title | string(60) | yes | null | SEO meta title |
seo_description | string(160) | yes | null | SEO meta description |
metadata | jsonb | yes | null | Additional custom data |
created_at | timestamp | yes | null | Creation timestamp |
updated_at | timestamp | yes | null | Last update timestamp |
Slug Generation
The HasSlug trait generates unique slugs automatically. When you set the slug attribute, the trait runs it through Str::slug() and appends an incrementing suffix (-1, -2, etc.) if a collision is detected.
The trait also provides a findBySlug() static method:
use Shopper\Models\Brand;
$brand = Brand::findBySlug('nike');
Relationships
Products
A brand has many products. This is a standard HasMany relationship using the configured product model.
$brand->products;
$brand->products()->count();
To get only published products for a brand (for storefront display):
$brand->products()->publish()->get();
With eager loading:
$brands = Brand::query()->with('products')->get();
Query Scopes
Enabled Brands
The enabled scope filters brands where is_enabled is true. Use it for all storefront queries to exclude draft or hidden brands.
use Shopper\Models\Brand;
Brand::query()->enabled()->orderBy('position')->get();
Status Management
The updateStatus() method provides a convenient way to toggle brand visibility:
$brand->updateStatus(true); // enable
$brand->updateStatus(false); // disable
You can also update the field directly:
$brand->update(['is_enabled' => true]);
Brands support two media collections through Spatie MediaLibrary, using the same config-driven collection names as products.
| Collection | Config key | Behavior | Description |
|---|
| Default gallery | shopper.media.storage.collection_name | Multiple files | Brand images |
| Thumbnail | shopper.media.storage.thumbnail_collection | Single file | Brand logo |
To add a brand logo:
$collection = config('shopper.media.storage.thumbnail_collection');
$brand->addMedia($file)->toMediaCollection($collection);
To retrieve the logo URL with a specific conversion:
$collection = config('shopper.media.storage.thumbnail_collection');
$url = $brand->getFirstMediaUrl($collection, 'medium');
From a URL:
$collection = config('shopper.media.storage.thumbnail_collection');
$brand->addMediaFromUrl('https://example.com/nike-logo.png')
->toMediaCollection($collection);
Creating Brands
To create a brand with a logo and SEO metadata:
use Shopper\Models\Brand;
$brand = Brand::query()->create([
'name' => 'Nike',
'slug' => 'nike',
'website' => 'https://nike.com',
'description' => 'Just Do It',
'is_enabled' => true,
'position' => 1,
'seo_title' => 'Nike - Official Products',
'seo_description' => 'Shop official Nike products including shoes, clothing, and accessories.',
]);
$collection = config('shopper.media.storage.thumbnail_collection');
$brand->addMedia($logoFile)->toMediaCollection($collection);
Retrieving Brands
To get all enabled brands ordered by position:
Brand::query()->enabled()->orderBy('position')->get();
To get brands that have at least one product, with product counts:
$brands = Brand::query()
->enabled()
->has('products')
->withCount('products')
->get();
To find a brand by slug:
$brand = Brand::findBySlug('nike');
For navigation or filter dropdowns where you only need name and slug:
$brands = Brand::query()
->enabled()
->select(['id', 'name', 'slug'])
->orderBy('name')
->get();
The metadata JSON column lets you store additional custom data that doesn’t warrant its own database column:
$brand->update([
'metadata' => [
'country_of_origin' => 'USA',
'founded_year' => 1964,
'social' => [
'instagram' => '@nike',
'twitter' => '@nike',
],
],
]);
$country = $brand->metadata['country_of_origin'] ?? null;
Configuration
Disabling Brands
If you don’t need brands in your store, disable the feature in config/shopper/features.php:
use Shopper\Enum\FeatureState;
return [
'brand' => FeatureState::Disabled,
];
When disabled, the brand menu item is hidden from the sidebar, brand-related routes are not registered, and brand selection is removed from product forms.
Permissions
The admin panel generates four permissions for brand management:
| Permission | Description |
|---|
browse_brands | View the brands list |
read_brands | View a single brand |
add_brands | Create new brands |
edit_brands | Edit existing brands |
delete_brands | Delete brands |
Components
To customize the admin UI for brand management:
php artisan shopper:component:publish brand
This creates config/shopper/components/brand.php:
use Shopper\Livewire;
return [
'pages' => [
'brand-index' => Livewire\Pages\Brand\Index::class,
],
'components' => [
'slide-overs.brand-form' => Livewire\SlideOvers\BrandForm::class,
],
];
Storefront Example
This example shows a brand listing page and a brand detail page with its published products.
namespace App\Http\Controllers;
use Shopper\Models\Brand;
class BrandController extends Controller
{
public function index()
{
$brands = Brand::query()
->enabled()
->withCount('products')
->orderBy('position')
->get();
return view('brands.index', compact('brands'));
}
public function show(string $slug)
{
$brand = Brand::findBySlug($slug);
$products = $brand->products()
->publish()
->paginate(12);
return view('brands.show', compact('brand', 'products'));
}
}
View Composer
For brands that appear in multiple views (header, footer, filters), use a View Composer to avoid repeating the query:
namespace App\View\Composers;
use Illuminate\View\View;
use Shopper\Models\Brand;
class BrandsComposer
{
public function compose(View $view): void
{
$view->with('brands', Brand::query()
->enabled()
->orderBy('position')
->take(12)
->get()
);
}
}
Register it in your AppServiceProvider:
use Illuminate\Support\Facades\View;
public function boot(): void
{
View::composer(['partials.brands', 'filters.brands'], BrandsComposer::class);
}