Skip to content
Gaurav TiwariDocs

Hooks, Functions and Templates Reference

Updated November 7, 2026

Sooner or later a store needs one thing done differently: a different label, a page that unlocks on a custom rule, a CRM that hears about every renewal. Editing plugin files works until the next update wipes your change.

Stackless gives you three safe places to hook in instead: a set of public PHP functions, actions and filters with the stackless_ prefix, and templates you can copy into your theme. Everything on this page comes from the plugin’s own code. Put your snippets in a child theme’s functions.php or a small custom plugin.

Public Functions

These live in includes/functions.php and load with the plugin. Every one of them is safe to call whether or not the owning module is on: with the module off, you get a neutral default (false, 0, an empty array, or the store’s base currency).

Modules:

  • stackless_module_active(string $id): bool is true when a module is switched on and actually running on this request. Module ids are snake_case, such as subscriptions, multi_currency, software_licensing.

Subscriptions:

  • stackless_get_subscription($subscription_id) returns a subscription object or null.
  • stackless_get_customer_subscriptions(int $user_id, array $args = []): array takes status (array of status slugs) and product_id in $args.
  • stackless_customer_has_active_subscription(int $user_id, int $product_id = 0): bool counts active, trialing and pending-cancel subscriptions. $product_id 0 means any product.
  • stackless_is_subscription_product($product): bool takes a WC_Product or an ID.
  • stackless_get_order_subscriptions(\WC_Order $order): array returns subscriptions created or renewed by that order.
  • stackless_order_contains_subscription(\WC_Order $order): bool
  • stackless_cart_contains_subscription(): bool
  • stackless_cart_subscription_count(): int returns how many subscriptions the cart will create, one per billing schedule.
  • stackless_is_renewal_order(\WC_Order $order): bool
  • stackless_order_requires_saved_payment_method(\WC_Order $order): bool and stackless_cart_requires_saved_payment_method(): bool tell a gateway it must save a reusable payment method.
  • stackless_create_renewal_order($subscription) returns a pending renewal WC_Order or a WP_Error.
  • stackless_import_subscription(array $data) creates a subscription from another system’s record and keeps its dates. It returns the subscription id or a WP_Error, charges nothing and sends no customer email. It’s idempotent per source (required, such as wcs:123): a second call with the same source returns the subscription created the first time. The other keys are listed above the function in functions.php.

Currency:

  • stackless_current_currency(): string is the visitor’s cart currency.
  • stackless_base_currency(): string is the store base currency.
  • stackless_enabled_currencies(): array lists ISO codes enabled for checkout.
  • stackless_convert_amount($amount, string $to = '', string $from = '') converts at the configured rate, with no charm rounding. $to defaults to the visitor’s currency and $from to the base currency.
  • stackless_currency_rate(string $currency = ''): float is the effective rate from base, markup included. It’s 0 when unknown and 1 when the module is off.
  • stackless_switch_currency(string $currency, string $source = 'api') returns true, a WP_Error, or false when the module is off.
  • stackless_currency_signup_fee(float $fee, \WC_Product $product, string $currency = ''): float

Templates:

  • stackless_get_template(string $name, array $args = []): void loads a template from templates/, overridable from the theme.

Invoice and Quote Functions

These are defined only when their module is running, so wrap calls in function_exists().

Custom Invoices:

  • stackless_is_invoice($order): bool
  • stackless_invoice_save_draft(array $input, int $invoice_id = 0, int $revision = 0) returns a WC_Order or WP_Error.
  • stackless_invoice_input(int $invoice_id): array
  • stackless_invoice_summary(int $invoice_id): ?array
  • stackless_invoice_issue(int $invoice_id, int $revision, string $signature, string $context = 'api') issues a draft. The signature must come from stackless_invoice_summary().
  • stackless_invoice_schedule(int $invoice_id, array $schedule) starts or replaces an issued invoice’s recurring schedule and returns the schedule row or a WP_Error. $schedule takes unit (week, month or year), count (1 to 12), start (Y-m-d, after today in the site timezone), ends (never, after or on), ends_after, ends_on and mode (send or draft). Invalid input returns status 400; it never throws.
  • stackless_invoice_view_url(int $invoice_id): string returns the signed customer link, or an empty string for drafts.
  • stackless_invoice_admin_url(array $args = []): string
  • stackless_invoice_integration_key_valid($key): bool
  • stackless_create_invoice(array $request): array is the keyed automation entry point. It needs integration_key and idempotency_key.
  • stackless_create_invoice_draft_trusted(array $request): array always makes a private draft and never emails. It needs idempotency_key.
  • stackless_agent(string $command, array $input = []): array runs invoice commands (and quote commands when Request a Quote is on) as the signed-in user.

