Skip to content
Gaurav TiwariDocs

Hooks, Functions and Templates Reference

Updated October 17, 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.

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_subscription, 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-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.

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

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

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

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

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
  • 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

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.

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
  • 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
  • order_bumps/bumps.php
  • page_access/notice.php, page_access/my-pages.php
  • paypal_gateway/payment-fields.php
  • polar_gateway/payment-fields.php
  • product_addons/product-form.php, product_addons/checkout-fields.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
  • stripe_gateway/payment-fields.php, stripe_gateway/express-buttons.php
  • subscriptions/cart/recurring-totals.php
  • subscriptions/myaccount/subscriptions.php, subscriptions/myaccount/view-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, subscription-details.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