> ## 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

> Manage product brands with logos, SEO metadata, and storefront filtering.

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:

```php theme={null}
namespace App\Models;

use Shopper\Models\Brand as ShopperBrand;

class Brand extends ShopperBrand
{
}
```

Update `config/shopper/models.php`:

```php theme={null}
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:

```php theme={null}
use Shopper\Models\Brand;

$brand = Brand::findBySlug('nike');
```

## Relationships

### Products

A brand has many [products](/v2/products). This is a standard `HasMany` relationship using the configured product model.

```php theme={null}
$brand->products;
$brand->products()->count();
```

To get only published products for a brand (for storefront display):

```php theme={null}
$brand->products()->publish()->get();
```

With eager loading:

```php theme={null}
$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.

```php theme={null}
use Shopper\Models\Brand;

Brand::query()->enabled()->orderBy('position')->get();
```

## Status Management

The `updateStatus()` method provides a convenient way to toggle brand visibility:

```php theme={null}
$brand->updateStatus(true);  // enable
$brand->updateStatus(false); // disable
```

You can also update the field directly:

```php theme={null}
$brand->update(['is_enabled' => true]);
```

## Media

Brands support two media collections through [Spatie MediaLibrary](https://spatie.be/docs/laravel-medialibrary), using the same config-driven collection names as [products](/v2/products#media).

| 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:

```php theme={null}
$collection = config('shopper.media.storage.thumbnail_collection');

$brand->addMedia($file)->toMediaCollection($collection);
```

To retrieve the logo URL with a specific conversion:

```php theme={null}
$collection = config('shopper.media.storage.thumbnail_collection');

$url = $brand->getFirstMediaUrl($collection, 'medium');
```

From a URL:

```php theme={null}
$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:

```php theme={null}
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:

```php theme={null}
Brand::query()->enabled()->orderBy('position')->get();
```

To get brands that have at least one product, with product counts:

```php theme={null}
$brands = Brand::query()
    ->enabled()
    ->has('products')
    ->withCount('products')
    ->get();
```

To find a brand by slug:

```php theme={null}
$brand = Brand::findBySlug('nike');
```

For navigation or filter dropdowns where you only need name and slug:

```php theme={null}
$brands = Brand::query()
    ->enabled()
    ->select(['id', 'name', 'slug'])
    ->orderBy('name')
    ->get();
```

## Metadata

The `metadata` JSON column lets you store additional custom data that doesn't warrant its own database column:

```php theme={null}
$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`:

```php theme={null}
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:

```bash theme={null}
php artisan shopper:component:publish brand
```

This creates `config/shopper/components/brand.php`:

```php theme={null}
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.

```php theme={null}
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:

```php theme={null}
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`:

```php theme={null}
use Illuminate\Support\Facades\View;

public function boot(): void
{
    View::composer(['partials.brands', 'filters.brands'], BrandsComposer::class);
}
```