Request a Quote:

  • stackless_create_quote_request(array $request): array needs integration_key (from Invoices → Automation) and idempotency_key. It never sets prices.
  • stackless_create_quote_request_trusted(array $request): array needs idempotency_key.

Subscription Statuses and Contracts

Subscription objects implement Stackless\Contracts\SubscriptionData and are WooCommerce orders of type stackless_sub, so get_id(), get_customer_id(), get_currency(), get_items() and get_meta() work as on any order. The extra methods are is_active(), get_billing_period(), get_billing_interval(), get_next_payment_date(), get_trial_end_date(), get_end_date(), has_product(), get_renewal_mode() and get_recurring_total().

Status slugs used in hooks:

  • pending
  • sub-trial
  • sub-active
  • sub-onhold
  • sub-paused
  • sub-pendcancel
  • sub-cancelled
  • sub-expired

A payment gateway that wants to renew Stackless subscriptions automatically implements Stackless\Contracts\SubscriptionGateway: stackless_supports(string $feature), stackless_charge_renewal(\WC_Order $renewal_order, $subscription) and stackless_cancel_subscription($subscription, bool $at_period_end). Gateways that don’t still work in manual-renewal mode.

A paused subscription doesn’t renew and doesn’t grant Page Access until it resumes. The REST API and AI agents show this status as paused.

A gateway can also implement these contracts from Stackless\Contracts:

  • PausableGateway: stackless_pause_subscription($subscription, ?int $resume_at): bool and stackless_resume_subscription($subscription): bool, for a gateway whose provider bills renewals itself and can pause billing. $resume_at is a Unix time, or null to pause until resumed by hand. Return false to refuse, and the subscription stays as it was. A provider-billed gateway without this contract can’t pause, so customers don’t see Pause on its subscriptions. Gateways whose renewals Stackless schedules need nothing: Subscriptions pauses those itself.
  • AdvanceCharge: stackless_renewal_lead_seconds(): int, for a gateway whose charges land some time after they’re requested, such as card and UPI mandates in India. Subscriptions starts the renewal charge that many seconds before the due date. The next payment date doesn’t change.
  • SavedMethodCharger: stackless_charge_saved_method(\WC_Order $order, \WC_Payment_Token $token, bool $on_session): array charges a saved token for a new order outside the renewal schedule. Post-purchase Offers call it after the customer clicks. It returns result (success, pending, failed or action_required), an optional message, and for action_required a redirect to the page where the customer completes authentication. Stripe, PayPal, Square, Mollie and Razorpay implement it.

When a renewal charge fails, a gateway writes why on the renewal order before it sets that order to failed, because dunning reads it on the status change. SubscriptionData::META_DECLINE (_stackless_decline_category) holds one of hard, soft, funds, auth or network, and SubscriptionData::META_DECLINE_CODE (_stackless_decline_code) holds the provider’s own code for notes and reports.

Core and Settings Actions

  • stackless_loaded fires after all enabled modules have started. Args: the Stackless\Core\Plugin instance.
  • stackless_settings_after_{module_id} fires inside a module’s settings form, after its fields. Use it to print extra settings UI.
  • stackless_settings_saved_{module_id} fires after a module’s settings are saved.

Subscription Actions

These are the documented cross-module actions, fired by the Subscriptions module:

  • stackless_subscription_created: $subscription, \WC_Order $parent_order
  • stackless_subscription_status_changed: $subscription, string $from, string $to
  • stackless_subscriptions_status_{to}: $subscription, string $from, for one target status, such as stackless_subscriptions_status_sub-cancelled
  • stackless_subscription_renewal_order_created: \WC_Order $renewal, $subscription
  • stackless_subscription_renewal_paid: $subscription, \WC_Order $renewal
  • stackless_subscription_payment_failed: $subscription, \WC_Order $renewal
  • stackless_subscription_switched: $subscription, int $old_product_id, int $new_product_id, \WC_Order $switch_order
  • stackless_subscriptions_payment_method_changed: $subscription, $token
  • stackless_subscription_resumed: $subscription, string $from, when a paused subscription resumes
  • stackless_subscription_cancel_reason: $subscription, string $reason, string $comment, after a customer cancels through the cancellation form
  • stackless_subscription_imported: $subscription, array $data, after stackless_import_subscription() creates a subscription

