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.