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.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.
Model
The model used isShopper\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:config/shopper/models.php:
Database Schema
| Column | Type | Nullable | Default | Description |
|---|---|---|---|---|
id | bigint | no | auto | Primary key |
name | string | no | - | Category name |
slug | string | yes | auto | URL-friendly identifier (unique, auto-generated) |
description | longtext | yes | null | Category description |
position | smallint | no | 0 | Display order position |
is_enabled | boolean | no | false | Category 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 |
parent_id | bigint | yes | null | Foreign key to parent category |
created_at | timestamp | yes | null | Creation timestamp |
updated_at | timestamp | yes | null | Last update timestamp |
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.| Collection | Config key | Behavior | Description |
|---|---|---|---|
| Default gallery | shopper.media.storage.collection_name | Multiple files | Category images |
| Thumbnail | shopper.media.storage.thumbnail_collection | Single file | Primary image for listings and navigation |
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).
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 thelaravel-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
ThedescendantCategories() 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 theproduct_has_relations table:
Available Tree Methods
Thelaravel-adjacency-list package provides these methods for traversing the hierarchy:
| Method | Description |
|---|---|
ancestors() | The model’s recursive parents |
ancestorsAndSelf() | The model’s recursive parents and itself |
bloodline() | The model’s ancestors, descendants and itself |
children() | The model’s direct children |
childrenAndSelf() | The model’s direct children and itself |
descendants() | The model’s recursive children |
descendantsAndSelf() | The model’s recursive children and itself |
parent() | The model’s direct parent |
parentAndSelf() | The model’s direct parent and itself |
rootAncestor() | The model’s topmost parent |
siblings() | The parent’s other children |
siblingsAndSelf() | All the parent’s children |
Query Scopes
Enabled Categories
Filter categories that are visible to customers:Root Categories
Get only top-level categories (those without a parent):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
ThefindBySlug static method looks up a category by its slug and throws a ModelNotFoundException if not found:
Label with Path
ThegetLabelOptionName() 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 withupdateStatus():
Pass true to enable a category or false to disable it.
Creating Categories
Create a root category by omitting theparent_id:
parent_id:
Retrieving Categories
Root categories with their children:Working with Products
Products in this category only:Metadata
Themetadata 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 yourconfig/shopper/features.php file, set the category feature to disabled:
Permissions
The admin panel generates four permissions for category management:| Permission | Description |
|---|---|
browse_categories | View the categories list |
read_categories | View a single category |
add_categories | Create new categories |
edit_categories | Edit existing categories |
delete_categories | Delete categories |
Components
You can publish the Livewire components to customize the admin UI for categories:config/shopper/components/category.php:
Storefront Example
Here is a complete controller for displaying categories and their products on your storefront. Theshow method collects products from the category and all its descendants, so browsing “Electronics” also returns products from “Computers”, “Laptops”, etc.