Queuety
The WordPress workflow engine.
wp plugin install https://github.com/inline0/queuety/releases/latest/download/queuety.zip --activateLatest release v0.22.0 GPL-2.0-or-later WordPress 6.4+ and PHP 8.2+
use Queuety\Queuety;
Queuety::register( 'send_email', SendEmailHandler::class );
Queuety::dispatch( 'send_email', [
'to' => 'user@example.com',
'subject' => 'Welcome',
'body' => 'Hello from Queuety!',
] );In practice
A workflow defined in PHP and the worker command that runs it.
An approval workflow
The workflow runs a step, waits 24 hours, then pauses at wait_for_approval() until another process sends the approval signal.
The delay is stored in the database, so it survives worker restarts and deploys, and a signal sent early is recorded and picked up when the workflow reaches the wait. Cancelling the run calls the on_cancel() handler.
$workflow_id = Queuety::workflow( 'approval_flow' )
->then( SubmitRequestHandler::class )
->delay( hours: 24 )
->wait_for_approval()
->then( ProcessApprovalHandler::class )
->on_cancel( CleanupHandler::class )
->dispatch( [ 'request_id' => 99 ] );
// Later, from another process:
Queuety::signal( $workflow_id, 'approval', [ 'approved_by' => 'admin@example.com' ] );# Single worker on default queue
wp queuety work
# Process a specific queue
wp queuety work --queue=emails
# Process multiple queues in priority order
wp queuety work --queue=critical,default,low
# One-shot mode
wp queuety work --once
# Multi-worker mode
wp queuety work --workers=4
# Adaptive worker pool
wp queuety work --queue=providers --min-workers=2 --max-workers=6Direct MySQL Claims
Each worker runs in a long-lived WP-CLI process and claims jobs straight from MySQL with FOR UPDATE SKIP LOCKED.
Durable Workflows
Multi-step processes with persistent state that survive PHP timeouts, crashes, and retries.
State Machines
Long-lived, event-driven state with Queuety::machine(): define states and transitions, send events, read the timeline.
Signals and Approvals
wait_for_signal(), wait_for_approval() and wait_for_input() pause a workflow until external input arrives.
Priority Levels
Four levels from Low to Urgent. Within a queue, higher priority jobs are claimed first.
Rate Limiting
Per-handler limits over a sliding window. A worker that claims a rate-limited job puts it back and waits.
Recurring Jobs
Interval and cron-based scheduling. Built-in cron parser with no external dependencies.
Parallel Steps
Run workflow steps concurrently and wait for all to complete before advancing.
Metrics & Logging
A permanent database log with throughput, average and p95 duration, and error rate per handler.
WP-Cron Fallback
Without shell access, a once-a-minute WordPress cron event runs a one-shot worker for the default queue.
Middleware
Onion-style job middleware for rate limits, exception throttling, uniqueness, timeouts and overlap protection.
PHP 8.2+
Enums, readonly classes, match expressions, constructor promotion, and PHP attributes.
Why Queuety
State lives in MySQL
Queue rows, workflow state, logs, signals and streamed chunks are stored in MySQL, so a worker restart loses no orchestration state. Each step boundary commits the state update, the job completion and the next enqueue in one transaction.
Off the page request
WP-Cron only fires on page visits, and a 60-second LLM call gets killed by a 30-second PHP timeout. Queuety workers run in a long-lived WP-CLI process and pick up work without waiting for traffic.
Guardrails for agent runs
for_each() with a quorum mode, approval gates and limits such as max_transitions() and max_state_bytes() keep long agent runs inside a defined envelope.
Usable outside WordPress
The database-backed core runs in any PHP process that can open the same database, so another app can dispatch jobs, send signals and read workflow status. Action triggers, the WP-Cron bridge and the WP-CLI commands still need WordPress.
Compared with
How Queuety relates to the schedulers and workflow engines WordPress developers already know.
| Tool | Good at | Difference with Queuety |
|---|---|---|
| WP-Cron | Good atBuilt into WordPress, it needs no access to the system scheduler on shared hosting, and a task that misses its time runs at the next page load instead of being dropped. | Difference with QueuetyQueuety workers run independently of site traffic and need a long-running wp queuety work process. Its WP-Cron bridge turns recurring events into schedules but does not run their hook callbacks yet, so WP-Cron stays in charge of them. |
| Action Scheduler | Good atMade for distribution inside plugins and themes with no server access required, it processes millions of payments for Subscriptions and webhooks for WooCommerce every month, and logs every action in an admin interface. | Difference with QueuetyAction Scheduler runs a hook at a later time, like a delayed do_action(). Queuety adds multi-step workflows whose state persists between steps, signal and approval waits and state machines, and you inspect them through WP-CLI commands such as wp queuety workflow timeline. |
| Temporal | Good atA durable execution platform: an Event History records each step of a workflow, so a run resumes from the last recorded event after crashes or outages, and Temporal Cloud is offered as an alternative to hosting the service yourself. | Difference with QueuetyA Temporal Service is a Go server plus a database that runs next to your application. Queuety keeps its state in MySQL tables on the WordPress database connection and runs workers through WP-CLI. |
Questions and answers
Short answers from the Queuety documentation.
Not for simple jobs on the default queue. The plugin runs a one-shot worker through WordPress cron every minute, as long as WordPress cron is firing, and it handles one job per run. Recurring schedules, custom queues and lower latency need a dedicated wp queuety work process.
No. It needs either mysqli or pdo_mysql and picks mysqli by default under WordPress. Define QUEUETY_DB_DRIVER as auto, mysqli or pdo to force a driver.
Jobs are claimed with SELECT ... FOR UPDATE SKIP LOCKED, which MySQL supports from 8.0 and MariaDB from 10.6. The project's CI runs against MySQL 8.0.
Yes, the database-backed core: jobs, workers, workflows, signals, state machines, artifacts, logs, metrics and schedules run against a direct Connection. Worker pools also need the DB_HOST, DB_NAME, DB_USER and DB_PASSWORD constants. Queuety::on_action(), replace_wp_cron(), the plugin bootstrap and the wp queuety commands still require WordPress.
Not yet. Queuety::replace_wp_cron() turns recurring wp_schedule_event() events into Queuety schedules, but their hook callbacks do not run yet, so keep WP-Cron for events that must fire. Events from wp_schedule_single_event() stay with WP-Cron.
wp queuety work --workers=N forks a fixed pool and restarts children that crash; it needs the pcntl and posix extensions. --min-workers and --max-workers run an adaptive pool that grows with the backlog and shrinks when it quiets down.
Install the plugin, start a worker, and follow the guides from your first job to approval workflows and state machines.