Product Data and Catalogue Hooks
The eight hooks over product data, the catalogue, the card lists and stock.
Overview
Product data flows two ways: through the save filter on the way in, and through the read filter on the way out. The second is a hot path and its result goes into the cache.
The rest build the lists a customer sees: catalogue plans, related products, software cards. The stock hook stands apart; it is moved not by an administrator but by an order.
Reference
Changing the product data before it is saved
Runs before the product data is written. This is how you hold one pricing policy in one place.
Hook::add('filter:product.save_data', 10, function (&$input, $detail) {
// Hold the pricing policy in one place.
$input['pricing'] = Acme::applyMargin($input['pricing'] ?? []);
});Changing a product as it is read
Runs after a product is read from the database. Every product read passes here: the catalogue, the basket, the service detail.
Hook::add('filter:product.get', 10, function (&$product, $context) {
// A product that was not found arrives as an EMPTY array.
if (!$product) return;
$product['acme_badge'] = Acme::badgeFor((int) ($context['id'] ?? 0));
});Changing the product configuration fields
Runs before the configuration fields offered by the module appear.
Hook::add('filter:product.config_options', 10, function (&$config_options, $module, $module_data) {
// Emptying the array removes the form entirely.
unset($config_options['legacy_option']);
});Changing the catalogue plans
Runs once the plan list on the catalogue page is prepared. Ordering, hiding and badging belong here.
Hook::add('filter:product.catalog_plans', 10, function (&$plans, $ctx) {
$plans = Acme::sortByPopularity($plans);
});Changing the related product cards
Runs once the related product list under a product detail is built. The core default is up to four products from the same catalogue.
Hook::add('filter:product.related_products', 10, function (&$related, &$id) {
$related = Acme::recommendFor($id) ?: $related;
});Changing the software card list
Runs once the card list of the software store is prepared.
Hook::add('filter:product.software_list', 10, function (&$out, $ctx) {
// If you produce a price, use the currency from the context.
$out = Acme::attachOffers($out, (int) ($ctx['currency'] ?? 0));
});Following a stock change
Runs when the stock of a product moves. The move is not manual: an order becoming active, or ceasing to be, drives it.
Hook::add('action:product.stock_changed', 10,
function ($product_id, $delta, $new_stock, $order_id) {
// Stock never goes below zero.
if ($new_stock === 0) Acme::alertOutOfStock($product_id);
});Changing per-country message pricing
Runs before international text message prices are saved.
Hook::add('filter:product.intl_sms_country_pricing_save', 10, function (&$current_list) {
foreach ($current_list as $code => $row)
$current_list[$code]['amount'] = Acme::markup((float) ($row['cost'] ?? 0));
});Pitfalls
A change you make in the product read filter is kept and comes back on later reads. A mistake here shows up not on one page but everywhere until the cache is cleared.
If the array is empty after the configuration fields filter, no form appears at all. Clearing everything while meaning to hide one field leaves the administrator unable to configure the product.
Related Articles
- Product Hooks
- Service Hooks
- How Hooks Work
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.