Every hook on this page exists in both builds unless it is marked . The tables, the REST namespace and the stored values are identical too.
Three words used throughout
Safety-clamped — a limit is re-applied after your filter runs, so a listener cannot produce a nonsensical result. Named per hook. Additive only — add keys freely; the plugin’s own are restored afterwards. Removing or rewriting one breaks the feature rather than extending it. Seeded conservatively — the value with no listener attached is the cautious one. Pro attaches at priority 10 like anyone else, so a listener at a later priority still has the last word.Lifecycle actions
Every “this happened” hook has a counterpart for when it is undone, so a listener can clean up what it granted without polling.cartpresets_redemptions_released is the counterpart to after_redemption_recorded. An order leaving the counted statuses gives its slot back, so a listener that granted something on record can revoke it on release.
cartpresets_carrier_product_created fires both when the plugin creates a carrier product and when a merchant points it at an existing one. A listener’s question is “what is the carrier now”, so a separate hook would only mean subscribing to both to stay correct.
Worked example: grant a membership when a preset is bought
Pricing
cartpresets_item_unit_price is the last word on what a line costs. Safety-clamped: max( 0, ... ) is re-applied afterwards, so no listener can produce a negative price and hand the customer money.
cartpresets_item_subscription_terms decides how far a preset discount reaches into a subscription, as [ 'scope' => 'first'|'all', 'waive_fee' => bool ].
firstspends the discount on the payment being made now and bills renewals at the product’s own price.allholds the preset’s price on every renewal.waive_feezeroes the product’s sign-up fee for that line.
first, fee charged — so a discount cannot become permanent by default. Safety-clamped: a returned scope that is not exactly all reads as first.
Cart
cartpresets_cart_item_data is additive only. Append your own keys freely; the plugin’s are re-applied afterwards, because the integrity rules read them and a filter rewriting an instance ID would break preset isolation for every other line in the cart.
cartpresets_cart_group_id is applied on both sides of every comparison, so overriding it changes what “the rest of this preset” means consistently rather than only where the trigger line is read.
cartpresets_clear_cart decides the outright clear — the whole cart, the customer’s own shopping included. It fires once per link, before the limit check, so the filter and the removal cannot reach different conclusions. The two narrower settings it outranks are read only when it resolves false.
cartpresets_preset_coupon is seeded empty, and shared code never reads the stored setting itself — so with nothing attached a preset applies no coupon. Honouring the stored code is Pro.
cartpresets_post_add_url is safety-clamped against the host allow-list.
cartpresets_notice_text receives one customer-facing sentence before its values are put in, with the notice ID and whether this is the plural form of a pair. See Cart notices and wording.
Availability and eligibility
cartpresets_item_is_valid is additive only in the restrictive direction: it can turn a valid item invalid — a membership gate, a regional rule — but cannot rescue one the plugin has already hard-failed. A deleted product or a missing carrier means the resulting cart line would be broken rather than merely unwanted, and the filter is not applied at all in those cases.
cartpresets_eligibility is seeded allowed => true, and every verdict comes from a listener — including the plugin’s own. On free nothing is attached and the verdict stays yes.
Worked example: a regional rule
Tier
cartpresets_preset_premium_fields covers created_at, end_at, max_total, max_per, unavailable_action, unavailable_url and coupon. cartpresets_premium_settings covers the store-wide unavailable_action and redemption_statuses.
Neither is a validation step — a rejected value is silently the stored one, and the rest of the same save goes through.
Schema
disc_type means underneath the pricing engine.
Writes walk the schema rather than the payload, so a key you have not registered here is never persisted no matter what a request contains.
Identity and keys
cartpresets_generated_slug runs before uniquification and its result is sanitised — a listener shapes the slug, it cannot break the URL.
cartpresets_redemption_customer_key decides how “the same customer” is recognised for per-customer limits. Default: user ID for members, a hashed fingerprint for guests. Return a stable key per real person.
Listing
search, status, sort with dir, per_page, page, and filters.
The pre-builder arguments compose with the filter rows: added_after / added_before (YYYY-MM-DD, inclusive bounds on the publish date, the end one running to the last second of its day), price_op with price and price_max, orders_op with orders and orders_max, plus product, stock_problem and has_coupon.
filters is either the encoded query-string form — field~op~value rows joined by |, each value percent-encoded — or an array of { field, op, value }:
price is what the customer pays; order_revenue is that price multiplied by recorded redemptions. Both are computed from live product prices, so they are applied after the SQL rather than inside it.
What you return is validated exactly like a caller’s own arguments — an unknown sort key falls back, statuses outside the ladder are dropped, rows outside the allow-lists are dropped, and every value still reaches SQL through prepare().
Admin picker
simple, variable, subscription, variable-subscription.
External, grouped and composite types are excluded because they cannot be added to a cart by product ID. Grant a custom type here only if it can.
The admin panel
licence key here, because shared code cannot name a class the free build does not ship.
Order and display
cartpresets_order_line_item_meta is additive only. cartpresets_custom_item_shipping_data and cartpresets_admin_capability are safety-clamped.
cartpresets_preset_url shapes output only. It does not affect how an incoming request is parsed — that read path is independent and is not rewired by this filter, so changing one keeps the other working.
Reporting
cartpresets/v1/sales routes are not registered at all — as with import and export, absent rather than refusing.
Updates
What is deliberately not exposed
These are the guarantees a store owner is paying for. A hook that weakened one would undermine the plugin rather than extend it.- No filter bypasses a capability or nonce check.
- No filter turns a hard validation failure back into a valid item.
- No filter bypasses purchase-limit enforcement at either stage.
- No filter disables cascade removal or weakens instance isolation.
- No filter sits between a query and
$wpdb->prepare(). - No PHP filter registers anything the React admin renders.