PHPackages                             shewa/wp-job-queue - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Queues &amp; Workers](/categories/queues)
4. /
5. shewa/wp-job-queue

ActiveLibrary[Queues &amp; Workers](/categories/queues)

shewa/wp-job-queue
==================

A reusable WordPress background job queue powered by WP-Cron.

1.0.0(1mo ago)017MITPHPPHP &gt;=7.4 &lt;=8.5

Since Jul 12Pushed 1mo agoCompare

[ Source](https://github.com/shewa12/wp-job-queue)[ Packagist](https://packagist.org/packages/shewa/wp-job-queue)[ RSS](/packages/shewa-wp-job-queue/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependenciesVersions (2)Used By (0)

WP Job Queue
============

[](#wp-job-queue)

A reusable WordPress background job queue powered by WP-Cron. Jobs are stored in a custom database table, processed one step at a time, report progress to the admin UI, and can be cancelled while running.

Requirements
------------

[](#requirements)

- PHP 7.4 through 8.5
- WordPress 6.0+

Installation
------------

[](#installation)

```
composer require shewa/wp-job-queue
```

On plugin activation, install the database table:

```
use WpJobQueue\Database\Migrator;

register_activation_hook( __FILE__, function () {
    Migrator::install();
} );
```

Quick start
-----------

[](#quick-start)

```
use WpJobQueue\Bootstrap;
use WpJobQueue\JobManager;

// Boot on plugins_loaded
Bootstrap::init( [
    'jobs' => [
        MyCustomJob::class,
    ],
] );

// Dispatch a job
$job_id = JobManager::instance()->dispatch(
    MyCustomJob::class,
    [ 'step' => 0 ]
);

if ( is_wp_error( $job_id ) ) {
    // Handle error
}
```

---

Architecture
------------

[](#architecture)

```
wp-job-queue/
├── src/
│   ├── AbstractJob.php          # Base class to extend
│   ├── Bootstrap.php            # Package bootstrapping
│   ├── JobManager.php           # Dispatch, process, cancel
│   ├── JobRegistry.php          # Job type → class mapping
│   ├── JobRepository.php        # Database access
│   ├── JobRecord.php            # Job value object
│   ├── JobResult.php            # Step result object
│   ├── JobStatus.php            # Status constants
│   ├── Contracts/JobInterface.php
│   ├── Cron/CronScheduler.php   # WP-Cron worker
│   ├── Database/Migrator.php    # Table installer
│   └── Http/AjaxHandler.php     # Generic AJAX endpoints

```

ComponentResponsibility`Bootstrap`Registers jobs, cron hooks, AJAX handlers, and runs DB upgrades`JobRegistry`Maps job type strings (e.g. `export_websites`) to PHP classes`JobManager`Public API: dispatch, process, cancel, list, and get jobs`JobRepository`All reads/writes to the `{prefix}wp_job_queue_jobs` table`CronScheduler`Schedules and runs the WP-Cron worker every minute`AjaxHandler`REST-like AJAX endpoints for polling and cancelling from JavaScript`AbstractJob`Base class your job handlers extend---

How it works under the hood
---------------------------

[](#how-it-works-under-the-hood)

WP Job Queue uses a **step-based processing model**. Each job is not one long-running task — it is a record in the database that advances one small chunk of work per execution. This fits WordPress well because PHP requests and WP-Cron ticks are short-lived.

### Lifecycle

[](#lifecycle)

```
dispatch() → pending → running → (step loop) → completed
                              ↘ failed
                              ↘ cancelled

```

1. **Dispatch** — `JobManager::dispatch()` validates the job class, checks that the type is registered, ensures no duplicate active job exists for the same type/user, inserts a `pending` row, and schedules WP-Cron.
2. **Schedule** — `CronScheduler::schedule()` registers a recurring event on the `wp_job_queue_worker` hook (every minute). `spawn_cron()` is also called to nudge WP-Cron immediately.
3. **Worker tick** — On each cron run (and optionally during admin requests), `JobManager::processNext()` loads the oldest `pending` or `running` job and executes **one step**.
4. **Step execution** — The registered job class is instantiated and `handle( JobRecord $job )` is called. It returns a `JobResult` telling the manager what to do next.
5. **State update** — Based on the result:
    - `JobResult::continue()` — update payload/progress, keep status `running`, increment `current_step`
    - `JobResult::complete()` — set status `completed`, store final result JSON, set progress to 100
    - `JobResult::failed()` — set status `failed`, store the error message
6. **Idle cleanup** — When no `pending` or `running` jobs remain, the cron event is unscheduled to avoid unnecessary overhead.

### Processing triggers

[](#processing-triggers)

Jobs advance through two mechanisms:

TriggerWhenPurposeWP-Cron (`wp_job_queue_worker`)Every minute while jobs are activePrimary background workerAdmin init (`maybeProcessOnAdmin`)Each admin page load by a privileged userHelps local/dev sites where cron is unreliable### Concurrency model

[](#concurrency-model)

- Only **one step** of **one job** runs per worker invocation.
- Only **one active job per type per user** is allowed at dispatch time (prevents duplicate exports, etc.).
- Jobs are processed FIFO by `created_at`.

### Error handling

[](#error-handling)

- Uncaught exceptions in `handle()` are caught and mark the job as `failed`.
- Missing job handlers mark the job as `failed` with a clear message.
- Cancelled jobs are skipped on subsequent steps if the handler checks status early.

### WP-Cron note

[](#wp-cron-note)

WordPress cron only fires when someone visits the site. For production, either prompt users to keep the admin page open while jobs run, or configure a real server cron:

```
*/1 * * * * wget -q -O - https://yoursite.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1
```

---

Database table
--------------

[](#database-table)

Table name: `{prefix}wp_job_queue_jobs` (override with the `wp_job_queue_table_name` filter)

ColumnDescription`id`Auto-increment primary key`job_type`Unique job identifier (from `getType()`)`status``pending`, `running`, `completed`, `failed`, or `cancelled``payload`JSON data passed between steps`progress`0–100 percentage`current_step`Number of completed steps`total_steps`Total steps if known (0 otherwise)`result`JSON output when completed`error_message`Error text when failed`status_message`Human-readable progress message`user_id`User who dispatched the job`created_at` / `updated_at`Timestamps`started_at` / `completed_at`Set when job starts and reaches a terminal stateThe table is created by `Migrator::install()` and auto-upgraded on boot via `Migrator::maybeUpgrade()`.

---

Creating a job
--------------

[](#creating-a-job)

Extend `AbstractJob` and implement `handle()`. Each call to `handle()` should process **one step** and return a `JobResult`.

```
