- Docs
- Start Here
- Migrate from BullMQ
BullMQ code, minus Redis.
bunqueue keeps the BullMQ Queue and Worker API. Most migrations are an import change and a deleted Redis config. This page shows the diff first, then lists every real difference.
This page is for BullMQ users. It shows the full before/after up front, then walks the steps and the few API differences that actually matter.
Before upgrading bunqueue 2.8 to 2.9
Section titled “Before upgrading bunqueue 2.8 to 2.9”bunqueue 2.9 replaces Croner with Bun 1.4’s native cron parser. Standard
five-field schedules, the documented leading-seconds six-field form, and the
published shortcuts continue to work. Croner-only extensions such as L, W,
#, +, ?, and seven-field years do not.
Before stopping a 2.8 broker, inventory persisted schedules:
bunqueue cron listbunqueue cron delete legacy-last-day# Recreate it with a supported schedule through your normal cron add/API path.Do this while 2.8 is still running for both SQLite and PostgreSQL deployments. On startup, 2.9 validates the complete persisted collection before advancing any missed schedule. If one unsupported definition remains, startup fails with its escaped name and schedule plus remediation guidance; this prevents a silent omission or a due-timer retry loop. Do not hand-edit PostgreSQL cron payloads: replace or delete them through the 2.8 public API.
Interval definitions are also checked before any scheduler, deduplication, or
database mutation. repeatEvery must be a positive safe integer in milliseconds.
Invalid legacy or corrupt rows fail startup with the cron name and remediation
guidance. When both a valid calendar schedule and interval are present, the
calendar schedule continues to take precedence for backward compatibility.
The whole migration, one diff
Section titled “The whole migration, one diff”// Before: BullMQ + Redisimport { Queue, Worker } from 'bullmq';
const connection = { host: 'localhost', port: 6379 };const queue = new Queue('emails', { connection });const worker = new Worker( 'emails', async (job) => { await sendEmail(job.data); return { sent: true }; }, { connection, concurrency: 5 });
// After: bunqueue, no Redisimport { Queue, Worker } from 'bunqueue/client';
const queue = new Queue('emails', { embedded: true, dataPath: './data/bunq.db' });const worker = new Worker( 'emails', async (job) => { await sendEmail(job.data); return { sent: true }; }, { embedded: true, concurrency: 5 });// Before: BullMQ + Redisimport { Queue, Worker } from 'bullmq';
const connection = { host: 'localhost', port: 6379 };const queue = new Queue('emails', { connection });const worker = new Worker( 'emails', async (job) => { await sendEmail(job.data); return { sent: true }; }, { connection, concurrency: 5 });
// After: bunqueue, no Redisimport { Queue, Worker } from 'bunqueue-client';
const queue = new Queue('emails', { embedded: false });const worker = new Worker( 'emails', async (job) => { await sendEmail(job.data); return { sent: true }; }, { embedded: false, concurrency: 5 });BullMQ is a Node.js library, so the before/after pairs on this page are JavaScript and TypeScript only. If the destination is Python, the target-side API is in the Python SDK reference.
BullMQ is a Node.js library, so the before/after pairs on this page are JavaScript and TypeScript only. If the destination is PHP, the target-side API is in the PHP SDK reference.
BullMQ is a Node.js library, so the before/after pairs on this page are JavaScript and TypeScript only. If the destination is Go, the target-side API is in the Go SDK reference.
BullMQ is a Node.js library, so the before/after pairs on this page are JavaScript and TypeScript only. If the destination is Rust, the target-side API is in the Rust SDK reference.
BullMQ is a Node.js library, so the before/after pairs on this page are JavaScript and TypeScript only. If the destination is Elixir, the target-side API is in the Elixir SDK reference.
Embedded mode (embedded: true) requires the Bun runtime. On Node.js and Deno, bunqueue-client connects to a bunqueue server with embedded: false; the Queue and Worker implementation and API are shared with bunqueue/client.
embedded: true runs the queue inside your process, backed by a local SQLite file, so there is no queue server to operate. dataPath says where that file lives.
If your producers and workers run in separate processes or machines, start a bunqueue server (bunqueue start) and replace embedded: true with connection: { host, port }. The rest of the code is identical. See Server Mode.
Step by step
Section titled “Step by step”bun remove bullmq ioredisbun add bunqueueThen, in your code:
- Change imports from
'bullmq'to'bunqueue/client'.Queue,Worker,QueueEvents, andFlowProducerall exist under the same names; Pro-oriented code may use theQueuePro,WorkerPro, andQueueEventsProaliases plus theJobProtype. - Delete the Redis connection object. Pass
embedded: trueplus adataPath, orconnection: { host, port }for server mode. - Run your test suite. Job options, events, and flows behave the same, the differences are listed below.
- Remove the Redis server from your infrastructure.
What stays the same
Section titled “What stays the same”- Classes:
Queue,Worker,QueueEvents,FlowProducer, same constructors minus the Redis connection. - Job options:
priority,delay,attempts,backoff(including the{ type, delay }object form),jobId,removeOnComplete,removeOnFail,repeat. jobIddeduplication: adding the samejobIdtwice returns the existing job instead of creating a duplicate, exactly like BullMQ.- Events:
worker.on('completed' | 'failed' | 'progress', ...)fire with the same signatures. - Job states: the full BullMQ v5 state machine, including
prioritizedandwaiting-children. - Flows:
FlowProducer.add(),addBulk(),getFlow(),queuesOptions,failParentOnFailure,removeDependencyOnFailure. The Bun package sends one broker-sidePUSHFgraph. Configured SQLite commits every row before publishing local state, while PostgreSQL commits the complete graph in one database transaction; both provide all-or-nothing creation without client-side rollback. - Per-worker rate limiting:
limiter: { max, duration }onWorkerOptions, unchanged.
BullMQ Pro-oriented APIs in the Bun package
Section titled “BullMQ Pro-oriented APIs in the Bun package”The Pro aliases point directly at bunqueue’s native implementations:
import { QueuePro, WorkerPro, QueueEventsPro } from 'bunqueue/client';import type { JobPro } from 'bunqueue/client';No extra package, connection, or license is required. The Bun client supports:
- job groups with round-robin fairness, priority/FIFO lanes, atomic
maxSize, pause/resume, backlog and priority queries, per-group concurrency/rate defaults, local overrides, and manualworker.rateLimitGroup()cooldowns; - native processor batches through
batch: { size, minSize?, timeout?, groupAffinity? },job.getBatch(), and selectivemember.setAsFailed(error); - cooperative active-job cancellation through the processor’s second-argument
AbortSignal; and - structural Observable processor results without requiring RxJS.
These features use the same public contract in embedded memory/SQLite and TCP server mode. PostgreSQL 15–18 makes group capacity, ordering, pause, limits, and manual deadlines authoritative across brokers. BullMQ Pro telemetry and its NestJS integration are intentionally outside this compatibility layer; use bunqueue’s existing metrics/events and framework-neutral Worker lifecycle instead.
Ordinary BullMQ job code remains unchanged:
// This BullMQ code runs as-is on bunqueueawait queue.add('task', data, { priority: 1, delay: 5000, attempts: 3, backoff: { type: 'exponential', delay: 1000 }, removeOnComplete: true, jobId: 'order-123',});What changes
Section titled “What changes”Backoff
Section titled “Backoff”Backoff is the wait time between retries. Both BullMQ forms work, plus a shorthand:
backoff: { type: 'exponential', delay: 1000 } // same as BullMQbackoff: { type: 'fixed', delay: 5000 } // same as BullMQbackoff: 1000 // shorthand: exponential with 1000ms baseExponential retries wait roughly 2s, 4s, 8s with a 1000ms base (delay * 2^attempts). bunqueue adds automatic jitter (a small random spread) so thousands of failed jobs do not retry at the same instant, and caps delays at 1 hour by default.
Repeatable jobs
Section titled “Repeatable jobs”BullMQ’s legacy repeat: { cron: '...' } key becomes pattern:
await queue.add('task', data, { repeat: { pattern: '0 * * * *' } }); // cron syntaxawait queue.add('task', data, { repeat: { every: 3600000 } }); // fixed intervalremoveOnComplete is boolean only
Section titled “removeOnComplete is boolean only”BullMQ accepts removeOnComplete: { age, count } for retention rules. bunqueue accepts only true or false. Use queue.clean() for age-based cleanup.
Sandboxed processors
Section titled “Sandboxed processors”BullMQ lets you pass a file path as the processor to run it in a child process. In bunqueue, move that logic into an inline processor function (recommended, production-ready):
const worker = new Worker( 'queue', async (job) => { // the same logic from your processor.js return result; }, { embedded: true, concurrency: 4 });An experimental SandboxedWorker (built on Bun Workers) exists if you need process isolation. See Worker vs SandboxedWorker.
Queue-level rate limiting
Section titled “Queue-level rate limiting”The per-worker limiter works as in BullMQ. bunqueue also offers a queue-level limit:
queue.setGlobalRateLimit(100); // max 100 jobs per second across all workersqueue.setGlobalRateLimit(100, 60_000); // max 100 jobs per minuteThe optional second duration argument is the window in milliseconds and is
enforced by the broker in both embedded and TCP modes. It defaults to 1,000 ms.
No Redis required
Section titled “No Redis required”bunqueue uses in-memory storage when no persistence backend is configured. A single broker can opt into SQLite persistence while many workers connect over TCP. If the broker tier must scale, standalone servers can instead share a PostgreSQL 15–18 database/namespace (18.6 recommended). This is not Redis Cluster and does not copy its topology or Redis-specific semantics; see storage backends and when BullMQ is the better pick.
Migration checklist
Section titled “Migration checklist”-
bun remove bullmq ioredis,bun add bunqueue - Imports point to
bunqueue/client - Redis connection config deleted,
embedded: true+dataPath(orconnection) added - For multi-broker server mode, PostgreSQL 15–18 URL/namespace and unique broker IDs configured
-
repeat: { cron }renamed torepeat: { pattern } -
removeOnComplete/removeOnFailobjects replaced with booleans - File-path processors converted to inline processor functions
- Tests pass
- Redis removed from infrastructure
Gotchas
Section titled “Gotchas”- Persistence is opt-in for embedded mode. No
dataPathmeans in-memory only. Server mode readsBUNQUEUE_DATA_PATHor--data-path. - One writer per SQLite file. Do not point two embedded processes or brokers at the same file. Multiple clients use one SQLite server; multiple active brokers use PostgreSQL 15–18; 18.6 is recommended.
SandboxedWorkeris experimental. Prefer inline processors in production.
Getting help
Section titled “Getting help”Open a GitHub issue or ask in Discussions.