wp-action-scheduler
Design and review Action Scheduler jobs in WordPress plugins using Action Scheduler 3.9.x public APIs - async, single, recurring, and cron-expression actions; action_scheduler_init load timing; hook/args/group naming; unique and priority parameters; idempotent callbacks; chunked workloads; activation/deactivation cleanup; WooCommerce-bundled or standalone dependency usage; admin and WP-CLI debugging; queue runner limits; and safe operational troubleshooting. Use when a plugin schedules background jobs with as_enqueue_async_action, as_schedule_single_action, as_schedule_recurring_action, as_schedule_cron_action, as_get_scheduled_actions, or integrates with WooCommerce background queues.
Best use case
wp-action-scheduler is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Design and review Action Scheduler jobs in WordPress plugins using Action Scheduler 3.9.x public APIs - async, single, recurring, and cron-expression actions; action_scheduler_init load timing; hook/args/group naming; unique and priority parameters; idempotent callbacks; chunked workloads; activation/deactivation cleanup; WooCommerce-bundled or standalone dependency usage; admin and WP-CLI debugging; queue runner limits; and safe operational troubleshooting. Use when a plugin schedules background jobs with as_enqueue_async_action, as_schedule_single_action, as_schedule_recurring_action, as_schedule_cron_action, as_get_scheduled_actions, or integrates with WooCommerce background queues.
Teams using wp-action-scheduler should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/wp-action-scheduler/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How wp-action-scheduler Compares
| Feature / Agent | wp-action-scheduler | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Design and review Action Scheduler jobs in WordPress plugins using Action Scheduler 3.9.x public APIs - async, single, recurring, and cron-expression actions; action_scheduler_init load timing; hook/args/group naming; unique and priority parameters; idempotent callbacks; chunked workloads; activation/deactivation cleanup; WooCommerce-bundled or standalone dependency usage; admin and WP-CLI debugging; queue runner limits; and safe operational troubleshooting. Use when a plugin schedules background jobs with as_enqueue_async_action, as_schedule_single_action, as_schedule_recurring_action, as_schedule_cron_action, as_get_scheduled_actions, or integrates with WooCommerce background queues.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# WordPress plugin: Action Scheduler
Action Scheduler is a WordPress-native job queue used by WooCommerce and many
high-volume plugins. It is not just a nicer `wp_schedule_event()` wrapper: it
stores actions in queue tables, tracks status, claims batches, logs attempts,
supports groups, and can run through WP-Cron, async loopback requests, admin
tools, and WP-CLI.
Use `wp-plugin-cron` first when the main question is "native WP-Cron or Action
Scheduler?". Use this skill once the answer is Action Scheduler or the plugin
already depends on it.
## When to load references
- Need copy-ready scheduling/callback patterns for async, single, recurring,
cron-expression, unique, chunked, and activation/deactivation flows: read
[references/api-patterns.md](references/api-patterns.md).
- Debugging stuck queues, failed actions, duplicate jobs, table/schema problems,
or WP-CLI/admin operations: read
[references/operational-debugging.md](references/operational-debugging.md).
## Misconception this skill corrects
> "Action Scheduler means the callback will run once, soon, and in order."
Wrong mental model. Action Scheduler is an at-least-once background queue. Jobs
can run late, fail, be retried manually, be re-created by recurring schedules,
or be triggered by WP-CLI/admin tools. Callbacks must be idempotent and should
advance durable state, not rely on "this hook only fires once".
## When to use this skill
Trigger when ANY of the following is true:
- Scheduling with `as_enqueue_async_action()`, `as_schedule_single_action()`,
`as_schedule_recurring_action()`, or `as_schedule_cron_action()`.
- Replacing many WP-Cron single events with a real queue.
- Building WooCommerce order/customer/subscription/membership background work.
- Reviewing duplicate actions, stuck `pending` actions, `failed` actions, or
oversized queue tables.
- Adding WP-CLI or admin debugging instructions for queued jobs.
- Deciding whether to use `$unique`, `as_has_scheduled_action()`, or
`as_next_scheduled_action()` guards.
## Dependency and load timing
Action Scheduler can be present as:
- WooCommerce bundled package.
- Standalone plugin.
- Composer package bundled by another plugin.
Do not assume your plugin owns the loaded version. Action Scheduler registers
available versions and initializes the latest registered version. In local
3.9.3, registration happens on `plugins_loaded` priority `0`, initialization
loads the procedural API, and `action_scheduler_init` fires when the store,
logger, runner, admin view, and recurring scheduler are ready.
Rules:
- Register your job callbacks on every request, early enough for runners:
`add_action( 'myplugin/process_order', ... )` should not be hidden behind an
admin-only screen load.
- Schedule actions on or after `action_scheduler_init` when scheduling at
runtime.
- In activation hooks, guard with `function_exists( 'as_schedule_single_action' )`
before calling the API. If Action Scheduler is an optional dependency, fall
back to WP-Cron or show an admin notice.
- Never schedule at plugin file top-level before WordPress and dependencies
load.
```php
add_action( 'action_scheduler_init', static function (): void {
if ( ! as_has_scheduled_action( 'myplugin/hourly_sync', array(), 'myplugin' ) ) {
as_schedule_recurring_action(
time() + HOUR_IN_SECONDS,
HOUR_IN_SECONDS,
'myplugin/hourly_sync',
array(),
'myplugin',
true
);
}
} );
```
## Public API in Action Scheduler 3.9.3
Scheduling functions return an action ID as `int`; `0` means scheduling failed.
| Function | Use |
|---|---|
| `as_enqueue_async_action( $hook, $args = array(), $group = '', $unique = false, $priority = 10 )` | Run once as soon as possible. |
| `as_schedule_single_action( $timestamp, $hook, $args = array(), $group = '', $unique = false, $priority = 10 )` | Run once at/after a Unix timestamp. |
| `as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args = array(), $group = '', $unique = false, $priority = 10 )` | Fixed interval recurrence in seconds. |
| `as_schedule_cron_action( $timestamp, $schedule, $hook, $args = array(), $group = '', $unique = false, $priority = 10 )` | Cron-expression recurrence. |
| `as_unschedule_action( $hook, $args = array(), $group = '' )` | Cancel the next pending matching action. |
| `as_unschedule_all_actions( $hook, $args = array(), $group = '' )` | Cancel all pending matching actions. |
| `as_next_scheduled_action( $hook, $args = null, $group = '' )` | Return next timestamp, `true` for async/running, or `false`. |
| `as_has_scheduled_action( $hook, $args = null, $group = '' )` | Efficient boolean check for pending/running actions. |
| `as_get_scheduled_actions( $args = array(), $return_format = OBJECT )` | Query actions by hook, group, status, date, etc. |
| `as_get_datetime_object( $date_string = null, $timezone = 'UTC' )` | Build AS DateTime object for queries. |
| `as_supports( $feature )` | Feature detection. In 3.9.3 it supports `ensure_recurring_actions_hook`. |
The `$priority` parameter is queue priority, not callback priority. Lower
numbers run first; 3.9.3 expects `0-255` and defaults to `10`.
## Hook, args, and group rules
- Use namespaced hook names: `myplugin/process_order`, not `process_order`.
- Use one stable group per plugin or feature: `myplugin`, `myplugin-import`,
`myplugin-webhooks`.
- Keep action args small and JSON-serializable. The 3.9.3 DB store can keep
larger encoded args in `extended_args`, but validates against an 8000-character
encoded limit and still hashes/indexes args for lookup.
- Pass identifiers, not large DTOs, `WC_Order` objects, full API payloads, or
secrets. Reload current state inside the callback.
- Callback args are passed positionally with `do_action_ref_array( $hook,
array_values( $args ) )`. Associative keys are for storage/query readability,
not named parameter delivery.
- Unscheduling matches the hook/args/group combination you pass. For per-entity
jobs, pass the exact args. For deactivation, prefer canceling a plugin-owned
group with an empty hook if the group is exclusive to your plugin.
```php
as_enqueue_async_action(
'myplugin/process_order',
array( 'order_id' => 123 ),
'myplugin'
);
add_action(
'myplugin/process_order',
static function ( int $order_id ): void {
myplugin_process_order( $order_id );
},
10,
1
);
```
## Unique actions
In 3.9.3, the scheduling functions support `$unique`. The public docs in the
local source say a unique action is not scheduled when another pending or
running action has the same hook and group parameters. The 3.9.3 DB store
matches pending/running uniqueness by hook and group, not by args.
Use `$unique = true` only for "only one pending/running copy of this hook+group
should exist", such as a global reindex, import coordinator, or recurring sync.
Do not use it for per-order or per-user jobs under one hook/group unless only
one outstanding job for the whole group is intended.
For per-entity duplicate suppression, check the exact args with
`as_has_scheduled_action()` and still make the callback idempotent. That guard is
not a substitute for durable state because it is not a hard business lock.
```php
if ( function_exists( 'as_enqueue_async_action' ) ) {
$ref = new ReflectionFunction( 'as_enqueue_async_action' );
if ( $ref->getNumberOfParameters() >= 4 ) {
as_enqueue_async_action( 'myplugin/reindex', array(), 'myplugin', true );
} elseif ( ! as_has_scheduled_action( 'myplugin/reindex', array(), 'myplugin' ) ) {
as_enqueue_async_action( 'myplugin/reindex', array(), 'myplugin' );
}
}
```
Do not use uniqueness as the only safety mechanism for payment capture,
inventory mutation, email send, or external API side effects. The callback must
still check durable state.
```php
$args = array( 'order_id' => $order_id );
if ( ! as_has_scheduled_action( 'myplugin/process_order', $args, 'myplugin' ) ) {
as_enqueue_async_action( 'myplugin/process_order', $args, 'myplugin' );
}
```
## Idempotent callback pattern
```php
add_action(
'myplugin/capture_payment',
static function ( int $order_id ): void {
$order = wc_get_order( $order_id );
if ( ! $order ) {
return;
}
if ( 'yes' === $order->get_meta( '_myplugin_payment_captured', true ) ) {
return;
}
myplugin_capture_payment_for_order( $order );
$order->update_meta_data( '_myplugin_payment_captured', 'yes' );
$order->save();
},
10,
1
);
```
For failures that should be retried or surfaced, throw an exception. For
permanent no-op cases, return cleanly. Swallowing all exceptions makes failures
look complete and hides broken jobs from the admin UI and logs.
## Recurring actions
For plugin-owned recurring actions:
- Register the callback on every request.
- Schedule idempotently on activation and/or `action_scheduler_init`.
- Clear on deactivation with exact hook, args, and group.
- Use `action_scheduler_ensure_recurring_actions` in AS 3.9.3+ when you need a
repair hook for recurring actions that may have been deleted manually.
```php
add_action( 'action_scheduler_ensure_recurring_actions', static function (): void {
if ( ! function_exists( 'as_supports' ) || ! as_supports( 'ensure_recurring_actions_hook' ) ) {
return;
}
if ( ! as_has_scheduled_action( 'myplugin/hourly_sync', array(), 'myplugin' ) ) {
as_schedule_recurring_action(
time() + HOUR_IN_SECONDS,
HOUR_IN_SECONDS,
'myplugin/hourly_sync',
array(),
'myplugin',
true
);
}
} );
```
## Statuses, tables, and runner model
Core statuses in 3.9.3:
- `pending`
- `in-progress`
- `complete`
- `failed`
- `canceled`
The DB store uses prefixed tables based on these base names:
- `actionscheduler_actions`
- `actionscheduler_claims`
- `actionscheduler_groups`
- `actionscheduler_logs`
Do not write direct SQL for normal plugin behavior. Use the public API, admin
UI, or WP-CLI. Direct SQL is acceptable only for emergency diagnostics with a
backup and site-specific approval.
The default queue runner schedules WP-Cron hook `action_scheduler_run_queue`
every minute and can dispatch async admin-context requests on shutdown. Default
web runner batch size is filterable through
`action_scheduler_queue_runner_batch_size` and defaults to `25`.
## WP-CLI and admin debugging
Admin UI: Tools -> Scheduled Actions.
Common WP-CLI commands in 3.9.3:
```bash
wp action-scheduler action list --group=myplugin --status=pending
wp action-scheduler action next myplugin/process_order --group=myplugin
wp action-scheduler action get 123 --format=json
wp action-scheduler action logs 123
wp action-scheduler action run 123
wp action-scheduler run --group=myplugin --batch-size=25 --batches=1
wp action-scheduler clean --status=complete,canceled --before='31 days ago'
wp action-scheduler fix-schema
```
Use WP-CLI for deterministic local/dev runs and for production debugging when
the web runner is too slow or loopback requests are blocked.
## Critical rules
- Use `action_scheduler_init` as the safe runtime scheduling point.
- Register callbacks on every request, not only inside admin pages or AJAX
handlers.
- Keep args small, scalar/array, JSON-serializable, and non-sensitive.
- Treat args as positional callback params; array keys are not named params.
- Prefer plugin-prefixed hook names and stable group names.
- Use `$unique = true` only for hook+group singleton suppression; still make
callbacks idempotent.
- Use `as_has_scheduled_action()` for a boolean guard; use
`as_next_scheduled_action()` only when you need the timestamp.
- Throw for real job failures; return for permanent no-op cases.
- Do not mutate AS tables directly in normal plugin code.
- For large workloads, enqueue chunks/cursors instead of one massive action.
## Common mistakes
```php
// WRONG - callback hidden in an admin screen, runner cannot find it from WP-Cron.
if ( is_admin() && isset( $_GET['page'] ) && 'myplugin' === $_GET['page'] ) {
add_action( 'myplugin/process_order', 'myplugin_process_order' );
}
// WRONG - passes a large payload and secrets through queued args.
as_enqueue_async_action( 'myplugin/send_payload', $full_api_payload, 'myplugin' );
// WRONG - associative args treated as named callback parameters.
add_action( 'myplugin/process_order', static function ( array $args ): void {
myplugin_process_order( $args['order_id'] );
}, 10, 1 );
// RIGHT - pass ID, reload state, receive positional callback arg.
as_enqueue_async_action(
'myplugin/process_order',
array( 'order_id' => $order_id ),
'myplugin'
);
add_action( 'myplugin/process_order', 'myplugin_process_order', 10, 1 );
// WRONG - this only matches empty-arg actions for this hook+group. It will not
// clear per-order jobs scheduled with array( 'order_id' => $order_id ).
as_unschedule_all_actions( 'myplugin/process_order', array(), 'myplugin' );
// RIGHT - exact hook + args + group for one per-entity job.
as_unschedule_all_actions(
'myplugin/process_order',
array( 'order_id' => $order_id ),
'myplugin'
);
// RIGHT - on deactivation, clear all pending actions in an exclusive
// plugin-owned group.
as_unschedule_all_actions( '', array(), 'myplugin' );
```
## Cross-references
- Run `wp-plugin-cron` before this when choosing between WP-Cron and Action
Scheduler.
- Run `wp-plugin-lifecycle` for activation/deactivation structure and multisite
activation behavior.
- Run `wp-plugin-dto` when queued args should hydrate a stable input object
inside the callback.
- Run `wp-plugin-presenter` when an action produces admin/REST/email output.
- Run `wp-security-audit` for callbacks processing persisted IDs, external API
payloads, or user-supplied data.
## What this skill does NOT cover
- Building an external queue on Redis, SQS, RabbitMQ, or Beanstalkd.
- Forking or replacing Action Scheduler internals.
- Native WP-Cron basics; use `wp-plugin-cron`.
- WooCommerce-specific business rules for orders/subscriptions/memberships;
combine this with the relevant WooCommerce skill.
## Source notes
Validated against the local Action Scheduler 3.9.3 plugin installed at
`wp-content/plugins/action-scheduler` on 2026-04-29:
- `functions.php` public API signatures.
- `ActionScheduler::init()` and `action_scheduler_init` timing.
- `ActionScheduler_Action::execute()` positional arg delivery.
- `ActionScheduler_Store` statuses and DB store args length behavior.
- `ActionScheduler_QueueRunner` runner hook and batch size.
- WP-CLI command classes under `classes/WP_CLI`.Related Skills
wcs-renewal-scheduler
Safely integrate with WooCommerce Subscriptions renewal, next-payment scheduling, Action Scheduler events, and retry flow. Shows when to use WC_Subscription::update_dates, update_status, wcs_create_renewal_order, wcs_renewal_order_created, woocommerce_scheduled_subscription_payment, woocommerce_scheduled_subscription_payment_{gateway_id}, woocommerce_subscription_renewal_payment_complete, and payment retry hooks. Use when changing next payment dates, forcing/rescheduling renewals, reacting to failed renewals, building gateway recurring charge logic, or debugging missing/duplicate renewal orders.
jfb-form-action
Registers a custom JetFormBuilder Form Action — a server-side handler that runs after submit (send to API, subscribe to CRM, write to a sheet, etc.). Covers extending Base action class, declaring settings via action_attributes(), implementing do_action() with Action_Exception error reporting, building the action-editor React panel via window.JetFBActions.addAction(), the two field-mapping patterns (dynamic "Add row" like Google Sheets vs fixed-key pattern like Fluent CRM with predefined target fields), looking up the form's current fields via the useFields() hook, multi-select with FormLabeledTokenField, plus the category/docHref convention. Use when scaffolding a JFB integration plugin (Mailchimp, Slack, custom API, payment processor, CRM subscribe). Triggers on mentions of "JFB action", "jet-form-builder/actions/register", "Base_Action", "JetFBActions.addAction", "Action_Exception", "field map", or "fields_map".
jfb-action-messages
Surfaces user-facing custom messages from a JetFormBuilder custom Form Action — both the idiomatic path (register message types via 'jet-form-builder/form-messages/register' so they appear in the form's Messages panel and can be overridden globally per form) and the action-local path (custom message fields inside the action editor, dispatched via Action_Exception for errors or via context + 'jet-form-builder/form-handler/after-send' + Messages_Manager::dynamic_success() for success messages). Use when a custom JFB action needs configurable messages for cases like "already subscribed", "duplicate row skipped", "API rate limited", or per-action success copy. Triggers on mentions of "JFB messages", "Action_Exception", "Base_Action_Messages", "jet-form-builder/form-messages/register", "_jf_messages", "dynamic_success", "add_context_once" with a message, "after-send" hook, or "custom action message".
jfb-action-item-decorator
Wraps every action item in the JetFormBuilder action editor with custom UI via the 'jet.fb.action.item' wp.hooks filter — the wrapper renders on top of (or alongside) the original action editor for each action and can read/write that action's settings and events array. Use to add quick toggles or panels to every action without modifying each action's own editor — e.g. a TRUE/FALSE/Always button group that drives which custom event the action responds to, a "run once per session" toggle, an inline label override, any visual shortcut over the action's persisted state. Triggers on mentions of "jet.fb.action.item", "useLoopedAction", "useActionsEdit", "useActions", "ActionItemWrapper", "ActionItemBody", "decorate every action", "per-action toggle", or "visual control over action events".
jfb-action-external-api
How to read submitted JetFormBuilder form data from a custom action, transform it (including %macro% replacement of admin-typed templates), call an external HTTP API, write the response back into the form context for downstream actions to use, and dispatch JFB events based on the API outcome. Use when building a custom action that integrates with a third-party service (LLM call, webhook, CRM, payment processor, geolocation lookup) and needs the action to behave as a node in a data-flow graph rather than a one-shot leaf. Triggers on mentions of "jet_fb_context", "update_request", "has_field", "get_value", "wp_remote_post" with form data, "macro replacement", or "API call from action".
jfb-action-events
Configures the JetFormBuilder action events system — the named events (DEFAULT.PROCESS, DEFAULT.REQUIRED, GATEWAY.SUCCESS, GATEWAY.FAILED, BAD.REQUEST, ON.DYNAMIC_STATE, never) that decide WHEN a Form Action runs. Covers declaring supported / unsupported / required events on a custom action, the validation precedence inside Base_Event::is_valid_action(), the multi-event subscription model the action editor exposes, and how to register a brand-new event type via the 'jet-form-builder/event-types' filter using Base_Event, Base_Action_Event, or Base_Gateway_Event. Use when an action needs to run only on payment callbacks, only on validation failure, only on a specific conditional state, after another action's hidden flow, or when the plugin needs to define its own event class (e.g. for inbound webhooks). Triggers on mentions of "JFB events", "action events", "GATEWAY.SUCCESS", "DEFAULT.PROCESS", "supported_events", "unsupported_events", "get_required_events", "Base_Event", "Base_Gatewa...
webapp-testing
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
web-artifacts-builder
Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.
valyu
Search the live web and 36+ specialised data sources including SEC filings, PubMed, ChEMBL, clinical trials, FRED economic indicators, and patent databases. Use when current, authoritative, or paywalled data is required.
ui-ux-pro-max
AI-powered design intelligence with 67 UI styles, 161 color palettes, 57 font pairings, 99 UX guidelines, and 25 chart types across 15+ tech stacks. Generates complete design systems for any product type with industry-specific reasoning rules.
theme-factory
Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors/fonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly.
slack-gif-creator
Knowledge and utilities for creating animated GIFs optimized for Slack. Provides constraints, validation tools, and animation concepts. Use when users request animated GIFs for Slack like "make me a GIF of X doing Y for Slack."