Skip to main content

Plugin Hooks Reference (wpec_*)

All hooks use the wpec_ prefix. Parameters shown with types.


Lifecycle Hooks

wpec_initialized (action)

Fired after the plugin has fully initialized (Bootstrap complete).

ParameterTypeDescription
$bootstrapBootstrapThe plugin bootstrap instance
add_action('wpec_initialized', function($bootstrap) {
// Plugin is ready — register custom services, post types, etc.
});

wpec_register_services (action)

Fired during service registration phase. Use to register custom services in the DI container.

ParameterTypeDescription
$containerContainerThe DI container instance
add_action('wpec_register_services', function($container) {
$container->singleton(MyCustomService::class, function() {
return new MyCustomService();
});
});

wpec_components_loaded (action)

Fired after all plugin components (admin, API controllers, etc.) are loaded.

ParameterTypeDescription
$bootstrapBootstrapThe plugin bootstrap instance

wpec_activated (action)

Fired when the plugin is activated. Use for setup tasks (create tables, seed data, etc.).

Parameters: None

wpec_deactivated (action)

Fired when the plugin is deactivated. Use for cleanup.

Parameters: None

add_action('wpec_deactivated', function() {
wp_clear_scheduled_hook('my_custom_cron');
});

Product Hooks

Filters

HookParametersReturnsDescription
wpec_product_price$price, $productfloatModify product display price
wpec_product_sale_price$salePrice, $productfloat|nullModify sale price
wpec_product_is_purchasable$isPurchasable, $product, $quantityboolControl if product can be bought
wpec_product_not_purchasable_message$message, $productstringCustom "not available" message
wpec_product_query_args$argsarrayModify product listing query
wpec_product_before_save$data, $product, $isUpdatearrayModify data before DB write
// B2B pricing: 20% discount for wholesale customers
add_filter('wpec_product_price', function($price, $product) {
if (current_user_can('wholesale_customer')) {
return $price * 0.80;
}
return $price;
}, 10, 2);

// Force exclude out-of-stock on frontend
add_filter('wpec_product_query_args', function($args) {
if (!is_admin()) {
$args['exclude_out_of_stock'] = true;
}
return $args;
});

Actions

HookParametersDescription
wpec_before_save_product$product, $isUpdateBefore product is saved to DB
wpec_product_saved$product, $isUpdateAfter product is saved
wpec_before_delete_product$idBefore product deletion
wpec_product_deleted$idAfter product is deleted
add_action('wpec_product_saved', function($product, $isUpdate) {
if ($isUpdate) {
cdn_purge('/products/' . $product->getSlug());
}
}, 10, 2);

API Response Filters

HookParametersReturnsDescription
wpec_api_product_response$data, $productarrayTransform single product API response
wpec_api_products_response$data, $args, $totalarrayTransform product list API response
add_filter('wpec_api_product_response', function($data, $product) {
$data['estimated_delivery'] = calculate_delivery_date($product->getId());
$data['is_bestseller'] = get_post_meta($product->getId(), '_is_bestseller', true);
return $data;
}, 10, 2);

Variant Hooks

Fired by the variant repository around save/delete of product variants.

HookTypeParametersDescription
wpec_validate_variant_saveaction$variant, $isUpdateBefore any DB write for a variant. A listener may throw \RuntimeException to veto the save (e.g. block a simple→variable transition while product-level stock is non-zero). The exception bubbles to the calling controller.
wpec_variant_savedaction$variant, $isUpdateAfter a variant is inserted or updated
wpec_before_variant_deletedaction$id, $productIdBefore a variant is deleted
wpec_variant_deletedaction$id, $productIdAfter a variant is deleted
// Veto deletion of the last variant of a variable product
add_action('wpec_validate_variant_save', function($variant, $isUpdate) {
if (!$variant->getSku()) {
throw new \RuntimeException('Variants must have a SKU.');
}
}, 10, 2);

Reviews Hooks

HookTypeParametersDescription
wpec_review_createdaction$reviewAfter a new review is saved (any status)
wpec_review_approvedaction$reviewId, $customerIdWhen a review is approved (auto-approved on creation, or approved by an admin)
add_action('wpec_review_approved', function($reviewId, $customerId) {
// Award loyalty points for an approved review, send a thank-you email
award_review_points($customerId, $reviewId);
}, 10, 2);

Shop, Search & Category Hooks

Filters

