Action Scheduler
Action Scheduler is a background processing library that ships with WooCommerce (and many extensions) to run work later or off the request—think “do_action()
on a timer or ASAP, but not blocking page loads.” Instead of relying on transient, best-effort hooks, it provides a proper job queue with persistence, logging, retries, and tooling.
Under the hood, each action records a hook name, arguments, schedule, and optional group in dedicated database tables ({prefix}_actionscheduler_*
). A runner claims due actions (so they’re not double-processed), executes them, records status and logs, and reschedules if they’re recurring or retriable. This design scales far better than firing heavy tasks during a user’s page request and is resilient across cache layers, PHP timeouts, or traffic spikes.
Jobs are triggered by WP-Cron, a real server cron calling wp-cron.php
, or WP-CLI (recommended for high volume). Because storage is in custom tables, lookup and processing are faster and more reliable than stuffing everything into wp_options
or waiting on ad-hoc transients. Typical uses include webhook delivery, subscription renewals and payment retries, email digests, inventory syncs, migrations, and any long-running process you don’t want tied to the front-end response.
Where you see it in WooCommerce
WooCommerce → Status → Scheduled Actions lists pending, complete, failed, and canceled jobs, with tools to run or delete them.
How it works (high level)
- Triggers: WP-Cron automatically; you can also process via WP-CLI.
- Storage: Tables like
{prefix}_actionscheduler_actions
,_claims
,_groups
,_logs
. - Statuses: pending, complete, failed, canceled.
- Locking: “Claims” prevent two runners from executing the same action.
- Args delivery: Actions are fired via
do_action_ref_array( $hook, $args )
. Each element of$args
becomes a parameter; if you pass a single associative array, your callback receives that array as the first argument.
Core scheduling API
// 1) Run once in 60s
$id = as_schedule_single_action( time() + 60, 'lc_send_followup', ['order_id' => 123], 'emails' );
// 2) Recurring hourly (guard against duplicates)
if ( ! as_has_scheduled_action('lc_cleanup') ) {
as_schedule_recurring_action( time(), HOUR_IN_SECONDS, 'lc_cleanup' );
}
// 3) Cron-like (daily at 03:00)
as_schedule_cron_action( time(), '0 3 * * *', 'lc_nightly_jobs' );
// 4) Fire ASAP (non-blocking)
as_enqueue_async_action( 'lc_do_async', ['id' => 42] );
// 5) Unschedule / query
as_unschedule_all_actions( 'lc_cleanup' );
$ids = as_get_scheduled_actions([
'hook' => 'lc_send_followup',
'status' => ActionScheduler_Store::STATUS_PENDING,
'per_page' => 20,
'return_format' => 'ids',
]);
WP-CLI essentials
- Run the queue now (optionally target hooks/groups, tweak batch size):
wp action-scheduler run --group=woocommerce --batch-size=200
- Clean old rows (e.g., shorten retention window):
wp action-scheduler clean --before='14 days ago' --status=complete,failed
- Explore more:
wp action-scheduler --help
Best practices
- Use real cron or CLI for production sites with volume.
- Group related actions for easier filtering and targeted runs.
- Guard against duplicates with
as_has_scheduled_action()
or the$unique
flag. - Monitor backlogs in WooCommerce → Status → Scheduled Actions.
- Prune table bloat periodically (default retention is ~1 month).
Troubleshooting
- Backlog or “past due” → verify WP-Cron is firing, switch to a server cron/CLI runner, or manually run pending actions.
- Actions not firing → confirm the hook callback is registered when the runner executes (e.g., after plugins load).
- Large args → keep payloads small; store big data elsewhere and pass references/IDs.