Skip to content
Gaurav TiwariDocs

Hooks, Functions and Templates

Version 1.0.0-beta.1 Updated October 28, 2026

Sooner or later a store needs one thing done differently: a CRM that hears about every renewal, a help desk button next to a license, a template with your own markup. Editing plugin files works until the next update wipes the change.

Stackless for EDD gives you safe places to hook in instead: public PHP functions, actions and filters with the stackless_edd_ prefix, two PHP interfaces and templates you can copy into your theme. Every signature on this page comes from the plugin’s 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. Each one is safe to call whether or not its module is on. With the module off you get a neutral default: false, 0, an empty array, null, the base currency or a WP_Error.

Modules

  • stackless_edd_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, software_licensing, multi_currency.

Subscriptions

  • stackless_edd_get_subscription($subscription_id) returns a SubscriptionData object or null.
  • stackless_edd_get_customer_subscriptions(int $user_id, array $args = []): array takes status (array of status slugs) and download_id in $args.
  • stackless_edd_customer_has_active_subscription(int $user_id, int $download_id = 0): bool counts active, trialing and pending-cancel subscriptions. 0 means any download.
  • stackless_edd_is_subscription_product(int $download_id, ?int $price_id = null): bool checks a download or one of its price options.
  • stackless_edd_get_order_subscriptions(int $order_id): array returns subscriptions created or renewed by an order.
  • stackless_edd_order_contains_subscription(int $order_id): bool
  • stackless_edd_cart_contains_subscription(): bool
  • stackless_edd_cart_subscription_count(): int is how many subscriptions the cart will create, one per billing schedule.
  • stackless_edd_is_renewal_order(int $order_id): bool
  • stackless_edd_order_requires_saved_payment_method(int $order_id): bool and stackless_edd_cart_requires_saved_payment_method(): bool tell a gateway it must save a reusable payment method.
  • stackless_edd_create_renewal_order($subscription) returns a pending renewal order ID or a WP_Error.
  • stackless_edd_query_subscriptions(array $args = []): array pages through subscriptions. $args: status, customer_id, user_id, download_id, page, per_page (1 to 100). Returns ['items' => SubscriptionData[], 'total' => int].
  • stackless_edd_get_subscription_orders(int $subscription_id): array returns the parent, renewal and switch orders, newest first.
  • stackless_edd_cancel_subscription(int $subscription_id, bool $at_period_end = true, string $by = 'admin') returns true or a WP_Error. $by is admin, customer or system and shows in the notes.
  • stackless_edd_reactivate_subscription(int $subscription_id, string $by = 'admin') withdraws a pending cancellation or resumes an on-hold subscription.
  • stackless_edd_update_subscription_status(int $subscription_id, string $status, string $note = '') moves a subscription to a SubscriptionData::STATUS_* status. Use the cancel and reactivate functions for those two changes.
  • stackless_edd_add_subscription_note(int $subscription_id, string $note): void

Licenses, Invoices and Quotes

  • stackless_edd_get_license(int $license_id): ?array returns id, license_key, download_id, price_id, order_id, subscription_id, user_id, customer_id, customer_email, status (stored), effective_status (expiry applied), activation_limit, activations, expires_at, created_at and updated_at (UTC, Y-m-d H:i:s).
  • stackless_edd_get_customer_licenses(int $user_id, string $email = ''): array includes guest purchases made with the same email, newest first.
  • stackless_edd_get_customer_invoices(int $user_id, string $email = '', array $args = []): array returns issued invoices, never drafts. See Contracts for Other Plugins below for the fields.
  • stackless_edd_get_customer_quotes(int $user_id, string $email = '', array $args = []): array returns quote requests, newest first.

Currency

  • stackless_edd_current_currency(): string is the visitor’s cart currency.
  • stackless_edd_base_currency(): string is the store currency from Downloads → Settings → General.
  • stackless_edd_price_decimals(string $currency = ''): int and stackless_edd_base_price_decimals(): int follow EDD’s decimal rules, such as 0 for JPY.
  • stackless_edd_enabled_currencies(): array lists ISO codes enabled for checkout.
  • stackless_edd_convert_amount($amount, string $to = '', string $from = '') converts at the configured rate with no charm rounding. $to defaults to the visitor’s currency, $from to the base currency.
  • stackless_edd_currency_rate(string $currency = ''): float is the rate from base, markup included. It’s 0 when unknown and 1 when the module is off.
  • stackless_edd_switch_currency(string $currency, string $source = 'api') returns true, a WP_Error or false when the module is off.
  • stackless_edd_currency_signup_fee(float $fee, int $download_id, ?int $price_id = null, string $currency = ''): float

