WP Background Processing: async requests and queued batches without a cron job
WordPress background processing class
At a glance
- What is it?
- This WordPress library gives plugins two ways to push slow work off the request: a one-off non-blocking request dispatched to the site itself, and a persistent queue that scales its batch size to the server and restarts itself if it dies.
- Who is it for?
- This library fits a WordPress plugin that has a slow, fire-and-forget job such as sending email, importing data, or a long batch operation,. It is a poor fit for a site that needs guaranteed delivery, since a dispatched request still depends on the web server making an outbound loopback call, and it is a poor fit for a large volume of work where a proper job queue belongs.
- Can I use it commercially?
- Yes, with conditions. GPL-2.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 23 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two patterns, one library
WP Background Processing does two related things. The first is firing off non-blocking asynchronous requests, which the documentation describes as useful for pushing slow one-off tasks such as sending emails to a background process. Once the request has been dispatched it processes in the background instantly, as far as the visitor is concerned, because the visitor's request is not the one doing the work.
The second is background processing, which works in a similar fashion but allows you to queue tasks. Items pushed onto the queue are processed in the background once the queue has been saved and dispatched.
The distinction that matters in practice is the difference between one task and many. An async request is a single fire-and-forget action with no tracking. A background process is a queue, which survives a crash, scales its work, and reports when it is done.
The library is inspired by TechCrunch's WP Asynchronous Tasks, and the project points to an example plugin and an accompanying article for a longer walkthrough. It requires PHP 5.6 or above, and it is licensed GPL-2.0, which is the licence a WordPress plugin directory would expect. The repository is not archived and the last push was on 2026-09-30, with the most recent release, 1.4.0, published on 2024-12-17.
The async request pattern in three properties and one method
An async request is a class extending WP_Async_Request with three members to define. The prefix is a unique string associated with your plugin, theme, or site's custom function prefix, and the action is a unique name. The handle method is where your logic goes, and the data you attach to the request arrives there in $_POST.
class WP_Example_Request extends WP_Async_Request {
protected $prefix = 'my_plugin';
protected $action = 'example_request';
protected function handle() {
// Actions to perform.
}
}Dispatching is three calls. Instantiate the request, optionally attach data, then dispatch:
$this->example_request = new WP_Example_Request();
$this->example_request->data( array( 'value1' => $value1, 'value2' => $value2 ) );
$this->example_request->dispatch();The data method returns the object, so it chains with dispatch into a single expression:
$this->example_request->data( array( 'data' => $data ) )->dispatch();The mechanism is worth understanding because it explains the constraints. The dispatch does not spawn a process; it causes the site to make a non-blocking HTTP request to itself, and that request runs the handle method. That is why the data arrives in $_POST, and it is also why the pattern depends on the host permitting that loopback call. On a locked-down host where outbound requests to your own domain are refused, the request is simply never handled.
There is also no completion signal. The caller has no way to learn that the task succeeded, which is fine for a fire-and-forget email and wrong for anything where you need to know.
The queue scales with the server and restarts itself
A background process extends WP_Background_Process and has the same two identifying properties, plus a task method and an optional complete method.
class WP_Example_Process extends WP_Background_Process {
protected $prefix = 'my_plugin';
protected $action = 'example_process';
protected function task( $item ) {
// Actions to perform.
return false;
}
protected function complete() {
parent::complete();
// Show notice to user or perform some other arbitrary task...
}
}The return value of task is the control flow. Return false and the item is removed from the queue. Return the item and it is pushed back for further processing in the next pass, which is how you implement a multi-step job. If the item was modified and is pushed back, the current state is saved before the batch is exited, so a long-running job can checkpoint itself.
The complete method runs once the queue has finished, and the documentation is specific that if you override it you must ensure the parent actions are performed, either by calling parent::complete() or by replicating them.
Two behaviours are worth internalising. Queues scale based on available server resources, so a higher end server processes more items per batch, and once a batch completes the next batch starts instantly. And health checks run by default every 5 minutes to ensure the queue is running when queued items exist, restarting it if it has failed. That health check is the difference between a queue and a hope.
Queues are first in first out, and a dispatch while running is a no-op
The queue semantics are described plainly in the documentation. Queues work on a first in first out basis, which allows additional items to be pushed to the queue even if it is already processing. Saving a new batch of queued items and dispatching while another background processing instance is already running results in the dispatch shortcutting out, and the existing instance eventually picking up the new items and processing them when it is their turn.
That is a deliberate design rather than an oversight. It means the push path is cheap and safe to call on every request, and there is never a second worker competing for the same queue.
The usage pattern follows from it. You push items with push_to_queue, an item being any valid PHP value, string, integer, array or object, and then you save and dispatch:
foreach ( $items as $item ) {
$this->example_process->push_to_queue( $item );
}
$this->example_process->save()->dispatch();The one rule that surprises people is stated in bold in the documentation: you must instantiate your process unconditionally. All requests should do this, even if nothing is pushed to the queue. The reason is the health check, which needs the process to be registered and available on a request that pushes nothing, so that it can restart a queue that has failed.
So the correct shape of a WordPress integration is to construct the process near the top of every request, push conditionally, and dispatch only when something was pushed.
The unserialize risk, and the option that limits it
Queue items that contain non-scalar values are serialized when stored in the database, and that creates an object injection risk on the way back out. The library addresses it directly, and the documentation is unusually explicit about the default being unsafe.
The option is the allowed_classes option passed to unserialize(), which limits which classes can be instantiated. Internally it is the protected $allowed_batch_data_classes property. To maintain backward compatibility the default value is true, meaning any serialized object will be instantiated, and the documentation notes that this default behaviour may change in a future major release.
The library's authors encourage all users to set a strict value, and specifically suggest false to disallow any objects from being instantiated, or a very limited list of class names. The default can be overridden by passing an array of allowed classes to the constructor:
$allowed_batch_data_classes = array( MyCustomItem::class, MyItemHelper::class );
$this->example_process = new WP_Example_Process( $allowed_batch_data_classes );Or by passing false, which disallows objects entirely:
$this->example_process = new WP_Example_Process( false );The other way to change it is to override the property in your subclass, and objects in the serialized string that are not allowed to be instantiated get the class type __PHP_Incomplete_Class instead.
This is the sharpest edge in the library and the one a security review will ask about. A queue that stores user-influenced data and instantiates whatever it finds is a remote code execution path, and the fix is one constructor argument that the library does not apply for you.
Installing it, and the Mozart prefix problem
The recommended install is Composer, and the package name is the same as the repository:
composer require deliciousbrains/wp-background-processingThe second half of the install advice is the part that catches people. It is highly recommended to prefix wrap the library class files using the Mozart package, to prevent collisions with other projects using this same library. The reason is that the library defines global class names, WP_Async_Request and WP_Background_Process, and two plugins on the same site both shipping this library produce a fatal redeclaration error. Mozart prefix-wraps the library class files so each copy avoids colliding with another.
So a plugin that ships this library does not simply depend on it and hope. It depends on it, and it also runs a prefixing step as part of its build. The Makefile in this repository is the library's own, not a plugin's, and it shows the standard toolchain: a test target that runs both unit tests and style checks, with the vendor directory created by composer install with the ignore-platform-reqs flag, an update-deps target running composer update with all dependencies, and clean and clean-all targets that remove the test caches, the vendor directory and the coverage file. The style check runs phpcs against a.phpcs.xml configuration, and phpunit.xml holds the test configuration, with a sonar-project.properties file for Sonar analysis and a bin directory for the CLI tooling.
The licence is GPL-2.0, in a license.txt file at the root, which is the correct licence for a library that a WordPress plugin will vendor.
Editorial conclusion
This library fits a WordPress plugin that has a slow, fire-and-forget job such as sending email, importing data, or a long batch operation,. It is a poor fit for a site that needs guaranteed delivery, since a dispatched request still depends on the web server making an outbound loopback call, and it is a poor fit for a large volume of work where a proper job queue belongs. Before adopting, set $allowed_batch_data_classes to false or a narrow list on any process that accepts serialized items, instantiate the process unconditionally on every request as the documentation insists, and confirm the host allows the loopback request the async pattern depends on.
Frequently asked questions
How do I install WP Background Processing in a WordPress plugin?
The recommended way is to load it through Composer with composer require deliciousbrains/wp-background-processing. The documentation strongly recommends prefix wrapping the library class files with the Mozart package, because the library defines global class names that would collide with another copy on the same site.
What is the difference between an async request and a background process in this library?
An async request is a single non-blocking call for one-off tasks, with no tracking of completion. A background process is a persistent queue: items are pushed with push_to_queue, the queue is saved and dispatched, batches scale with server resources, and health checks restart the queue if it fails.
What should I return from the task method?
Return false to remove the item from the queue, or return the item to push it back for further processing in the next pass. If the item was modified and is pushed back, its current state is saved before the batch exits, so a long job can checkpoint itself.
How do I limit which classes can be unserialized from queue items?
Pass an array of allowed class names to the constructor, or pass false to disallow objects entirely, and the value is held in the protected $allowed_batch_data_classes property. The library also documents overriding that property in your subclass.
Why must I instantiate the background process on every request?
The documentation states you must instantiate your process unconditionally, with all requests doing this even if nothing is pushed to the queue. The health checks that run every 5 minutes to restart a failed queue depend on the process being available on a request that pushes nothing.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/deliciousbrains-wp-background-processing)Was this useful?