Here’s a small example that logs every cancellation:

add_action('stackless_subscription_status_changed', function ($subscription, $from, $to) {
    if ($to === 'sub-cancelled') {
        error_log('Subscription #' . $subscription->get_id() . ' cancelled (was ' . $from . ')');
    }
}, 10, 3);

Other Module Actions

  • stackless_currency_switched: $currency, $previous, $source
  • stackless_checkout_enhancements_consents_recorded: $order, $records
  • stackless_buy_links_processed: int $added, array $params, after a buy link filled the cart and before the redirect
  • stackless_custom_invoices_issued: $order, $context
  • stackless_custom_invoices_paid: $order
  • stackless_custom_invoices_cancelled: $order
  • stackless_custom_invoices_recurring_created: $invoice, $schedule
  • stackless_request_quotes_requested: $id, $event
  • stackless_request_quotes_proposal_sent: $quoteId, $offer
  • stackless_request_quotes_accepted: $quoteId, $issued
  • stackless_request_quotes_declined: $quoteId, $reason
  • stackless_page_access_changed: $postId, $productIds
  • stackless_software_licensing_keys_created: int[] $license_ids, \WC_Order $order
  • stackless_software_licensing_site_activated: $licenseId, string $site, bool $isLocal
  • stackless_software_licensing_site_deactivated: int $licenseId, string $siteUrl
  • stackless_software_licensing_order_revoked: $order, $revoked
  • stackless_software_licensing_package_download: $license, $product, $package
  • stackless_stripe_gateway_dispute_created: $order, $dispute
  • stackless_paypal_gateway_webhook: $type, $resource, $event
  • stackless_polar_gateway_webhook: $type, $data, $mode
  • stackless_creem_gateway_webhook: string $type, array $object, string $mode, string $gateway_id
  • stackless_creem_gateway_subscription_synced: $subscription, array $remote, string $event
  • stackless_stripe_managed_subscription_synced: $subscription, array $remote, string $event
  • stackless_razorpay_gateway_webhook: string $type, array $event, string $mode
  • stackless_mollie_gateway_webhook: array $payment, string $mode, with the payment read back from the Mollie API
  • stackless_square_gateway_webhook: string $type, array $event, string $mode
  • stackless_paystack_gateway_webhook: string $type, array $data, string $mode
  • stackless_flutterwave_gateway_webhook: string $type, array $data, string $mode
  • stackless_mercadopago_gateway_webhook: string $type, array $notification, string $mode
  • stackless_checkout_recovery_abandoned: array $row, once, when a captured checkout is left without an order and its first reminder comes due
  • stackless_checkout_recovery_recovered: array $row, \WC_Order $order, when a captured checkout becomes an order

Useful Filters

Catalog and product pages:

  • stackless_free_price_text_text: string $text, ?\WC_Product $product, the text shown for a zero price
  • stackless_sold_count_count: int $count, \WC_Product $product, defaults to WooCommerce’s total sales
  • stackless_stock_urgency_level: string $level, \WC_Product $product, one of none, in-stock, low, very-low, out
  • stackless_custom_cart_text_text: string $text, \WC_Product $product, string $context, where context is loop or single
  • stackless_quantity_limits_limits: array $limits, \WC_Product $product, with keys min, max, step (max 0 means no limit)
  • stackless_hide_price_until_login_viewer_sees_prices: bool $sees
  • stackless_wholesale_pricing_viewer_roles: string[] $roles, the wholesale roles a viewer is priced with. Return [] for public prices.
  • stackless_wholesale_pricing_skip_item: bool $skip, array $cart_item, to leave a cart line out of wholesale pricing