Orders, Gateways and Payments

  • stackless_edd_get_gateway(string $gateway_id): ?\StacklessEDD\Core\Gateway is null for EDD’s own and third-party gateways.
  • stackless_edd_complete_order(int $order_id, string $transaction_id = '', string $note = ''): bool records the transaction and moves the order to complete. Safe to call twice.
  • stackless_edd_add_order_note(int $order_id, string $note): void
  • stackless_edd_order_pay_url(int $order_id): string is EDD’s order recovery link, where a customer pays a pending order.
  • stackless_edd_get_saved_payment_methods(int $user_id, string $gateway_id = ''): array returns id, gateway, label, is_default and expires per method.
  • stackless_edd_add_payment_method_url(string $gateway_id, int $subscription_id = 0): string is empty when the gateway can’t do it.
  • stackless_edd_cart_item_is_locked(int $download_id, array $options): bool and stackless_edd_cart_has_locked_items(): bool. Invoice lines and renewal or switch payments are locked: leave their price, quantity and fees alone.

Money, Notices, Account and Agents

  • stackless_edd_price($amount, string $currency = ''): string formats a price with its symbol, like EDD does on the storefront.
  • stackless_edd_decimal($amount, ?int $decimals = null, string $currency = ''): string returns a rounded decimal string.
  • stackless_edd_add_notice(string $message, string $type = 'success'): void shows a shopper message on the next page view. Types: success, error, info.
  • stackless_edd_account_url(string $tab = '', array $args = []): string links to a tab of the [stackless_edd_account] page, such as stackless_edd_account_url('subscriptions', ['id' => 4]).
  • stackless_edd_account_item_actions_html(string $type, int $id, $data): string returns what plugins printed on stackless_edd_account_item_actions.
  • stackless_edd_agent(string $command, array $input = []): array runs an agent command as the signed-in user. See AI Agents.
  • stackless_edd_get_template(string $name, array $args = []): void and stackless_edd_get_template_html(string $name, array $args = []): string load a template (see Override Templates).

Subscription Statuses and Contracts

Subscriptions live in their own tables and implement StacklessEDD\Contracts\SubscriptionData. Renewal and switch orders are ordinary EDD orders linked through order meta. Dates are UTC DateTimeImmutable objects, or null.

Methods: get_id(), get_status(), get_customer_id() (EDD customer), get_user_id() (0 for guests), get_email(), get_currency(), get_gateway(), get_parent_order_id(), get_items(), get_meta(), update_meta(), is_active(), get_billing_period(), get_billing_interval(), get_next_payment_date(), get_trial_end_date(), get_end_date(), set_next_payment_date(), has_product(int $download_id), get_renewal_mode() (auto or manual) and get_recurring_total().

Hooks pass the internal status slugs. The REST API and webhooks show the public names.

ConstantSlug in hooksPublic name
STATUS_PENDINGpendingpending
STATUS_TRIALtriallingtrial
STATUS_ACTIVEactiveactive
STATUS_ON_HOLDon_holdon-hold
STATUS_PENDING_CANCELpending_cancelpending-cancel
STATUS_CANCELLEDcancelledcancelled
STATUS_EXPIREDexpiredexpired

Order meta constants on the same interface: META_TOKEN_ID (_stackless_edd_payment_token_id), META_GATEWAY_CUSTOMER (_stackless_edd_gateway_customer_id) and META_GATEWAY_SUBSCRIPTION (_stackless_edd_gateway_subscription_id) on the parent order, and META_RENEWAL_OF (_stackless_edd_renewal_of) and META_SWITCH_OF (_stackless_edd_switch_subscription_id) on renewal and switch orders.

Gateways That Renew Automatically

A gateway built on StacklessEDD\Core\Gateway renews subscriptions by implementing StacklessEDD\Contracts\SubscriptionGateway:

  • stackless_edd_supports(string $feature): bool for auto_renewal, multiple_subscriptions, zero_total_setup, payment_method_change, amount_change, date_change and gateway_managed.
  • stackless_edd_charge_renewal(\EDD\Orders\Order $renewal_order, SubscriptionData $subscription): array returns result (success, pending or failed) and an optional message. It must be idempotent per renewal order, and on success it calls stackless_edd_complete_order() itself.
  • stackless_edd_cancel_subscription(SubscriptionData $subscription, bool $at_period_end): bool cancels at the provider for gateway-managed billing. Others return true.

Third-party EDD gateways have no gateway object. Subscriptions bought with them renew manually: a pending renewal order plus a payment link.

Core and Settings Hooks