HookParametersReturnsDescription
wpec_product_sort_options$orderbyMaparrayAdd/modify sort dropdown options
wpec_products_per_page$perPageintChange products per page (default: 20)
wpec_api_category_response$dataarrayTransform category API response
wpec_search_results$results, $queryarrayModify search results before returning
// Add "popularity" sort option
add_filter('wpec_product_sort_options', function($map) {
$map['popularity'] = 'p.sales_count';
$map['rating'] = 'p.average_rating';
return $map;
});

// Add "did you mean" suggestions
add_filter('wpec_search_results', function($results, $query) {
if (empty($results['products']['items'])) {
$results['suggestions'] = get_search_suggestions($query);
}
return $results;
}, 10, 2);

Cart Hooks

Actions

HookParametersDescription
wpec_item_added_to_cart$item, $cartAfter item is added to cart
wpec_cart_item_updated$item, $cartAfter cart item quantity is updated
wpec_item_removed_from_cart$item, $cartAfter item is removed from cart
wpec_cart_cleared$cartAfter entire cart is emptied
add_action('wpec_item_added_to_cart', function($item, $cart) {
analytics_track('add_to_cart', [
'product_id' => $item['product_id'],
'quantity' => $item['quantity'],
'cart_total' => $cart->getTotal(),
]);
}, 10, 2);

Filters

HookParametersReturnsDescription
wpec_cart_item_price$price, $product, $variant, $quantityfloatModify item price before adding to cart
wpec_cart_calculated_totals$totals, $cartarrayModify final cart totals (subtotal, tax_total, shipping_total, discount_total, grand_total)
// Volume discount: 10% off when buying 5+
add_filter('wpec_cart_item_price', function($price, $product, $variant, $quantity) {
if ($quantity >= 5) {
return $price * 0.90;
}
return $price;
}, 10, 4);

// Add handling fee for orders under 30 EUR
add_filter('wpec_cart_calculated_totals', function($totals, $cart) {
if ($totals['subtotal'] < 30) {
$totals['grand_total'] += 2.50;
}
return $totals;
}, 10, 2);

Checkout & Order Hooks

Actions

HookParametersDescription
wpec_checkout_initialized$checkoutCheckout process begins (before validation)
wpec_before_create_order$order, $checkoutBefore order is saved to DB
wpec_order_created$order, $checkoutAfter order is created
wpec_checkout_completed$checkout, $orderAfter checkout fully completes (order + payment initiated)
wpec_order_saved$orderEvery time an order is saved (create or update)
wpec_order_before_delete$orderBefore order is deleted
wpec_order_status_changed$order, $newStatus, $oldStatusWhen order status changes
wpec_order_payment_status_changed$orderId, $statusWhen payment status changes
wpec_order_fulfillment_status_changed$orderId, $statusWhen fulfillment status changes
note
wpec_order_created also fires for marketplace orders

When an order originates from a marketplace (e.g. Skroutz), wpec_order_created fires with $checkout = null, immediately followed by wpec_marketplace_order_created. Guard for a null $checkout if your callback expects checkout data.

add_action('wpec_order_created', function($order, $checkout = null) {
if ($order->getTotal() > 500) {
notify_manager('High-value order: #' . $order->getOrderNumber());
}
}, 10, 2);

add_action('wpec_order_status_changed', function($order, $newStatus, $oldStatus) {
if ($newStatus === 'completed') {
award_loyalty_points($order->getCustomerId(), $order->getTotal());
}
}, 10, 3);

Filters

HookParametersReturnsDescription
wpec_order_number_prefix$prefixstringModify order number prefix (default: 'ORD-')
wpec_checkout_payment_gateways$methodsarrayFilter available payment gateways
wpec_checkout_shipping_methods$methods, $checkoutarrayFilter available shipping methods
wpec_checkout_order_meta$orderMeta, $checkoutarrayAdd custom order metadata
// Custom order number prefix
add_filter('wpec_order_number_prefix', function($prefix) {
return 'MYSHOP-'; // MYSHOP-00001, MYSHOP-00002, etc.
});

// Hide COD for orders over 200 EUR
add_filter('wpec_checkout_payment_gateways', function($methods) {
$cart = wpec_get_cart();
if ($cart && $cart->getGrandTotal() > 200) {
return array_filter($methods, fn($m) => $m['id'] !== 'cod');
}
return $methods;
});

// Add free shipping for orders over 50 EUR
add_filter('wpec_checkout_shipping_methods', function($methods, $checkout) {
if ($checkout->getSubtotal() >= 50) {
array_unshift($methods, [
'id' => 0, 'title' => 'Free Shipping',
'cost' => 0, 'formatted_cost' => '0.00',
]);
}
return $methods;
}, 10, 2);

Customer Hooks

Fired by the customer repository. These are the primary integration points the ERP CustomerContactSync listener subscribes to; use them to mirror customers into an external CRM.