Subscriptions:

  • stackless_subscriptions_customer_actions: array $actions, $subscription, the buttons customers see in My Account. Each action has label, url, class and an optional confirm.
  • stackless_subscriptions_product_plan: $plan, \WC_Product $product
  • stackless_subscriptions_switch_candidates: array $products, \WC_Product $current, the products a customer can switch to
  • stackless_order_pay_link_allowed: bool $allowed, \WC_Order $order, whether an order’s pay link may be shown. Subscriptions returns false for renewals that Polar, Paddle, Dodo Payments, Creem or Stripe Managed Payments bill themselves, because paying them another way would charge the customer twice once the provider’s retry succeeds. FluentCRM smart codes, the payment-failed email and My Account ask it.

Page Access:

  • stackless_page_access_can_view: bool $allowed, int $postId, int $userId, int[] $products, where $userId is 0 for guests
  • stackless_page_access_post_types: defaults to ['page']
  • stackless_page_access_paid_statuses: defaults to WooCommerce’s paid statuses
  • stackless_page_access_store_page_ids: shop, cart, checkout, account, terms and privacy pages, which never take a restriction

Software Licensing:

  • stackless_software_licensing_allow_activation: true|\WP_Error $allowed, array $license, string $site, bool $isLocal. Return a WP_Error to refuse.
  • stackless_software_licensing_generate_for_order: bool $generate, \WC_Order $order
  • stackless_software_licensing_generate_key: string $key, array $config, array $data
  • stackless_software_licensing_local_patterns: the site patterns treated as local or staging
  • stackless_software_licensing_payload, stackless_software_licensing_version_payload and stackless_software_licensing_info_payload: the license API responses. The license payload, and the array stackless_get_license() returns, include channel: stable or beta.
  • stackless_documentation_url: change where the admin Docs links point. It receives the URL and the module or screen key, so you can send your team to internal docs instead.
  • stackless_software_licensing_email_ids: the WooCommerce emails that include license keys

Currency and payments:

  • stackless_multi_currency_default_currency: string $default, array $enabled
  • stackless_multi_currency_fixed_coupon_types: defaults to ['fixed_cart', 'fixed_product']
  • stackless_multi_currency_convert_fee: bool $convert, $fee, string $currency
  • stackless_currency_gateways_capabilities: array $map of gateway id to currency codes
  • stackless_currency_selector_country and stackless_currency_selector_country_currencies: the visitor’s detected country and the country to currency map
  • stackless_stripe_gateway_webhook_tolerance: signature tolerance in seconds, default 300
  • stackless_stripe_gateway_dispute_sets_on_hold: bool, \WC_Order $order, default true
  • stackless_paypal_gateway_button_style: array $style with layout, color, shape, label
  • stackless_paypal_gateway_currencies and stackless_polar_gateway_currencies: currencies each gateway accepts
  • stackless_creem_gateway_currencies, stackless_razorpay_gateway_currencies, stackless_mollie_gateway_currencies, stackless_paystack_gateway_currencies, stackless_flutterwave_gateway_currencies and stackless_mercadopago_gateway_currencies: the same for those gateways. Creem’s can only narrow its list to USD and EUR, Razorpay’s covers one-time payments, Paystack accounts in Egypt or Rwanda can add EGP or RWF, and Mercado Pago’s also receives the account’s site id.
  • stackless_stripe_gateway_adopt_payment_method: null, array $args, turns a payment method saved by the WooCommerce Stripe plugin into a Stackless token. $args takes payment_method, customer, user_id and mode. It returns the token id or a WP_Error.
  • stackless_paypal_gateway_adopt_vault: null, array $args, does the same for a vault id saved by WooCommerce PayPal Payments. $args takes vault_id, customer_id, user_id, email and mode.
  • stackless_paypal_gateway_saved_charge_args: array $body, \WC_Order $order, bool $on_session, the order request for a charge on a saved PayPal account or card
  • stackless_paypal_gateway_fastlane_available: bool $available, whether Fastlane is offered. The default is true for US stores charging USD.
  • stackless_paypal_gateway_card_fields_style: array $style, the look of the inputs inside PayPal’s card fields, in PayPal’s CSS subset, with keys such as input, input:focus and .invalid
  • stackless_stripe_managed_session_args: array $body, \WC_Order $order, ?\WC_Order $subscription, the Stripe Managed Payments Checkout Session request
  • stackless_stripe_managed_tax_code: string $code, \WC_Order $order, the Stripe tax code for an order (txcd_ and eight digits)
  • stackless_stripe_managed_is_digital: bool $digital, \WC_Product $product, whether a product counts as digital, so Stripe Managed Payments can sell it
  • stackless_stripe_managed_pay_page_config: array $config, \WC_Order $order, the pay page settings, such as accent
  • stackless_stripe_managed_api_version: string $version, the Stripe API version Stripe Managed Payments calls
  • stackless_creem_gateway_checkout_args: array $body, \WC_Order $order, $subscription, \WC_Payment_Gateway $gateway, and stackless_creem_gateway_product_args: array $body, array $item, ?array $billing, string $mode, \WC_Payment_Gateway $gateway
  • stackless_paystack_gateway_checkout_args, stackless_flutterwave_gateway_checkout_args and stackless_mercadopago_gateway_checkout_args: array $body, \WC_Order $order, \WC_Payment_Gateway $gateway, the request that opens each hosted checkout
  • stackless_razorpay_gateway_mandate_caps: array $caps, the mandate ceilings in rupees per method (card, upi, emandate), for businesses Razorpay allows higher limits