HookTypeArguments
stackless_edd_loadedactionStacklessEDD\Core\Plugin $plugin, after enabled modules have started
stackless_edd_settings_after_{module_id}actionnone; prints inside a module’s settings form, after its fields
stackless_edd_settings_saved_{module_id}actionnone; runs after a module’s settings are saved
stackless_edd_admin_field_{type}actionarray $field; prints a custom settings field row
stackless_edd_settings_sanitize_option and stackless_edd_settings_sanitize_option_{id}filter$value, array $field, $raw; return null to skip saving
stackless_edd_account_tabsfilterarray $tabs; each tab has title, icon, priority, callback
stackless_edd_account_page_idfilterint $page_id
stackless_edd_emailsfilterarray $emails listed on Stackless → Emails
stackless_edd_defer_scriptfilterbool $defer, string $handle
stackless_edd_live_storefilterbool $live, string $state
stackless_edd_documentation_urlfilterstring $url, string $key

Here’s a tab added to the account page. The callback receives the tab’s query arguments:

add_filter('stackless_edd_account_tabs', function (array $tabs) {
    $tabs['support'] = [
        'title'    => 'Support',
        'icon'     => 'sparkles',
        'priority' => 80,
        'callback' => function (array $query) {
            echo '<p>Need help? Reply to any receipt email.</p>';
        },
    ];
    return $tabs;
});

stackless_edd_documentation_url changes where the Documentation and “How to set up” links in the Stackless admin point, for example to an internal wiki. $key is the module ID or a screen key such as dashboard, emails or account.

Subscription Actions

The Subscriptions module fires these cross-module actions:

ActionArguments
stackless_edd_subscription_createdSubscriptionData $subscription, int $parent_order_id
stackless_edd_subscription_status_changedSubscriptionData $subscription, string $from, string $to
stackless_edd_subscriptions_status_{to}SubscriptionData $subscription, string $from, for one target status
stackless_edd_subscription_renewal_order_createdint $renewal_order_id, SubscriptionData $subscription
stackless_edd_subscription_renewal_paidSubscriptionData $subscription, int $renewal_order_id
stackless_edd_subscription_payment_failedSubscriptionData $subscription, int $renewal_order_id
stackless_edd_subscription_switchedSubscriptionData $subscription, int $old_download_id, int $new_download_id, int $switch_order_id
stackless_edd_subscription_updatedint $subscription_id, after an admin, customer or gateway edit
stackless_edd_subscription_deletedint $subscription_id
stackless_edd_subscriptions_payment_method_changedSubscriptionData $subscription, string $gateway_id, $token_id

A small example that logs every cancellation:

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

To show a download’s subscription terms, ask apply_filters('stackless_edd_subscription_product_terms', null, int $download_id, ?int $price_id). Subscriptions answers period, interval, trial_length, trial_period, signup_fee (in the visitor’s currency) and times, or null for a price that isn’t a subscription.

Other Module Actions

ActionArguments
stackless_edd_software_licensing_keys_createdint[] $license_ids, \EDD\Orders\Order $order
stackless_edd_software_licensing_license_updatedint $license_id, array $data
stackless_edd_software_licensing_site_activatedint $license_id, string $site, bool $is_local
stackless_edd_software_licensing_site_deactivatedint $license_id, string $site_url
stackless_edd_software_licensing_order_revoked$order, $revoked
stackless_edd_software_licensing_package_download$license, int $download_id, $package, $version
stackless_edd_custom_invoices_paid\EDD\Orders\Order $order
stackless_edd_request_quotes_acceptedint $quote_id, $issued (the issued invoice order)
stackless_edd_payment_method_savedstring $gateway_id, string $token_id, int $user_id

Contracts for Other Plugins

Help desks, CRMs and agents read Stackless through these. They’re kept stable: keys get added, never renamed or removed. Each returns its neutral default when the owning module is off.

ContractOwnerReturns
stackless_edd_get_customer_invoices(int $user_id, string $email = '', array $args = [])Custom Invoicesid, number, title, status (sent, overdue, paid, cancelled, refunded), status_label, open, total, currency, due_date, issued_at, admin_url, customer_url, pay_url
stackless_edd_get_customer_quotes(int $user_id, string $email = '', array $args = [])Request a Quoteid, number, title, status (requested, proposal, offered, expired, accepted, declined, withdrawn), status_label, open, total, currency, due_date, admin_url, customer_url
apply_filters('stackless_edd_subscription_admin_url', '', $id)Subscriptionsthe subscription’s edit screen; $id is an ID or a SubscriptionData object
apply_filters('stackless_edd_license_admin_url', '', int $license_id)Software Licensingthe license’s screen under Stackless → Licenses
apply_filters('stackless_edd_invoice_admin_url', '', int $invoice_id)Custom Invoicesthe invoice’s screen under Stackless → Invoices
apply_filters('stackless_edd_license_product_terms', null, int $download_id, ?int $price_id)Software Licensingactivation_limit (0 = unlimited), validity (lifetime, subscription, days), validity_days, version, requires_wp, tested_wp, requires_php, software_type (plugin, theme), or null
do_action('stackless_edd_account_item_actions', string $type, int $id, $data)Subscriptions and Software Licensing templatesfires inside each item’s actions area on the account page; $type is subscription (with the SubscriptionData object) or license (with the license array)

