---
title: "wp_actionscheduler_actions: Table Schema & Columns"
description: "wp_actionscheduler_actions database schema — every column in the actions, claims, groups and logs tables, their indexes, and how the queue runner uses them."
url: "https://wpwebhooks.org/blog/action-scheduler-database-tables/"
date: "2026-07-23"
---

# wp_actionscheduler_actions: Table Schema & Columns

**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 a `claim_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](https://actionscheduler.org/) — 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](https://developer.wordpress.org/plugins/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](https://actionscheduler.org/admin/) reads directly from them.

FIG 01 — How the four Action Scheduler tables work together

/ 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](https://wpwebhooks.org/blog/action-scheduler-api-functions/) 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](https://wpwebhooks.org/blog/woocommerce-action-scheduler-mysql-deadlocks/).

> 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`](https://developer.wordpress.org/reference/classes/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](https://actionscheduler.org/perf/) 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](https://wpwebhooks.org/blog/action-scheduler-api-functions/).

## Structured data

```json
{"@context":"https://schema.org","@type":"Article","headline":"wp_actionscheduler_actions: Table Schema & Columns","description":"wp_actionscheduler_actions database schema — every column in the actions, claims, groups and logs tables, their indexes, and how the queue runner uses them.","datePublished":"2026-07-23","dateModified":"2026-07-23","author":{"@type":"Person","name":"Mateusz Skorupa","url":"https://wpwebhooks.org/about/"},"publisher":{"@type":"Organization","name":"WP Webhooks","url":"https://wpwebhooks.org"},"url":"https://wpwebhooks.org/blog/action-scheduler-database-tables/","image":"https://wpwebhooks.org/og_image.jpg","keywords":["wp actionscheduler actions","wp actionscheduler claims","action scheduler database tables","action scheduler schema","action scheduler mysql tables","wp actionscheduler groups","wp actionscheduler logs"]}

{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"WP Webhooks","item":"https://wpwebhooks.org/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://wpwebhooks.org/blog/"},{"@type":"ListItem","position":3,"name":"wp_actionscheduler_actions: Table Schema & Columns","item":"https://wpwebhooks.org/blog/action-scheduler-database-tables/"}]}

{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{"@type":"Question","name":"How many database tables does Action Scheduler create?","acceptedAnswer":{"@type":"Answer","text":"Four, all prefixed wp_actionscheduler_: actions (one row per scheduled job), claims (one row per batch a runner locks), groups (a lookup of group slugs), and logs (one row per lifecycle event or attempt)."}},{"@type":"Question","name":"What is the wp_actionscheduler_claims table for?","acceptedAnswer":{"@type":"Answer","text":"It hands out claim IDs. A queue runner inserts one row to get a unique claim_id, then stamps that id onto a batch of due pending actions so no other runner can process the same rows. When the batch finishes the claim row is deleted."}},{"@type":"Question","name":"What does the claim_id column in wp_actionscheduler_actions mean?","acceptedAnswer":{"@type":"Answer","text":"A non-zero claim_id means a runner has locked that action for execution. Zero means the action is unclaimed and available. The runner sets it in an UPDATE that also filters on claim_id = 0, which is where concurrent runners can deadlock under load."}},{"@type":"Question","name":"Can I query or delete Action Scheduler rows directly in MySQL?","acceptedAnswer":{"@type":"Answer","text":"You can read them freely for debugging. Deleting rows by hand is risky: prefer the as_unschedule_* functions or the admin Scheduled Actions screen, and only bulk-purge completed/failed rows once you understand the foreign relationships to the logs table."}},{"@type":"Question","name":"Why do the Action Scheduler tables grow so large?","acceptedAnswer":{"@type":"Answer","text":"Completed and failed actions are retained (default 30 days) along with their log rows for auditing. High-volume sites accumulate millions of rows; the built-in cleanup task trims old actions, and actionscheduler.org documents tuning the retention window."}}]}
```