Billing Health and payment alerts:

  • stackless_billing_health_sources: array $sources. Add [gateway_or_module_id => callable]. The callable returns label, issues (each with severity, title, detail, url, and optionally fix and fix_label) and recent failed or stuck webhook events.
  • stackless_billing_health_fix: null, string $source_id, string $fix_key, runs a fix for your own source. Return a message string on success, a WP_Error on failure, or null when the fix isn’t yours.
  • stackless_payment_alerts_gateways: array $routes. Add [route => ['label', 'ids', 'hosts', 'class', 'mode_meta', 'live_auth']] so the failed-payment email watches your gateway. live_auth lists the credential prefixes that mark a live call, for providers whose test and live calls share a host.

Sales:

  • stackless_buy_links_redirect: string $url, int $added, array $params
  • stackless_buy_links_buy_now_supports: bool $supports, \WC_Product $product, to show Buy now on types other than simple and variable
  • stackless_request_quotes_rate_limit: how many quote requests one IP address can send before it’s told to wait, default 5. The counter clears 10 minutes after that IP’s last request.
  • stackless_custom_invoices_can_issue: true $allowed, \WC_Order $order, string $context. Return a string to block issuing with that message.
  • stackless_buy_link_url: string $url, array $items, array $args, builds a signed Buy Link from other plugins. Pass '', items as [[product_id, variation_id, qty]] and $args with coupon, clear, to and exp. It returns the URL, or '' when Buy Links is off.
  • stackless_post_purchase_merchant_of_record: string[] $gateway_ids, the gateways, copies included, that never get one-click post-purchase charges. Their buyers get a pay link instead.
  • stackless_vat_numbers_pre_check: ?array $result, string $prefix, string $number, short-circuits the VIES request. Return a parsed result, or null to ask VIES.

FluentCRM:

  • stackless_fluentcrm_triggers: array $triggers, registers FluentCRM automation triggers as [action_hook => ['kind', 'args', 'module', 'label', 'resolve']]. Checkout Recovery uses it for “Checkout abandoned (Stackless)” and “Checkout recovered (Stackless)”.

License & Updates:

  • stackless_plugin_license_server and stackless_plugin_license_item_id: the license store URL and product ID

For example, to say “Free download” instead of “Free” on downloadable products:

add_filter('stackless_free_price_text_text', function ($text, $product) {
    return ($product && $product->is_downloadable()) ? 'Free download' : $text;
}, 10, 2);

Multi-Currency Rules for Custom Code

If you add prices or fees with code while Multi-Currency Checkout is on, follow these rules:

  • Prices you set with set_price() and cart fees are base-currency amounts. Multi-Currency Checkout converts them once, so never convert them yourself.
  • To show a base amount in the visitor’s currency, use stackless_convert_amount().
  • If you create an order during a storefront request, wrap it in do_action('stackless_multi_currency_pause') and do_action('stackless_multi_currency_resume').
  • An order’s currency is fixed at checkout. Subscriptions and their renewal orders inherit it and never change.

Override Templates

