Skip to main content
Categories provide a hierarchical organization structure for your products. Shopper uses nested categories with parent-child relationships, allowing unlimited depth. You can model complex product taxonomies like Electronics > Computers > Laptops without artificial limits.

Model

The model used is Shopper\Models\Category, which extends Shopper\Core\Models\Category. It implements the Shopper\Core\Models\Contracts\Category contract and Spatie\MediaLibrary\HasMedia for media support. The core model provides the business logic (relationships, scopes, slug generation, hierarchical structure via laravel-adjacency-list), while the admin model adds media collections and conversions through HasMedia and RegistersMediaCollections traits. This separation means the core model has no dependency on Spatie MediaLibrary. Media support is added at the admin layer.

Extending the Model

To add custom behavior, extend the admin model and update your configuration:
Update config/shopper/models.php:

Database Schema

The slug column is nullable at the database level, but is always auto-generated from the name when you set it. You never need to set it manually.

Hierarchical Structure

Categories support unlimited nesting levels, so you can organize products into deeply nested taxonomies:

Media

Categories support two media collections through Spatie MediaLibrary, using the same config-driven collection names as products. Both collections accept JPEG, PNG, WebP, AVIF, and SVG files by default (configurable in config/shopper/media.php). To display a category’s thumbnail on your storefront: The collection names are defined in config/shopper/media.php under the storage key. The getFirstMediaUrl method returns the URL for the first media in a collection. If no media has been uploaded, a fallback URL is returned instead. You can also request a specific conversion size like medium (500x500) or large (800x800).
To add a thumbnail to a category programmatically:

Relationships

Parent Category

Every category can optionally belong to a parent category. Use this to build breadcrumbs or display the parent context:

Child Categories

Using the laravel-adjacency-list package, categories expose a full set of hierarchical relationships: The children relationship returns direct children only. Use descendants for all recursive children (children of children, etc.) and ancestors for all recursive parents up to the root.

Descendant Categories

The descendantCategories() relationship returns a HasManyOfDescendants Eloquent relation, which means you can use it in whereHas(), withCount(), and other query builder contexts. This differs from descendants() which is a recursive scope and cannot be used the same way.

Products

Categories have a polymorphic many-to-many relationship with products through the product_has_relations table:

Available Tree Methods

The laravel-adjacency-list package provides these methods for traversing the hierarchy:

Query Scopes

Enabled Categories

Filter categories that are visible to customers:

Root Categories

Get only top-level categories (those without a parent):
Using the tree structure to get the full hierarchy:

Custom Paths & Slug Behavior

Categories automatically generate slug paths for nested URLs. For a category “Laptops” under “Computers” under “Electronics”, $category->slug_path returns electronics/computers/laptops:
When a category has a parent, the CategoryObserver automatically combines the parent’s slug with the category name on creation and update. For example, creating a “Laptops” category under a parent with slug computers produces the slug computers-laptops. This ensures slug uniqueness across the hierarchy.

Methods & Helpers

Find by Slug

The findBySlug static method looks up a category by its slug and throws a ModelNotFoundException if not found:

Label with Path

The getLabelOptionName() method returns the full hierarchical path as a formatted string, useful for select dropdowns and admin interfaces: For a category “Laptops” nested under “Computers” under “Electronics”, the method returns Electronics / Computers / Laptops.

Status Management

Toggle a category’s visibility with updateStatus(): Pass true to enable a category or false to disable it.

Creating Categories

Create a root category by omitting the parent_id:
Create a subcategory by setting parent_id:

Retrieving Categories

Root categories with their children:
Full category tree:
A category with all its descendants:
For navigation menus with nested enabled children:

Working with Products

Products in this category only:
Products in category and all descendants:

Metadata

The metadata JSON column lets you store arbitrary key-value data on a category without modifying the schema. This is useful for storing custom attributes like banner colors, display preferences, or integration identifiers:

Disabling Category Feature

If your store doesn’t use categories, you can disable the feature entirely. This removes the category section from the admin panel: In your config/shopper/features.php file, set the category feature to disabled:

Permissions

The admin panel generates four permissions for category management:

Components

You can publish the Livewire components to customize the admin UI for categories:
Creates config/shopper/components/category.php:

Storefront Example

Here is a complete controller for displaying categories and their products on your storefront. The show method collects products from the category and all its descendants, so browsing “Electronics” also returns products from “Computers”, “Laptops”, etc.