Queuety

Getting Started

Installation

Queuety requires the pdo_mysql extension in the PHP runtime that loads WordPress and WP-CLI. If that extension is missing, the plugin will stay loaded but will not initialize its runtime, and it will show an admin notice instead of fatally breaking the site.

If you want the DB-backed runtime without the WordPress plugin layer, see Standalone Use. That path uses the same PDO connection and runtime tables, but it does not include the WordPress hook bridges or WP-CLI registration.

Via Composer for Composer-managed WordPress

composer require queuety/queuety

Then activate the plugin:

wp plugin activate queuety

Via a packaged plugin zip

wp plugin install /path/to/queuety.zip --activate

From source during development

Place the repository in wp-content/plugins/queuety, then install PHP dependencies:

composer install
wp plugin activate queuety

On activation, Queuety creates its database tables (queuety_jobs, queuety_workflows, queuety_logs, queuety_schedules, queuety_queue_states, queuety_webhooks, queuety_locks, queuety_signals, queuety_workflow_dependencies, queuety_workflow_dispatch_keys, queuety_batches, queuety_chunks, queuety_workflow_events) so workers can claim jobs directly from MySQL.

By default, the plugin also schedules a one-shot worker through WordPress cron every minute for the default queue. That gives you a no-shell baseline for jobs and workflows that stay on default, as long as WordPress cron is firing. For custom queues, lower latency, or heavier workloads, move to dedicated wp queuety work processes.

Configuration

All constants are optional. Define them in wp-config.php before the plugin loads, or before Queuety boots in an embedded theme-style install:

ConstantDefaultDescription
QUEUETY_RETENTION_DAYS7Auto-purge completed jobs after N days
QUEUETY_LOG_RETENTION_DAYS0Auto-purge logs after N days (0 = keep forever)
QUEUETY_MAX_EXECUTION_TIME300Max seconds per job before timeout
QUEUETY_WORKER_SLEEP1Seconds to sleep when queue is empty
QUEUETY_WORKER_MAX_JOBS1000Max jobs before worker restarts
QUEUETY_WORKER_MAX_MEMORY128Max MB before worker restarts
QUEUETY_RETRY_BACKOFFexponentialBackoff strategy: exponential, linear, or fixed
QUEUETY_STALE_TIMEOUT600Seconds before stuck jobs are recovered
QUEUETY_CACHE_TTL5Default cache TTL in seconds
QUEUETY_DEBUGfalseEnable verbose worker logging
QUEUETY_CLI_COMMANDqueuetyRoot WP-CLI command name
QUEUETY_TABLE_PREFIXqueuety_Shared base name for all Queuety tables after the WordPress DB prefix
QUEUETY_TABLE_JOBSqueuety_jobsJobs table name
QUEUETY_TABLE_WORKFLOWSqueuety_workflowsWorkflows table name
QUEUETY_TABLE_LOGSqueuety_logsLogs table name
QUEUETY_TABLE_SCHEDULESqueuety_schedulesSchedules table name
QUEUETY_TABLE_SIGNALSqueuety_signalsSignals table name
QUEUETY_TABLE_WORKFLOW_DEPENDENCIESqueuety_workflow_dependenciesWorkflow dependency waits table name
QUEUETY_TABLE_WORKFLOW_DISPATCH_KEYSqueuety_workflow_dispatch_keysDurable workflow idempotency table name
QUEUETY_TABLE_CHUNKSqueuety_chunksStreaming chunks table name
QUEUETY_TABLE_QUEUE_STATESqueuety_queue_statesQueue states table name
QUEUETY_TABLE_WEBHOOKSqueuety_webhooksWebhooks table name

QUEUETY_TABLE_PREFIX changes only the Queuety portion of the table names. The WordPress database prefix still wraps the result, so $wpdb->prefix = 'wp_' and QUEUETY_TABLE_PREFIX = 'themequeue_' produce tables like wp_themequeue_jobs. Explicit QUEUETY_TABLE_* constants still win for individual tables.

Your first job

Create a handler class that implements the Handler interface:

use Queuety\Handler;

class SendEmailHandler implements Handler {
    public function handle( array $payload ): void {
        wp_mail( $payload['to'], $payload['subject'], $payload['body'] );
    }

    public function config(): array {
        return [
            'queue'           => 'emails',
            'max_attempts'    => 5,
        ];
    }
}

Register it and dispatch:

use Queuety\Queuety;

Queuety::register( 'send_email', SendEmailHandler::class );

Queuety::dispatch( 'send_email', [
    'to'      => '[email protected]',
    'subject' => 'Welcome',
    'body'    => 'Hello from Queuety!',
] );

Or use the modern dispatchable job class:

use Queuety\Contracts\Job;
use Queuety\Dispatchable;

readonly class SendEmailJob implements Job {
    use Dispatchable;

    public function __construct(
        public string $to,
        public string $subject,
        public string $body,
    ) {}

    public function handle(): void {
        wp_mail( $this->to, $this->subject, $this->body );
    }
}

SendEmailJob::dispatch( '[email protected]', 'Welcome', 'Hello from Queuety!' );

Your first workflow

Define step handlers that implement the Step interface:

use Queuety\Step;

class FetchDataHandler implements Step {
    public function handle( array $state ): array {
        $user = get_user_by( 'ID', $state['user_id'] );
        return [ 'user_name' => $user->display_name ];
    }

    public function config(): array {
        return [];
    }
}

class CallLLMHandler implements Step {
    public function handle( array $state ): array {
        $response = wp_remote_post( 'https://api.openai.com/v1/chat/completions', [
            'body' => json_encode( [ 'prompt' => "Generate report for {$state['user_name']}" ] ),
        ] );
        return [ 'llm_response' => wp_remote_retrieve_body( $response ) ];
    }

    public function config(): array {
        return [ 'max_attempts' => 5 ];
    }
}

class FormatOutputHandler implements Step {
    public function handle( array $state ): array {
        $url = save_report( $state['user_name'], $state['llm_response'] );
        return [ 'report_url' => $url ];
    }

    public function config(): array {
        return [];
    }
}

Dispatch the workflow:

$workflow_id = Queuety::workflow( 'generate_report' )
    ->then( FetchDataHandler::class )
    ->then( CallLLMHandler::class )
    ->then( FormatOutputHandler::class )
    ->dispatch( [ 'user_id' => 42 ] );

Start a worker

wp queuety work

The worker polls for pending jobs, executes them, advances workflows, and sleeps when the queue is empty. Press Ctrl+C to stop.

If you do not run a dedicated worker, Queuety still has the default once-per-minute WordPress cron fallback described above. The dedicated worker is the higher-throughput, lower-latency option.

For production, run multiple workers:

wp queuety work --workers=4

Process multiple queues with priority ordering:

wp queuety work --queue=high,default,low

What's next

  • Jobs for the complete dispatch and handler guide
  • Workflows for chains, parallel steps, conditional branching, and sub-workflows
  • Middleware for rate limiting, throttling, and custom pipeline logic
  • Batching for dispatching groups of jobs with callbacks
  • Streaming Steps for persisting streamed data chunk by chunk
  • Scheduling for recurring jobs
  • Caching for the pluggable cache layer
  • Testing for QueueFake and test assertions
  • CLI Reference for all available commands
  • PHP API for programmatic access

On this page