BullMQ: a Redis-backed job queue for Node.js, Python, Rust, Elixir and PHP
BullMQ - Message Queue and Batch processing for NodeJS, Python, Elixir, Rust and PHP based on Redis.
At a glance
- What is it?
- BullMQ keeps job state in Redis and ships native clients for six languages. It is a good fit when you already run Redis and need delayed, repeatable and parent-child jobs, and the wrong tool when you need durable replay of a log.
- Who is it for?
- Adopt BullMQ if you already operate Redis or a compatible server and your work is discrete jobs with retries, delays and dependencies, especially if you need the same queue consumed from more than one language. Do not adopt it if you need a replayable log with long retention, or if you cannot run Redis at all.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What BullMQ actually solves
A web request should not wait for a video transcode, an email send, or a third-party API call that may take thirty seconds. BullMQ moves that work out of the request path: you add a job to a queue, a separate worker process picks it up, and the producer returns immediately. The queue state lives in Redis, so the worker can be a different process, a different machine, or a different language from the producer.
The README describes the project as a "Redis-based distributed queue for Node.js, Python, Elixir, .NET, Rust, PHP, and more," and the repository layout backs that up: there are python/, rust/, elixir/, dotnet/ and php/ directories alongside the TypeScript source in src/. The language clients are not wrappers around an HTTP service. They speak the same Redis data structures, which is why a Python producer can hand work to a Node.js worker.
This is the audience: teams that already run Redis, that want job-level semantics rather than a message log, and that may have more than one runtime in the stack.
How jobs move through Redis
The core objects are Queue, Worker, QueueEvents and FlowProducer. A Queue is a named handle for adding jobs. A Worker is a named handle that registers a processor function and pulls jobs. QueueEvents subscribes to lifecycle events over Redis pub/sub, so a process that never runs a worker can still observe completions and failures.
The README's own example shows the shape: a queue named 'Paint' receives a job named 'cars' with data { color: 'blue' }, and a worker on the same queue name runs paintCar(job.data.color) when job.name is 'cars'. Note that the job name is separate from the queue name and from the payload, which is how you route different work through one queue.
FlowProducer adds parent-child relationships. The README example builds a tree with a root-job, two child jobs, and grandchildren, each on its own queueName. The children are added before the parent is processed, which is the mechanism behind fan-out then join workflows. The feature table in the README lists Parent/Child Dependencies as available in both BullMQ and BullMQ-Pro, while Batches Support, Group Support and Group Rate Limit are marked only for BullMQ-Pro.
The implementation detail worth knowing is that the job state transitions are executed as Lua scripts inside Redis, not as a sequence of client-side commands. The package.json build script copies .lua files from src/commands and src/commands/includes into the published dist, and there is a separate copy step for SQL files under src/postgres. That is why the README can claim atomicity: the check-and-move happens server-side in one script.
Installing BullMQ and running a first worker
The README gives the Node.js install as a yarn command, but the npm package name is bullmq, so npm install bullmq installs the same thing. The package.json sets engines.node to >=14.17.0.
npm install bullmqYou need a Redis server reachable from both the producer and the worker. The repository's docker-compose.yml starts redis:8-alpine on port 6379 under the service name redis, and also starts a Dragonfly instance on host port 6380 mapped to container port 6379.
docker compose up -d redisThe README's producer example is three lines past the import. It creates a queue named 'Paint' and adds a job named 'cars'.
import { Queue } from 'bullmq';
const queue = new Queue('Paint');
queue.add('cars', { color: 'blue' });The worker is a separate process. It registers a processor and dispatches on job.name.
import { Worker } from 'bullmq';
const worker = new Worker('Paint', async job => {
if (job.name === 'cars') {
await paintCar(job.data.color);
}
});If you want to observe results without polling Redis yourself, QueueEvents emits 'completed' and 'failed' with a jobId, and the failed handler also receives failedReason. The README shows both handlers in one snippet. The README does not document the default connection options in the gist; the documentation site at docs.bullmq.io is where it points for connection configuration, so check there before assuming a default host and port.
The Redis dependency is the whole design, not a detail
BullMQ is not a queue that happens to use Redis for storage. Redis is the coordination layer: job state, the waiting list, delayed sets, and the pub/sub channel for events all live there. If Redis is unavailable, producers cannot add jobs and workers cannot move them. There is no separate broker process to fail over to.
The README's feature table makes the storage choice explicit by listing Backend as redis for BullMQ, Bull, Kue and Bee, and mongo for Agenda. That row is the clearest single line in the comparison: the alternatives that look similar are separated mainly by what they persist to.
A second constraint is the adapter surface. The README notes that the node-redis adapter (createNodeRedisClient) requires redis v5 or newer, and that the Valkey Glide adapter (createValkeyGlideClient) requires installing @valkey/valkey-glide. The repository carries separate vitest configurations for ioredis, node-redis and valkey-glide, plus a postgres configuration. Those are distinct code paths, not one client behind a flag, so a bug can exist in one adapter and not another.
The README also lists Dragonfly as a sponsor and states it is a drop-in replacement that is "fully compatible with BullMQ." The docker-compose.yml reflects that with a Dragonfly service configured with DFLY_cluster_mode: 'emulated' and DFLY_lock_on_hashtags: 'true'. Treat that as the project's own compatibility claim, not an independent verification.
When BullMQ is the wrong tool
If your requirement is a durable, replayable log that multiple independent consumer groups can re-read from an arbitrary offset, BullMQ is the wrong shape. It tracks the state of individual jobs, and once a job completes and is removed, the payload is gone. There is no retention window you can rewind.
If you cannot operate Redis, this is also the wrong tool. The README's comparison table shows the only two backends in play are redis and mongo, and BullMQ is on the redis side. Adding Redis to a stack that has none is a real cost: another process to monitor, another thing to back up, and another failure mode during deploys.
There is also a feature boundary inside the project itself. The README's table marks Observables, Group Rate Limit, Group Support and Batches Support as available only in BullMQ-Pro, not in the open source BullMQ. If your design depends on batches or per-group rate limiting, the MIT-licensed package in this repository does not provide it, and the table is the project's own statement of that gap.
The repository also carries a postgres/ directory and vitest.postgres.config.ts, which suggests work on a PostgreSQL-backed path. The README does not describe a PostgreSQL backend in the feature table, so do not assume it is a supported production target based on the file tree alone.
BullMQ compared with RabbitMQ and Kafka
RabbitMQ is a message broker with its own server process, exchanges, bindings and routing keys. BullMQ has no broker of its own; the routing logic is the queue name plus the job name you pass to add(). The practical difference is operational: RabbitMQ means running and upgrading a broker, while BullMQ means running Redis, which many teams already have. The trade is that RabbitMQ's routing model is far richer than a queue name and a job name.
Kafka is a partitioned append-only log. Consumers track offsets and can replay. BullMQ has no offset concept and no log to replay; a completed job is finished. Kafka also decouples retention from processing, while BullMQ's Redis keys grow and shrink with job lifecycle. If you are choosing between them, the question is whether you need replay, not which is faster.
Within the Node.js ecosystem, the README's own table places Bull and Kue on Redis and Agenda on MongoDB. Bull is the predecessor project, and the README positions BullMQ as its successor with Parent/Child Dependencies and deduplication features that Bull lacks. Agenda's MongoDB backend is the meaningful alternative if your team already runs Mongo and does not want Redis.
Licence, releases and what maintenance costs you
The package.json declares "license": "MIT" and the repository root contains a LICENSE file. MIT is permissive: you can use, modify and redistribute the code, including in closed-source products, provided the copyright notice and permission notice are preserved. That is the extent of what the repository states; questions about your specific distribution obligations are for your own counsel, not for this article.
The last push to the default branch was on 2026-08-29, and the most recent release listed is v6.3.2 on the same date, with vpy3.1.1 and vpy3.1.0 for the Python client on 2026-08-29 and 2026-08-28. The repository is not archived. Release cadence is therefore active, but note that the Python client has its own version line, so "upgrading BullMQ" means two different version numbers depending on which language client you run.
The upgrade cost that matters most is the Redis data format. Because job state lives in Redis and the state transitions are Lua scripts shipped inside the package, a version bump can change what the scripts expect to find. The repository has a CHANGELOG.md and a commitlint.config.js, which indicates conventional commits feed the release notes, so the changelog is the place to read before upgrading. The README does not document a rollback procedure.
Editorial conclusion
Adopt BullMQ if you already operate Redis or a compatible server and your work is discrete jobs with retries, delays and dependencies, especially if you need the same queue consumed from more than one language. Do not adopt it if you need a replayable log with long retention, or if you cannot run Redis at all. Before committing, verify two things in your own environment: that your Redis deployment is one of the adapter targets you intend to use, and how the worker's stalled-job handling behaves when a process is killed mid-job.
Frequently asked questions
What is BullMQ used for?
It moves background work out of the request path by storing jobs in Redis and letting separate worker processes pick them up. The README's example adds a job named 'cars' to a queue named 'Paint' and processes it in a Worker registered on the same queue name.
What are the key differences between RabbitMQ and BullMQ?
RabbitMQ is a broker with its own server process and a routing model built on exchanges and bindings. BullMQ has no broker of its own: the README's comparison table lists its backend as redis, and routing is the queue name plus the job name passed to add().
Is BullMQ similar to Kafka?
Both move work between processes, but Kafka is a partitioned log with offsets that consumers can replay. BullMQ tracks individual job state in Redis, and the README gives no offset or replay mechanism.
Can BullMQ be used with PostgreSQL?
The README's feature comparison table lists the backend as redis only. The repository does contain a postgres/ directory and a vitest.postgres.config.ts, but the README does not describe PostgreSQL as a supported backend, so do not treat it as a production target on that basis.
How do you install BullMQ?
For Node.js or Bun, the README gives the yarn command and the package is published as bullmq on npm. Python installs from the python/ directory with pip install bullmq, and Rust uses cargo add bullmq-official --rename bullmq.
How do you use BullMQ in Node.js?
Import Queue to add jobs and Worker to process them, both constructed with the same queue name. QueueEvents on that name emits 'completed' and 'failed' events if you want to observe outcomes without a worker in the same process.
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/taskforcesh-bullmq)