Hooks: Actions & Filters
Every action and filter ShapedRecover fires: when it runs, what it passes, and a working example. Start with the ones marked for site owners.
A hook is a named point in the plugin's code where your own code can run. An action lets you do something at that moment. A filter hands you a value, and you return it, changed or unchanged. Every ShapedRecover hook starts with shaped_recover_.
Before you start
Where to put your code. Use a small custom plugin (best) or your child theme's functions.php. Never edit ShapedRecover's own files: an update replaces them and your changes are gone.
Hook in once ShapedRecover has loaded. The plugin fires shaped_recover_loaded once WooCommerce is confirmed and its data is ready. Add your hooks from there, so they stay quiet whenever the plugin is dormant (for example while WooCommerce is deactivated).
There's one catch. That action fires on plugins_loaded, and a theme's functions.php loads after that. So in a theme the action has already fired. This pattern works in both places:
/**
* My ShapedRecover tweaks.
*/
function mystore_shaped_recover_tweaks() {
// add_filter( 'shaped_recover_…', … ) calls go here.
}
if ( did_action( 'shaped_recover_loaded' ) ) {
mystore_shaped_recover_tweaks(); // Theme: the plugin has already loaded.
} else {
add_action( 'shaped_recover_loaded', 'mystore_shaped_recover_tweaks' ); // Plugin.
}The examples below show just the add_filter() or add_action() call. Put it inside a function like the one above.
Two kinds of hooks. Most hooks on this page are meant for you: they change timing, limits and links in supported ways, and the plugin checks or clamps what you return. A few exist so ShapedRecover Pro can plug into the free plugin. Those are marked Advanced below. They work, but a mistake there can lock features, break the admin screens or block saves, so only use them if you know exactly why.
All hooks at a glance
| Hook | Type | Group | For |
|---|---|---|---|
shaped_recover_loaded | Action | Boot | Everyone |
shaped_recover_pro_loaded | Action | Boot | Everyone |
shaped_recover_track_email | Filter | Tracking | Site owners |
shaped_recover_sweep_interval | Filter | Sending & scheduling | Site owners |
shaped_recover_dispatch_batch | Filter | Sending & scheduling | Site owners |
shaped_recover_dispatch_budget | Filter | Sending & scheduling | Site owners |
shaped_recover_sweep | Action | Sending & scheduling | Site owners |
shaped_recover_daily | Action | Sending & scheduling | Site owners |
shaped_recover_coupon_before_save | Action | Coupons | Site owners |
shaped_recover_help_links | Filter | Dashboard & admin | Site owners |
shaped_recover_dashboard | Filter | Dashboard & admin | Advanced |
shaped_recover_admin_enqueued | Action | Dashboard & admin | Advanced |
shaped_recover_admin_payload | Filter | Dashboard & admin | Advanced |
shaped_recover_capabilities | Filter | Dashboard & admin | Advanced |
shaped_recover_settings_updated | Action | Settings | Site owners |
shaped_recover_settings_errors | Filter | Settings | Advanced |
shaped_recover_settings_fields | Filter | Settings | Internal only |
shaped_recover_schema_version | Filter | Schema | Internal only |
shaped_recover_schema_tables | Filter | Schema | Internal only |
shaped_recover_schema_migrations | Filter | Schema | Internal only |
Boot
The plugin loads as a chain of actions: WooCommerce is checked, then the free plugin fires shaped_recover_loaded, then Pro checks the free plugin's version and fires shaped_recover_pro_loaded. If anything in the chain is missing or too old, the chain stops there with an admin notice. Nothing half-loads.
shaped_recover_loaded
Action. Fires on plugins_loaded (priority 20) once WooCommerce is active and at least version 8.3, and the stored data isn't from a newer version of ShapedRecover. (After an update, the tables are brought up to date first; any data migration then runs in the background as the shaped_recover_migrate job, so it may still be running when this fires.) ShapedRecover's own features hook in here at priority 5, so anything you add at the default priority 10 runs after them.
- Parameters: none.
- Doesn't fire when WooCommerce is missing or too old, or the database was written by a newer version of ShapedRecover than the one installed. In those cases the plugin shows a notice and stays dormant, and so does anything you hooked here.
add_action( 'shaped_recover_loaded', function () {
add_filter( 'shaped_recover_sweep_interval', function () {
return 2 * MINUTE_IN_SECONDS;
} );
} );shaped_recover_pro_loaded
Action. Fired by ShapedRecover Pro, from inside shaped_recover_loaded, once Pro has confirmed the free plugin is new enough. Pro's own features hook in here at priority 5.
- Parameters: none.
- Doesn't fire when Pro isn't installed, the free plugin is dormant, or the free plugin is older than Pro requires.
Use it only for code that needs Pro to be present. It tells you Pro is loaded, not that the license is active. To ask whether a Pro feature is available right now, use shaped_recover_can().
add_action( 'shaped_recover_pro_loaded', function () {
// Pro is installed and running on this request.
} );Tracking
shaped_recover_track_email
Filter. Decides whether ShapedRecover may capture or update a cart for an email address. It runs each time the plugin is about to save a shopper's cart (when the cart changes, or when they type at checkout), after its own checks have passed: tracking is on, the consent box is ticked, the user's role isn't excluded, the visit isn't stopped, and the address is valid.
| Parameter | Type | Description |
|---|---|---|
$track | bool | Whether to capture. true unless an earlier filter said no. |
$email | string | The shopper's address, trimmed and lower-cased. |
Return: bool. Return false to skip this address.
- Returning
falseskips the save. It doesn't delete a cart that was already captured for that address. To remove those too, callshaped_recover_delete_carts_by_email(). - Pro's Never Track These Addresses list uses this same filter. If you add your own, keep an earlier
false: return$trackwhen your rule doesn't apply, never a plaintrue.
// Never capture carts for staff test accounts on your own domain.
add_filter( 'shaped_recover_track_email', function ( $track, $email ) {
if ( '@mystore.com' === substr( $email, -12 ) ) {
return false;
}
return $track;
}, 10, 2 );Note the 10, 2 at the end: without it WordPress passes only the first parameter.
Sending & scheduling
ShapedRecover's background work runs through Action Scheduler, the job runner that ships with WooCommerce. A recurring job called the sweep moves cart statuses on (Shopping to Abandoned to Lost) and then sends any recovery emails that are due. A daily job does the clean-up. See How Sending Works for the full picture.
shaped_recover_sweep_interval
Filter. The number of seconds between sweeps. Read on admin page loads, when the plugin checks that its jobs exist.
| Parameter | Type | Description |
|---|---|---|
$interval | int | Seconds between sweeps. Default 300 (5 minutes). |
Return: int, seconds.
- Clamped to 60–3,600. A value below 60 becomes 60; above 3,600 becomes 3,600. A value that isn't a number is ignored and the default is used.
- When it takes effect: the next time someone opens a WordPress admin page, the sweep job is replaced with one at the new interval. Its first run is one interval later.
- What it changes: both how soon a cart is marked Abandoned after its cutoff and how close to its send time an email goes out. A shorter interval means more background runs, so on a busy shared host leave the default unless you have a reason.
// Sweep every 2 minutes.
add_filter( 'shaped_recover_sweep_interval', function () {
return 2 * MINUTE_IN_SECONDS;
} );shaped_recover_dispatch_batch
Filter. How many due emails one sending run may send. Read at the start of every run.
| Parameter | Type | Description |
|---|---|---|
$batch | int | Emails per run. Default 50. |
Return: int.
- Clamped to 1–500. A value that isn't a number is ignored.
- When a run reaches its batch size with more emails still due, it queues one follow-up run straight away rather than waiting for the next sweep. So a smaller batch doesn't slow a backlog much; it spreads it over more, shorter runs.
- Lower it if your mail provider limits how many messages you can send at once.
// Send at most 20 emails per run.
add_filter( 'shaped_recover_dispatch_batch', function () {
return 20;
} );shaped_recover_dispatch_budget
Filter. How many seconds one sending run may spend sending. The same limit applies to the Send Next Email bulk action on the Abandoned tab, where carts it doesn't reach in time are left due for the next sweep, and to Delete Old Records Now in Settings → Data & Storage, where the rest is handed to a background shaped_recover_purge job.
| Parameter | Type | Description |
|---|---|---|
$budget | int | Seconds per run. Default 20. |
Return: int, seconds.
- Clamped to 5–25. The ceiling keeps every run inside Action Scheduler's 30-second limit. A value that isn't a number is ignored.
- A run that runs out of time with emails still due queues a follow-up run, just as with the batch size.
- Lower it if your SMTP connection is slow and runs are timing out; there's no benefit in raising it on a fast mailer.
// Keep each run short on a slow SMTP connection.
add_filter( 'shaped_recover_dispatch_budget', function () {
return 10;
} );shaped_recover_sweep
Action. The recurring sweep job itself, run by Action Scheduler every 5 minutes by default (see shaped_recover_sweep_interval). ShapedRecover attaches its own work at these priorities:
| Priority | What runs |
|---|---|
| 10 | Cart statuses move on (Shopping → Abandoned, Abandoned → Lost). |
| 20 | Due recovery emails send. |
| 30 | Pro's admin notifications go out. |
- Parameters: none.
- Attach light, quick work only. Everything on the sweep shares Action Scheduler's time limit, and a slow callback delays recovery emails. Never schedule your own recurring job for something the sweep can carry.
// Runs after statuses move and emails send.
add_action( 'shaped_recover_sweep', function () {
// Quick work only, e.g. update a cached count your theme displays.
}, 40 );shaped_recover_daily
Action. The daily maintenance job, run by Action Scheduler at 03:00 in your site's time zone. At priority 0 it schedules tomorrow's run; at priority 10 it runs the retention clear-out and coupon cleanup. Pro also re-checks its license here once a week.
- Parameters: none.
- The time stays at 03:00 site time even after a late start or a daylight saving change.
add_action( 'shaped_recover_daily', function () {
// Nightly housekeeping of your own.
}, 20 );Coupons
shaped_recover_coupon_before_save
Action. Fires when ShapedRecover generates a new coupon for a recovery email, after it has set the coupon's rules and just before it saves it.
- Fires only for campaigns using Generate a new code, and only while coupons are enabled in WooCommerce → Settings → General. A campaign set to Use an existing coupon never creates a coupon, so this doesn't fire.
- Fires once per email. If sending is retried, the coupon already made for that email is reused, unless it has expired in the meantime; then a new one is made and this fires again.
| Parameter | Type | Description |
|---|---|---|
$coupon | WC_Coupon | The new coupon, not yet saved. Change it in place; there's nothing to return. |
$options | array | The campaign's cleaned coupon options, e.g. type (percent or fixed_cart), amount, expires_value, expires_unit (hours or days), free_shipping, auto_apply, exclude_products. |
$campaign | array | The campaign row, e.g. id, name. |
$cart | array | The cart row, e.g. id, email, total, currency, contents. |
Pro uses this action to apply Allow Coupons to Combine, Exclude Sale Items and Exclude Products, at the default priority 10. Use a later priority if you want the last word.
Keep the safety rules
Every generated coupon is locked to the shopper's email, usable once, and expires on schedule. Don't loosen those (email restrictions, usage limits, expiry) here, or a code shared from one shopper's email could be used by anyone.
// Add a minimum spend to generated coupons for one campaign.
add_action( 'shaped_recover_coupon_before_save', function ( $coupon, $options, $campaign, $cart ) {
if ( 'Final reminder' === $campaign['name'] ) {
$coupon->set_minimum_amount( 50 );
}
}, 20, 4 );Dashboard & admin
shaped_recover_help_links
Filter. The URLs behind the Help menu (the Help & Resources button in the top bar of every ShapedRecover tab). Useful for agencies that want Contact Support to reach their own help desk.
| Parameter | Type | Description |
|---|---|---|
$links | array<string, string> | URL for each menu item, keyed documentation, videos, forum, contact, whats_new. |
Return: the same array.
- Only those five keys are used; extra keys are ignored. The menu labels stay as they are.
- Each URL is cleaned with
esc_url_raw(). A missing, empty or invalid URL falls back to the default, so you only need to return the ones you change.
| Key | Help menu item |
|---|---|
documentation | Documentation |
videos | Video Tutorials |
forum | Support Forum |
contact | Contact Support |
whats_new | What's New |
add_filter( 'shaped_recover_help_links', function ( $links ) {
$links['contact'] = 'https://agency.example.com/support/';
return $links;
} );shaped_recover_dashboard
Filter. Advanced: used by ShapedRecover Pro. The whole Dashboard response for a date range, just before it's sent to the admin screen. Pro adds the Product Report's rows here as a products key.
| Parameter | Type | Description |
|---|---|---|
$overview | array | The Dashboard: first_run, setup, kpis, buckets, rate_carts, series, recent. |
$bounds | array | The range: start_gmt and end_gmt, UTC Y-m-d H:i:s. |
$sales | array | The range's credited sales, as shaped_recover_recovered_sales() returns them. |
Return: the array. A non-array is ignored.
The Dashboard only draws the keys it knows, so a key you add won't show up on screen. Changing the existing figures would make the Dashboard disagree with Reports, so don't. If you need the numbers, call the public functions instead.
shaped_recover_admin_enqueued
Action. Advanced: used by ShapedRecover Pro. Fires on ShapedRecover's admin page, right after the admin app's script is enqueued and before the app starts. Pro enqueues its own admin script here.
| Parameter | Type | Description |
|---|---|---|
$handle | string | The admin app's script handle, shaped-recover-admin. |
Make your script depend on $handle, so it loads after the app's registry is ready. The admin app's JavaScript registry isn't documented as a public API yet, so anything you build on it may need changes after an update.
add_action( 'shaped_recover_admin_enqueued', function ( $handle ) {
wp_enqueue_script( 'mystore-sprcv', plugins_url( 'admin.js', __FILE__ ), array( $handle ), '1.0.0', true );
} );shaped_recover_admin_payload
Filter. Advanced: used by ShapedRecover Pro. The data the admin app reads when it loads (version, capabilities, links, money format and so on). Pro adds a pro key with its version and license state.
| Parameter | Type | Description |
|---|---|---|
$payload | array | The boot data. |
Return: the array. A non-array is ignored.
Only add keys under your own prefix. Changing or removing existing keys can break the admin screens. The data is printed into the page for every user who can open ShapedRecover, so never add secrets.
shaped_recover_capabilities
Filter. Advanced: used by ShapedRecover Pro. The list of feature keys available on this request. This is how Pro switches its features on while its license is active or expired. Pro's keys are never_track, coupon_restrictions, coupon_exclude_products, product_report_widget and admin_notifications.
| Parameter | Type | Description |
|---|---|---|
$keys | string[] | Capability keys. Empty in the free plugin alone. |
Return: string[]. A non-array is ignored.
- Adding a key unlocks nothing. A key only counts when code that implements it has registered with
shaped_recover_register_feature(). In the free plugin alone, addingnever_trackhere leaves the field locked. - Removing a key works, and is the one sensible use: it switches a Pro feature off on this site, so it shows locked as in the free plugin. Stored settings stay untouched, and the feature comes back when you remove your filter.
- The list is worked out on every request and never saved.
// Lock the Product Report on this site, even with Pro licensed.
add_filter( 'shaped_recover_capabilities', function ( $keys ) {
return array_values( array_diff( (array) $keys, array( 'product_report_widget' ) ) );
}, 20 );Use a priority above 10 so your filter runs after Pro's.
Settings
shaped_recover_settings_updated
Action. Fires after a save on the Settings tab has passed validation and been written.
| Parameter | Type | Description |
|---|---|---|
$clean | array | The values just saved, keyed by setting key. Only the fields that were submitted, not every setting. |
$previous | array | The stored settings before the save. A setting that was never saved isn't in it. |
- Doesn't fire when the save was rejected or nothing was submitted.
- Check
array_key_exists()before reading a key from$clean. A save from one section doesn't include the others. - Setting keys are listed under
shaped_recover_setting().
// Log when someone switches tracking on or off.
add_action( 'shaped_recover_settings_updated', function ( $clean, $previous ) {
if ( array_key_exists( 'tracking_enabled', $clean ) ) {
error_log( 'ShapedRecover tracking is now ' . ( $clean['tracking_enabled'] ? 'on' : 'off' ) );
}
}, 10, 2 );shaped_recover_settings_errors
Filter. Advanced: used by ShapedRecover and Pro for rules that span several fields. Runs during a settings save, after each value has passed its own check and before anything is written. Add an error to reject the save.
| Parameter | Type | Description |
|---|---|---|
$errors | array | Errors so far. Each is array( 'code' => string, 'field' => string, 'message' => string ). |
$clean | array | The validated values about to be saved. Only the submitted fields. |
Return: the errors array. If it holds any errors, nothing is saved and the screen shows "Fix the highlighted setting to save" with your message under the field you named.
- Every error must have a
codeand afield(a setting key). Add a translatedmessageso the store owner knows what to fix. - Always return
$errors, adding to it. Returning a fresh array throws away the plugin's own checks, such as the rule that Mark Cart as Lost After must be at least a day longer than your slowest active campaign. - Only reject a save for a real reason, and say what to change. A store owner who can't save Settings can't turn tracking off either.
// Require a Reply-to Address on this store.
add_filter( 'shaped_recover_settings_errors', function ( $errors, $clean ) {
if ( array_key_exists( 'reply_to', $clean ) && '' === $clean['reply_to'] ) {
$errors[] = array(
'code' => 'sprcv_invalid_setting',
'field' => 'reply_to',
'message' => __( 'Add a reply-to address so shoppers can answer your emails.', 'mystore' ),
);
}
return $errors;
}, 10, 2 );shaped_recover_settings_fields
Filter. Internal only. The list of settings fields with their types, defaults and limits. It exists for the plugin's automated tests; shipped code never uses it. The admin screen has no way to show a field you add, and changing an existing field's type or limits can make stored values fail validation. Don't use it.
Schema
These filters let the plugin's test suite stage database upgrades. Internal only: shipped code never uses them, and changing them on a live store can leave its data in a state no release can read. They're listed here so you know what they are if you come across them, not to be used.
shaped_recover_schema_version
Filter. Internal only. The database schema version this build supports, an integer. If the stored version is higher than this, the plugin stops loading and shows a notice.
shaped_recover_schema_tables
Filter. Internal only. The table definitions the plugin creates and updates, keyed by unprefixed name (carts, campaigns, sends, tokens, unsubscribes).
shaped_recover_schema_migrations
Filter. Internal only. The data migrations, keyed by the schema version each one brings the data up to.
Where to go next
- Functions & Constants: read carts, settings and sales from your own code.
- How Sending Works: the sweep, batches and retries these filters tune.
- Requirements: keeping background jobs on time.
- Coupons in Emails: what a generated coupon looks like before your changes.
- Exclusions: the built-in ways to stop tracking an address or role.