HookTypeParametersDescription
wpec_customer_createdaction$customerAfter a new customer is inserted (ID set)
wpec_customer_updatedaction$customer, $oldSnapshotAfter a customer is updated; $oldSnapshot is the pre-update entity
wpec_before_customer_deletedaction$idBefore a customer is deleted (capture linked state here)
wpec_customer_deletedaction$idAfter a customer is deleted
add_action('wpec_customer_created', function($customer) {
crm_upsert_contact($customer->getEmail(), $customer->getName());
});

Payment Hooks

Actions

HookParametersDescription
wpec_payment_gateways_init$managerAfter all gateways registered — use to add custom gateways
wpec_payment_processed$order, $gatewayId, $resultAfter payment is processed through any gateway
wpec_payment_completed$order, $transactionIdPayment successfully completed (callback/webhook confirmed)
wpec_payment_failed$orderPayment failed
wpec_refund_processed$order, $amount, $resultAfter refund is processed
wpec_gateway_log$gatewayId, $message, $levelPayment gateway log messages (info/error/debug)
add_action('wpec_payment_gateways_init', function($manager) {
$manager->register('my_gateway', new MyCustomGateway());
});

add_action('wpec_payment_completed', function($order, $transactionId) {
auto_generate_invoice($order);
}, 10, 2);

Shipping Hooks

Filters

HookParametersReturnsDescription
wpec_shipping_cost$cost, $method, $cartTotal, $cartWeight, $itemCountfloatModify calculated shipping cost
wpec_shipping_methods$methods, $country, $cartTotal, $cartWeightarrayFilter available shipping methods after zone matching
wpec_tracking_url$url, $carrier, $trackingNumberstring|nullModify/add tracking URL for a carrier
// Add custom courier tracking
add_filter('wpec_tracking_url', function($url, $carrier, $trackingNumber) {
if ($carrier === 'my_courier') {
return 'https://mycourier.gr/track/' . $trackingNumber;
}
return $url;
}, 10, 3);

Inventory / WMS Hooks

Actions

HookParametersDescription
wpec_stock_changed$productId, $variantId, $warehouseId, $movementAfter any stock movement (sale, restock, adjustment, transfer)
wpec_low_stock$productId, $name, $sku, $totalStockProduct stock falls below low-stock threshold
add_action('wpec_stock_changed', function($productId, $variantId, $warehouseId, $movement) {
if ($movement['type'] === 'sale') {
sync_marketplace_stock($productId, $variantId);
}
}, 10, 4);

Filters

HookParametersReturnsDescription
wpec_warehouse_backfill_inline_threshold$thresholdintRow count (default 5000) below which a warehouse stock backfill runs inline instead of being queued

Warehouse Hooks

Fired by the warehouse repository. The pre-delete hook lets listeners capture state before cascade cleanup.

HookTypeParametersDescription
wpec_warehouse_createdaction$warehouseAfter a warehouse is created
wpec_warehouse_updatedaction$warehouse, $oldSnapshotAfter a warehouse is updated; $oldSnapshot is the previous entity
wpec_before_warehouse_deletedaction$idBefore a warehouse is permanently deleted
wpec_warehouse_deletedaction$id, $snapshotAfter deletion; $snapshot is the pre-delete entity
wpec_warehouse_deactivatedaction$idWhen a warehouse is deactivated
wpec_warehouse_activatedaction$idWhen a warehouse is (re)activated

Purchasing Hooks

Actions

HookParametersDescription
wpec_po_sent$poId, $poAfter a Purchase Order is sent to supplier
wpec_goods_received$grnId, $grn, $poAfter a Goods Received Note is processed
wpec_supplier_price_updated$price, $oldSnapshotAfter a supplier price is created/updated/deleted. On delete, $price is null and $oldSnapshot holds the removed row.
add_action('wpec_goods_received', function($grnId, $grn, $po) {
// Update accounting, notify warehouse team
}, 10, 3);

Invoicing Hooks

Actions

HookParametersDescription
wpec_invoice_issued$invoiceId, $invoiceAfter an invoice is created/issued
add_action('wpec_invoice_issued', function($invoiceId, $invoice) {
// Send invoice PDF to customer, sync to accounting
}, 10, 2);

ERP Integration Hooks

The ERP layer keeps its domains (Contacts, Inventory, Accounting) in sync with the commerce domains through integration listeners that subscribe to the commerce hooks above (wpec_customer_*, wpec_warehouse_*, wpec_product_*, wpec_stock_changed, …). These extra hooks let you observe or tune that machinery.

Actions