Stackless templates load through WooCommerce’s template system. To change one, copy it from wp-content/plugins/stackless/templates/ into your theme’s stackless/ folder, keeping the same path below it:

wp-content/plugins/stackless/templates/wishlist/button.php
→ wp-content/themes/yourtheme/stackless/wishlist/button.php

Use a child theme so a theme update doesn’t remove your copy. These templates are overridable this way:

  • back_in_stock/form.php, back_in_stock/email-confirm.php, back_in_stock/email-available.php
  • checkout_builder/compact.php
  • checkout_enhancements/consents.php, checkout_enhancements/order-consents.php, checkout_enhancements/trust.php
  • checkout_recovery/email-reminder.php
  • creem_gateway/payment-fields.php
  • currency_selector/selector.php
  • custom_invoices/invoice.php, custom_invoices/emails/invoice.php, custom_invoices/emails/plain/invoice.php
  • delivery_slots/checkout-field.php, delivery_slots/order-details.php, delivery_slots/email.php, delivery_slots/email-plain.php
  • flutterwave_gateway/payment-fields.php
  • gift_cards/product-fields.php, gift_cards/email-gift-card.php, gift_cards/myaccount-store-credit.php
  • mercadopago_gateway/payment-fields.php
  • mollie_gateway/payment-fields.php
  • order_bumps/bumps.php
  • page_access/notice.php, page_access/my-pages.php
  • paypal_gateway/payment-fields.php
  • paystack_gateway/payment-fields.php
  • polar_gateway/payment-fields.php
  • post_purchase/offer.php
  • product_addons/product-form.php, product_addons/checkout-fields.php
  • razorpay_gateway/payment-fields.php, razorpay_gateway/checkout.php, razorpay_gateway/authorize-page.php
  • request_quotes/form.php, request_quotes/proposal.php, request_quotes/emails/quote.php, request_quotes/emails/plain/quote.php
  • saved_carts/account.php, saved_carts/cart-form.php, saved_carts/signed-out.php
  • service_products/instructions.php, service_products/email.php, service_products/email-plain.php
  • software_licensing/myaccount-licenses.php, software_licensing/order-licenses.php, software_licensing/emails/licenses-table.php, software_licensing/emails/plain/licenses-table.php
  • square_gateway/payment-fields.php
  • stripe_gateway/payment-fields.php, stripe_gateway/express-buttons.php, stripe_gateway/managed-pay-page.php
  • subscriptions/cart/recurring-totals.php
  • subscriptions/myaccount/subscriptions.php, subscriptions/myaccount/view-subscription.php, subscriptions/myaccount/manage-subscription.php
  • subscriptions/order/related-subscriptions.php
  • subscriptions/emails/ and subscriptions/emails/plain/: new-subscription.php, new-subscription-admin.php, renewal-invoice.php, renewal-paid.php, payment-failed.php, upcoming-renewal.php, trial-ending.php, switched.php, cancelled.php, expired.php, paused.php, resumed.php, card-expiring.php, subscription-details.php
  • wholesale_pricing/tier-table.php
  • wishlist/button.php, wishlist/list.php

The license keys email follows the same rule: yourtheme/stackless/software_licensing/emails/license-keys.php and .../emails/plain/license-keys.php. A copy already in yourtheme/woocommerce/software_licensing/emails/ still works until you add one under stackless/.

custom_invoices/invoice.php is used both for the customer’s invoice page and the admin print view, so an override changes both.

Shortcodes and Blocks

  • [stackless_currency_selector], also a Currency Selector block (stackless/currency-selector)
  • [stackless_request_quote], also a block (stackless/request-quote)
  • [stackless_wishlist], also a block (stackless/wishlist)
  • [stackless_saved_carts]
  • [stackless_my_pages]
  • [stackless_product_details]
  • [stackless_login_to_see_price]

Each one only works while its module is switched on.

Good to Know

  • Hooks named here only fire while their module is running. Check with stackless_module_active() if your code depends on one.
  • Two files in templates/ aren’t overrides: checkout_builder/sample.php is the admin layout preview, and software_licensing/client/class-stackless-license-client.php is the client you ship inside your own plugin or theme. Add Licensing and Automatic Updates to Your Plugin or Theme covers it.
  • The license REST API is documented in License API Reference.
Share

Was this helpful?

Still stuck? Open a support ticket