Invoice and quote $args take open (bool, only open items) and number (1 to 100, default 20). Here’s a help desk button beside each license:

add_action('stackless_edd_account_item_actions', function ($type, $id, $data) {
    if ($type === 'license') {
        printf('<a class="button" href="%s">Get help</a>', esc_url(home_url('/support/?license=' . $id)));
    }
}, 10, 3);

Multi-Currency Rules for Custom Code

  • An order’s currency is fixed at checkout. Subscriptions and their renewal orders keep it.
  • Runtime cart prices and cart fees are base-currency amounts. Multi-Currency converts them once, so never pre-convert them.
  • If your code adds a surcharge to a cart item’s stored price, also return that base-currency surcharge from stackless_edd_runtime_price_surcharge ($surcharge, int $download_id, array $options). It starts as null. Return it unchanged for items you didn’t touch.
  • If your code scales the stored price, return the factor from stackless_edd_runtime_price_ratio ($ratio, int $download_id, array $options), such as 0.6 for 40% off.
  • Labels that show a base amount convert it with stackless_edd_convert_amount().
  • Wrap order creation during a storefront request in do_action('stackless_edd_multi_currency_pause') and do_action('stackless_edd_multi_currency_resume').
  • CLI or staging scripts that need storefront prices: add_filter('stackless_edd_multi_currency_is_storefront', '__return_true'), then do_action('stackless_edd_multi_currency_reset_context').

Gateway Copies and Integrations

Polar, Paddle and Dodo Payments can run as several copies, one per provider account: the original ID plus <id>_copyN, where N is 2 to 10. Each copy has its own settings, webhook URL and orders.

HookTypeArguments
stackless_edd_gateway_copy_familiesfilterarray $families; a gateway module declares itself copyable
stackless_edd_gateway_copies_panelfilterstring $html, string $gateway_id
stackless_edd_gateway_copy_addedactionstring $copy_id, string $family_id
stackless_edd_gateway_copy_removedactionstring $copy_id, string $family_id

Before you sync a customer or order to an outside service, ask apply_filters('stackless_edd_skip_integrations', false, string $email). True means the address is on the store’s internal and test list from Extra Enhancements. Orders from those addresses carry the order meta _stackless_edd_internal_email set to yes.

Override Templates

Front-end and email markup comes from the plugin’s templates/ folder, one subfolder per module. stackless_edd_get_template() looks in your theme first, so copy a file to the same path under a stackless-edd folder in your theme or child theme:

wp-content/plugins/stackless-edd/templates/subscriptions/account/view-subscription.php
wp-content/themes/your-child-theme/stackless-edd/subscriptions/account/view-subscription.php

Some templates you might override:

  • account/account.php: the tabbed account page
  • subscriptions/account/subscriptions.php and subscriptions/account/view-subscription.php
  • subscriptions/emails/*.php: subscription email bodies, such as renewal-invoice.php and payment-failed.php
  • software_licensing/account-licenses.php and software_licensing/emails/licenses-table.php
  • custom_invoices/invoice.php and custom_invoices/emails/invoice.php
  • request_quotes/form.php and request_quotes/proposal.php
  • wishlist/button.php and wishlist/list.php
  • currency_selector/selector.php and checkout_builder/compact.php

To load a file from somewhere else entirely, use the stackless_edd_template filter (string $file, string $name, array $args). Overrides of view-subscription.php and account-licenses.php must keep their stackless_edd_account_item_actions_html() call, or other plugins’ buttons disappear.

Good to Know

  • Other modules are reached only through these functions, hooks and contracts. Don’t create another module’s classes directly. Their internals can change between releases.
  • The webhook and REST filters are listed in Webhooks and REST API, and the agent command filters in AI Agents.
  • Checkbox settings save yes or no, and options are named stackless_edd_{module_id}_{key}.
  • Template overrides don’t update with the plugin. After an update, compare your copy with the plugin’s file, and check the changelog for what changed.
  • The licensing client in templates/software_licensing/client/ is a file you copy into your own plugin, not a theme template.
Share

Was this helpful?

Still stuck? Open a support ticket