Skip to content

Subscription Addons

Barberklingen\BasePlugin\Modules\SubscriptionAddons\SubscriptionAddonsLoader

Addon products are extra items attached directly to a subscription rather than purchased as a full order. They carry their own interval and count logic so they can be included in renewal orders on a different cadence than the main subscription.

Activating the module

SubscriptionAddonsLoader::get_instance();

The loader boots the following sub-modules:

Sub-module Responsibility
AddonOrderItemTypesLoader Registers the addon WC order item type
AddonAdminMetaOutput Renders the addon UI in the WP admin subscription screen
AddonAdminAjax AJAX handlers for admin-side addon management
AddonAdminProductSettings Adds _profile_addon_price / _profile_addon_oneoff_price fields to products
AddonRenewals Copies addon items into renewal orders at the correct interval
AddonSubscriptionsBackend Backend logic for attaching/detaching addons
AjaxSetAddonProductsEndpoint Customer-facing AJAX endpoint for updating addons
AddonKlaviyoExtension Fires Klaviyo events when addons change

The addon order item type

Addon items are stored as WC_Order_Item records with order_item_type = 'addon'. The custom class SubscriptionItemAddon extends WC_Order_Item_Product with two extra properties:

Property Type Description
addon_interval int How often the item is included (0 = once, 1 = every renewal, N = every N renewals)
addon_count int Countdown until the next inclusion; when it reaches 1 the item is added to the renewal order

Adding an addon to a subscription

use Barberklingen\BasePlugin\Modules\SubscriptionAddons\OrderItemTypes\Types\SubscriptionItemAddon;

SubscriptionItemAddon::add_to_subscription(
    $product,           // WC_Product or product ID
    $qty,               // int
    $subscription,      // WC_Subscription
    [
        'addon_interval' => 2,  // include every 2 renewals
        'addon_count'    => 1,  // include on the very next renewal
    ]
);

The method calculates price, taxes, and saves the item, then fires subscription/addon-product/added.

Removing an addon

SubscriptionItemAddon::remove_from_subscription($subscription, $addonItem);
// Fires: subscription/addon-product/before-remove, subscription/addon-product/removed

Pricing

Addon prices are read from two product meta fields:

Meta key Used when
_profile_addon_price addon_interval > 0 (recurring)
_profile_addon_oneoff_price addon_interval === 0 (one-off)

Both fall back to the product's regular price if the meta is absent. Override via filters:

add_filter('subscription/addon-product/recurring-price', fn($price, $product, $interval, $qty, $order) => $price);
add_filter('subscription/addon-product/one-off-price',   fn($price, $product, $interval, $qty, $order) => $price);

Interval options

AddonOptionsHelper provides helpers for rendering interval/delivery dropdowns in admin and frontend forms:

AddonOptionsHelper::get_interval_options();          // [0 => 'Only once', 1 => 'Every renewal', ...]
AddonOptionsHelper::get_next_delivery_options($interval, $period, $dateFrom);
AddonOptionsHelper::print_interval_field($fieldName, $currentValue);
AddonOptionsHelper::print_next_delivery_field($fieldName, $currentValue, $interval, $period, $dateFrom);

Interval options are filterable via subscription-addons/interval-options.

Checking addon presence

use Barberklingen\BasePlugin\Modules\SubscriptionAddons\Helpers\AddonSubscriptionHelper;

AddonSubscriptionHelper::has_addon_product($subscription, $productId);        // bool
AddonSubscriptionHelper::get_addon_product_item($subscription, $productId);   // ?SubscriptionItemAddon
AddonSubscriptionHelper::get_estimated_next_shipment($addonItem);             // ?string (formatted date)

Renewal behaviour

On each renewal, AddonRenewals decrements each addon's addon_count. When addon_count reaches 1, the item is added as a line_item to the renewal order. The count then resets to addon_interval (or the item is removed if addon_interval === 0).

Extending with GTM/Klaviyo

AddonGTMExtension and AddonKlaviyoExtension listen to the addon action hooks and push events to their respective systems. Consuming plugins can add their own extensions by hooking into:

  • subscription/addon-product/added($item, $subscription, $qty)
  • subscription/addon-product/removed($subscription, $productId)
  • subscription/addon-product/before-remove($subscription, $item, $productId)