HookParametersDescription
wpec_product_cost_updated$productId, $variantId, $newCostAfter a product/variant cost is recomputed by the cost-source sync. $variantId is 0 for simple products; $newCost may be null.
wpec_erp_user_removed$wpUserIdAfter an ERP staff user is removed (RBAC cleanup)

Filters

HookParametersReturnsDescription
wpec_erp_backfill_page_size$sizeintBatch size (default 1000, min 1) for ERP inventory backfill paging
add_action('wpec_product_cost_updated', function($productId, $variantId, $newCost) {
// Re-margin listings when landed cost changes
recalc_margins($productId, $variantId, $newCost);
}, 10, 3);

HR Hooks

Actions

HookParametersDescription
wpec_hr_leave_requested$requestAfter a leave request is submitted (always starts pending)
wpec_hr_leave_approved$requestAfter a leave request is approved
wpec_hr_leave_rejected$requestAfter a leave request is rejected
add_action('wpec_hr_leave_approved', function($request) {
// Push the approved leave to an external payroll/rota system
payroll_sync_leave($request);
});
No dedicated Ergani hooks

The Ergani (Greek labour ministry) submission flow does not fire its own wpec_ergani_* hooks. React to labour events through these HR leave hooks (and the employee/contract repository events) instead.


Marketplace Hooks

Actions

HookParametersDescription
wpec_marketplace_order_created$order, $marketplaceNew order from marketplace (e.g., Skroutz)
wpec_marketplace_order_updated$order, $marketplace, $changesMarketplace order updated
wpec_marketplace_order_accepted$order, $marketplaceOrder accepted by merchant
wpec_marketplace_order_rejected$order, $marketplaceOrder rejected
wpec_marketplace_order_ready$order, $marketplaceOrder marked ready for pickup/shipping
add_action('wpec_marketplace_order_created', function($order, $marketplace) {
// Auto-print shipping label, notify warehouse
}, 10, 2);

Email Hooks

Actions

HookParametersDescription
wpec_before_email_send$templateType, $recipient, $subject, $dataJust before wp_mail() is called
wpec_email_sent$templateType, $recipient, $resultAfter email is sent

Filters

HookParametersReturnsDescription
wpec_email_subject$subject, $templateType, $datastringModify email subject line
wpec_email_content$content, $templateType, $datastringModify email body (before HTML wrapping)
wpec_email_recipients$recipient, $templateType, $datastringModify email recipients
wpec_email_html$html, $templateType, $datastringModify complete HTML email (after wrapping)
wpec_email_headers$headers, $templateType, $dataarrayModify email headers (From, CC, BCC, etc.)
wpec_email_template_data$dataarrayAdd custom placeholders for all email templates
// Add store prefix to all email subjects
add_filter('wpec_email_subject', function($subject, $type, $data) {
return '[MyStore] ' . $subject;
}, 10, 3);

// CC warehouse on shipment emails
add_filter('wpec_email_recipients', function($recipient, $type, $data) {
if ($type === 'shipment_created') {
$recipient .= ', warehouse@mystore.gr';
}
return $recipient;
}, 10, 3);

// Add BCC to accounting for invoice emails
add_filter('wpec_email_headers', function($headers, $type, $data) {
if ($type === 'invoice_issued') {
$headers[] = 'Bcc: accounting@mystore.gr';
}
return $headers;
}, 10, 3);

// Add custom placeholders
add_filter('wpec_email_template_data', function($data) {
$data['support_email'] = 'support@mystore.gr';
$data['support_phone'] = '+30 210 1234567';
return $data;
});

Licensing Hooks

HookTypeParametersReturnsDescription
wpec_license_gatefilter$result, $featureId, $tierboolOverride the license-gate result for a feature
wpec_license_gate_checkedaction$result, $featureId, $tierFires on every gate check for observability — read-only, cannot change the outcome
Filter vs. action

wpec_license_gate is a filter: returning true can grant access to a gated feature. wpec_license_gate_checked is a deliberately read-only action — it lets you log or audit gate decisions without any ability to bypass them. Use the action for telemetry; reserve the filter for genuine entitlement overrides.

// Grant access to a specific ERP feature for starter tier
add_filter('wpec_license_gate', function($result, $featureId, $tier) {
if ($featureId === 'erp.custom_reports' && $tier === 'starter') {
return true;
}
return $result;
}, 10, 3);

// Observe every gate decision (no bypass)
add_action('wpec_license_gate_checked', function($result, $featureId, $tier) {
my_metrics('license_gate', ['feature' => $featureId, 'tier' => $tier, 'allowed' => $result]);
}, 10, 3);