Skip to main content
Associations define relationships between products, such as cross-sells, up-sells, and alternates.

Overview

Associations allow products to be related to each other. The type of association defines how the relationship should be presented on a storefront and how Lunar interprets it. Lunar ships with three built-in types (cross-sell, up-sell, and alternate), but custom types can also be created.

Model

Associations are stored as Lunar\Models\ProductAssociation models.

Fields

FieldTypeDescription
idbigIncrementsPrimary key
product_parent_idforeignIdThe owning product
product_target_idforeignIdThe associated product
typestringThe association type (e.g. cross-sell, up-sell, alternate)
created_attimestamp
updated_attimestamp

Relationships

RelationshipTypeRelated ModelDescription
parentBelongsToLunar\Models\ProductThe owning product
targetBelongsToLunar\Models\ProductThe associated product

Scopes

ScopeDescription
crossSell()Filter to cross-sell associations
upSell()Filter to up-sell associations
alternate()Filter to alternate associations
type(ProvidesProductAssociationType|string $type)Filter by a specific type

Association Types Enum

The built-in association types are defined using the Lunar\Base\Enums\ProductAssociation enum:
use Lunar\Base\Enums\ProductAssociation;

ProductAssociation::CROSS_SELL; // 'cross-sell'
ProductAssociation::UP_SELL;    // 'up-sell'
ProductAssociation::ALTERNATE;  // 'alternate'
The Lunar\Models\ProductAssociation model also defines CROSS_SELL, UP_SELL, and ALTERNATE as class constants, but these are deprecated since v1.2.0. Use the Lunar\Base\Enums\ProductAssociation enum instead.

Loading Associations

$product->associations;
This returns a collection of Lunar\Models\ProductAssociation models:
use Lunar\Models\ProductAssociation;

$association->parent; // The owning product
$association->target; // The associated product
$association->type;   // The association type string

Inverse Associations

To find products that associate to a given product (i.e. where the product is the target), use the inverseAssociations relationship:
$product->inverseAssociations;

Types of Association

Cross-Sell

Cross-selling encourages customers to purchase complementary products in addition to the item they intended to buy. For example, if a store sells a phone, cross-sell associations could include headphones or a case that works with that phone. Adding a cross-sell association
use Lunar\Base\Enums\ProductAssociation;

$product->associate($crossSellProduct, ProductAssociation::CROSS_SELL);

// Or associate multiple products at once
$product->associate([$productA, $productB], ProductAssociation::CROSS_SELL);
Fetching cross-sell associations
use Lunar\Base\Enums\ProductAssociation;

// Using the convenience scope
$product->associations()->crossSell()->get();

// Using the type scope
$product->associations()->type(ProductAssociation::CROSS_SELL)->get();

Up-Sell

Up-selling encourages customers to upgrade or include add-ons to the product they are buying, typically to a higher-value option. For example, given two phones:
  • Phone 16GB 5” Screen
  • Phone 32GB 6” Screen
The 32GB version could be added as an up-sell association on the 16GB product, allowing it to be presented as an upgrade when a customer views the 16GB version. Adding an up-sell association
use Lunar\Base\Enums\ProductAssociation;

$product->associate($upSellProduct, ProductAssociation::UP_SELL);

// Or associate multiple products at once
$product->associate([$productA, $productB], ProductAssociation::UP_SELL);
Fetching up-sell associations
use Lunar\Base\Enums\ProductAssociation;

// Using the convenience scope
$product->associations()->upSell()->get();

// Using the type scope
$product->associations()->type(ProductAssociation::UP_SELL)->get();

Alternate

Alternate products are alternatives to the current product. This is useful when a product is out of stock or not quite the right fit, allowing the storefront to suggest similar options. Adding an alternate association
use Lunar\Base\Enums\ProductAssociation;

$product->associate($alternateProduct, ProductAssociation::ALTERNATE);

// Or associate multiple products at once
$product->associate([$productA, $productB], ProductAssociation::ALTERNATE);
Fetching alternate associations
use Lunar\Base\Enums\ProductAssociation;

// Using the convenience scope
$product->associations()->alternate()->get();

// Using the type scope
$product->associations()->type(ProductAssociation::ALTERNATE)->get();

Custom Types

In addition to the built-in types, custom association types can be defined by passing any string as the type. No registration or configuration is required since the type column is a plain string in the database.
$product->associate($relatedProduct, 'my-custom-type');
Custom types can be queried using the type scope:
$product->associations()->type('my-custom-type')->get();
If using the admin panel, custom types can be added to the association type dropdown by replacing the default enum. See Admin Panel Configuration for details.

Removing Associations

The dissociate method removes associations between products. It accepts a single product, an array, or a collection. If no type is specified, all association types for the given product(s) are removed.
use Lunar\Base\Enums\ProductAssociation;

// Remove all associations with a product, regardless of type
$product->dissociate($associatedProduct);

// Also accepts an array or collection of products
$product->dissociate([$productA, $productB]);

// Remove only a specific association type
$product->dissociate($associatedProduct, ProductAssociation::CROSS_SELL);

Queued Operations

Both associate() and dissociate() dispatch queued jobs (Lunar\Jobs\Products\Associations\Associate and Lunar\Jobs\Products\Associations\Dissociate). This means the changes may not be reflected immediately if using an asynchronous queue driver.