Model
Extending the Model
To customize the Product model, create your own model that extends the base class:config/shopper/models.php:
Database Schema
| Column | Type | Nullable | Default | Description |
|---|---|---|---|---|
id | bigint | no | auto | Primary key |
name | string | no | - | Product name |
slug | string | yes | auto | URL-friendly identifier (unique) |
sku | string | yes | null | Stock Keeping Unit (unique) |
barcode | string | yes | null | Product barcode (unique) |
description | longtext | yes | null | Full product description |
type | string | yes | null | Product type enum value |
is_visible | boolean | no | false | Visibility on storefront |
featured | boolean | no | false | Featured product flag |
security_stock | integer | yes | 0 | Minimum stock alert threshold |
old_price_amount | integer | yes | null | Compare at price (in cents) |
price_amount | integer | yes | null | Current price (in cents) |
cost_amount | integer | yes | null | Cost per item (in cents) |
backorder | boolean | no | false | Allow backorders |
published_at | datetime | no | now() | Publication date |
seo_title | string(60) | yes | null | SEO meta title |
seo_description | string(160) | yes | null | SEO meta description |
weight_unit | string | yes | null | Weight unit enum |
weight_value | decimal(10,2) | yes | null | Weight value |
height_unit | string | yes | null | Height unit enum |
height_value | decimal(10,2) | yes | null | Height value |
width_unit | string | yes | null | Width unit enum |
width_value | decimal(10,2) | yes | null | Width value |
depth_unit | string | yes | null | Depth unit enum |
depth_value | decimal(10,2) | yes | null | Depth value |
volume_unit | string | yes | null | Volume unit enum |
volume_value | decimal(10,2) | yes | null | Volume value |
external_id | string | yes | null | External system reference |
metadata | json | yes | null | Additional custom data |
brand_id | bigint | yes | null | Foreign key to brands |
parent_id | bigint | yes | null | Foreign key for parent product |
Product Types
Products are categorized using theProductType enum:
Type Checking Methods
Type Capabilities
Relationships
Brand
Categories
Products have a many-to-many polymorphic relationship with categories:Collections
Channels
Products can be assigned to multiple sales channels:Variants
For products withtype = ProductType::Variant:
Attributes (Options)
Product attributes are accessed via theoptions() relationship to avoid collision with Eloquent’s $attributes property:
Related Products
Query Scopes
Published Products
Filter by Channel
Publication Status
Media Management
Products use Spatie Media Library for image management:Media Collections Configuration
The product registers three media collections:| Collection | Disk | Single File | Description |
|---|---|---|---|
products | configured | no | Product gallery images |
thumbnail | configured | yes | Main product image |
files | configured | no | Downloadable files |
Events
Product lifecycle events are dispatched automatically:Stock Management
Products use theHasStock trait for inventory management:
Avoiding N+1 Queries
Accessing$product->stock triggers an individual SUM(quantity) query on the inventory_histories table for each product.
When iterating over a collection (e.g. listing 50 products on a page), this results in 50 separate queries — a classic N+1 problem.
Use loadCurrentStock() to batch-load stock for an entire collection in a single query:
Preventing Lazy Stock Loading
Following the same pattern as Laravel’sModel::preventLazyLoading(), you can opt-in to catch
unoptimized stock access during development. When enabled, accessing $product->stock without
prior batch-loading throws a LazyStockLoadingException:
Pricing
Products use theHasPrices trait:
Creating Products
Retrieving Products
Components
Publish Livewire components to customize the admin interface:config/shopper/components/product.php: