TL;DR: Action Scheduler stores every background job across four MySQL tables, all prefixed wp_actionscheduler_.
wp_actionscheduler_actions— one row per scheduled job (hook, args, status, scheduled date).wp_actionscheduler_claims— one row per batch a runner locks, handing out aclaim_id.wp_actionscheduler_groups— a slug lookup so actions can be grouped and filtered.wp_actionscheduler_logs— one row per lifecycle event and execution attempt.
/ Overview
What tables does Action Scheduler create?
Action Scheduler — the background-job library maintained by WooCommerce and bundled inside WooCommerce, Jetpack, and hundreds of other plugins — creates four custom tables the first time it loads. On a standard single install they are wp_actionscheduler_actions, wp_actionscheduler_claims, wp_actionscheduler_groups, and wp_actionscheduler_logs (your prefix replaces wp_). Together they replace the single serialised cron option that WP-Cron uses, giving each job its own indexed row, a lock, a group, and an audit trail.
The default data store is the custom-table store shipped with the library. Older or constrained installs can fall back to a post-type store that keeps actions in wp_posts, but the custom tables are the standard and the ones you will inspect in production. The Scheduled Actions admin screen reads directly from them.
/ actions
What columns are in wp_actionscheduler_actions?
This is the core table — one row is one scheduled job. Every function in the Action Scheduler PHP API ultimately writes or reads a row here. The columns that matter most when you are debugging a stuck queue are status, scheduled_date_gmt, and claim_id.
| Column | Type | What it holds |
|---|---|---|
action_id | bigint | Primary key. The integer returned by the scheduling functions. |
hook | varchar | The action hook fired via do_action() when the job runs. |
status | varchar | pending, in-progress, complete, failed, or canceled. |
scheduled_date_gmt | datetime | Earliest time the action may run. The runner claims rows whose value is in the past. |
args | varchar | JSON-encoded arguments spread into the callback. A hash is also stored for dedup matching. |
group_id | bigint | Foreign key into wp_actionscheduler_groups. |
claim_id | bigint | 0 when unclaimed; a claim row id while a runner holds the action. |
attempts | int | How many times the runner has tried to execute the action. |
last_attempt_gmt | datetime | Timestamp of the most recent execution attempt. |
The args column is indexed via a stored hash rather than the raw JSON, which is how the $unique flag can match "the same hook, args and group" quickly. Keep argument payloads small — large serialised arrays bloat both this column and every dedup lookup.
/ claims
What is the wp_actionscheduler_claims table for?
The claims table is how Action Scheduler stops two queue runners from executing the same job. It is deliberately tiny — essentially just an id and a timestamp. When a runner wants work, it inserts one claim row to mint a fresh claim_id, then runs an UPDATE that stamps that id onto a batch of due, unclaimed actions.
SQL — how a runner claims a batch (simplified)
-- 1. mint a claim id INSERT INTO wp_actionscheduler_claims (date_created_gmt) VALUES (UTC_TIMESTAMP()); -- claim_id = LAST_INSERT_ID() -- 2. lock a batch of due, unclaimed actions UPDATE wp_actionscheduler_actions SET claim_id = :claim_id, last_attempt_gmt = UTC_TIMESTAMP() WHERE claim_id = 0 AND status = 'pending' AND scheduled_date_gmt <= UTC_TIMESTAMP() ORDER BY scheduled_date_gmt LIMIT 25;
Because that UPDATE filters on claim_id = 0, only one runner can win each row. When the batch finishes, the claim row is deleted and the actions' claim_id resets. This is also the exact statement that can deadlock when several runners scan overlapping rows at once — the subject of Action Scheduler MySQL deadlocks under concurrent workers.
The claims table is a lock table. A non-zero claim_id on an action means "a runner owns this row" — that single column is what makes concurrent processing safe. — Action Scheduler data store / groups + logs
What do the groups and logs tables store?
wp_actionscheduler_groups is a simple lookup: an group_id and a slug. The free-text group you pass to a scheduling function (for example 'order-webhooks') is normalised into one row here, and actions reference it by group_id. That is what powers the group filter in the admin UI and bulk operations like as_unschedule_all_actions().
wp_actionscheduler_logs records the lifecycle. Each row ties an action_id to a message and a timestamp — "action created", "action started via WP Cron", "action complete", or the exception message when a run throws. This is the history that raw WP-Cron events can never give you, and it is what the admin screen renders when you expand an action's log.
| Table | Key columns | Role |
|---|---|---|
wp_actionscheduler_groups | group_id, slug | Normalises group labels; actions reference it by id. |
wp_actionscheduler_logs | log_id, action_id, message, log_date_gmt | Per-event and per-attempt audit trail shown in wp-admin. |
/ Inspecting
How do you inspect the tables safely with SQL?
Reading is safe and often the fastest way to diagnose a backlog. A count of actions grouped by status tells you at a glance whether the queue is draining or stuck. Use the $wpdb object rather than a hard-coded prefix so the query works on any install.
PHP — how healthy is the queue right now?
global $wpdb; $rows = $wpdb->get_results( "SELECT status, COUNT(*) AS n FROM {$wpdb->prefix}actionscheduler_actions GROUP BY status" ); // pending piling up while in-progress stays high = runner is stalled
Do not delete rows by hand as a cleanup strategy. Actions are linked to log rows, and an in-progress action still referenced by a live claim will confuse the runner. Cancel through as_unschedule_action() / as_unschedule_all_actions() or the admin screen, and let Action Scheduler's own retention task trim completed and failed rows.
/ Maintenance
Why do these tables grow, and how is that managed?
Completed and failed actions are retained so you can audit them — by default for 30 days — together with every log row. On a busy WooCommerce store that is easily millions of rows. Action Scheduler ships a built-in cleanup action that deletes old, finished actions past the retention window; the performance guide documents tuning batch sizes and the retention period for high-throughput sites. If your actions and logs tables are enormous, the fix is tuning retention and indexes — not truncating the tables.
For the functions that create and query all of these rows — scheduling, cancelling, and checking whether an action exists — see the Action Scheduler PHP API reference.