· nodejs / typescript / job-queue
Best job queue library for Node.js and TypeScript in 2026
BullMQ wins for teams already running Redis. Trigger.dev and Inngest are managed options. QStash is the only edge-runtime choice. Full comparison inside.
By Ethan
2,837 words · 15 min read
Choosing a job queue for Node.js or TypeScript? If your backend already runs Redis, use BullMQ v5. You get 5.6M weekly downloads of community and ecosystem, zero per-execution cost, and the full feature set — cron, retries, priorities, concurrency — without a SaaS subscription.
If you want zero infrastructure to manage, use Trigger.dev v4 ($50/month base). It edges out Inngest on pricing at scale and matches it on TypeScript DX. If you’re building on Cloudflare Workers, QStash is the only viable option on this list.
Who this is for
Node.js / TypeScript developers who have outgrown setTimeout and cron scripts and are choosing a production-grade job queue in 2026. If you’ve never had a job fail silently and disappear, this article will convince you that you need one. If you already know you do, skip to the comparison table.
What we looked at
Six libraries: BullMQ v5.77.6, Trigger.dev v4.4.6, Inngest SDK v4 (GA March 3, 2026), QStash (@upstash/qstash), pg-boss v12, and Quirrel.
Download figures are from the npm downloads API for the week of May 22–28, 2026. Pricing was verified against each vendor’s pricing page on May 30, 2026: Trigger.dev, Inngest, Upstash/QStash. Code snippets come from the official docs for each library’s current stable version.
Why a job queue in the first place
Plain async functions and cron scripts fail in the same ways. A process restart loses in-flight work. A slow external API call holds up the request cycle. A spike in email volume brings down your main thread. A cron that runs at exactly the same time across ten servers sends your users ten identical invoices.
A job queue separates the work from the trigger. Jobs are durable: they survive restarts. They’re retried on failure with backoff. They run in the background, off the request path. They can be scheduled, delayed, prioritized, and rate-limited without you writing the machinery.
The specific machinery you need determines which library fits.
Comparison {#comparison}
| Dimension | BullMQ v5 | Trigger.dev v4 | Inngest | QStash | pg-boss v12 | Quirrel |
|---|---|---|---|---|---|---|
| Weekly downloads | 5.6M | 568K | 1.1M | 390K | 640K | 1.6K |
| Backend | Redis | Managed / self-hosted | Managed / self-hosted | HTTP (managed) | PostgreSQL | Managed |
| TypeScript | Native (56% of codebase) | Native | Native (SDK v4) | Native | Types included | Native |
| Cron scheduling | ✅ upsertJobScheduler | ✅ | ✅ | ✅ | ✅ (multiple per queue) | ✅ |
| Delayed jobs | ✅ | ✅ (waitpoints) | ✅ step.sleep() | ✅ (up to unlimited delay) | ✅ | ✅ |
| Retries | ✅ fixed / exponential / custom | ✅ | ✅ per-step | ✅ free | ✅ retryDelayMax | ✅ |
| Priorities | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ |
| Concurrency | ✅ per-worker | ✅ per-plan | ✅ per-plan | ❌ (HTTP push) | ✅ | ❌ |
| Dashboard | Bull Board (OSS) | Built-in (v4) | Built-in | Upstash Console | None official | None |
| Cloudflare Workers | ❌ | ❌ | ⚠️ unverified | ✅ native | ❌ | ❌ |
| Self-hosted | ✅ (Redis required) | ✅ Docker / K8s | ✅ | ❌ | ✅ (Postgres required) | N/A |
| Open source | ✅ MIT | ✅ MIT | ✅ Apache-2.0 | ❌ | ✅ MIT | ✅ MIT |
| Free tier | N/A (infra cost) | 20 concurrent runs | 5 concurrent steps | 1,000 msg/day | N/A (infra cost) | N/A |
| Paid base | ~$5–10/month (Redis) | $50/month | $75/month | $1/100K messages | ~$10–20/month (Postgres) | — |
| Status | Active | Active | Active | Active | Active | ⛔ Maintenance |
BullMQ v5 — the standard
5,595,335 downloads in the week of May 22–28, 2026. That is nearly 5× Inngest’s weekly count and close to 10× Trigger.dev’s. The ecosystem effects are real: there are ready-made integrations, community packages, Stack Overflow answers, and production war stories you can find at 2am when something breaks.
BullMQ is written natively in TypeScript — 56.5% of the codebase. Job payloads are typed via generics:
const queue = new Queue<{ userId: string; type: string }, void>('notifications');
The IDE tells you when your data shape is wrong before the job ever runs.
Cron scheduling
BullMQ v5 replaced the old repeat API with upsertJobScheduler at v5.16.0. Every pre-2023 tutorial you find online shows the old API. Use this:
await queue.upsertJobScheduler(
'daily-report',
{ pattern: '0 15 3 * * *' },
{ name: 'generate-report', data: { type: 'daily' }, opts: { attempts: 3 } },
);
The first argument is the scheduler ID, the second is the schedule (cron pattern or interval), the third is the job template. Multiple schedulers can share a queue.
Retries and concurrency
const worker = new Worker(queueName, processor, {
concurrency: 50,
});
await queue.add('send-email', { to: '[email protected]' }, {
attempts: 5,
backoff: { type: 'exponential', delay: 1000 },
});
Backoff types: fixed, exponential, or a custom function. Concurrency is per-worker instance and can be adjusted at runtime (worker.concurrency = 5). Priorities are numeric — lower number means higher priority.
Infrastructure
BullMQ requires Redis. ioredis v5 is a direct production dependency. A $7/month Upstash Redis instance handles moderate workloads. Railway and Render both offer Redis plans from ~$5/month.
No Redis means no BullMQ. That is the one hard constraint. If you’re deciding between Redis and its drop-in fork, see Redis vs Valkey.
Dashboard
Bull Board is the community-maintained open-source dashboard — @bull-board/api plus a UI adapter of your choice (Express, Fastify, Hapi). It is not officially maintained by the BullMQ team but it works. BullMQ Pro (commercial, separate license) includes a first-party dashboard.
v4 → v5 migration note
If you’re on BullMQ v4, the Job Scheduler API change is a breaking rewrite of any cron-using code. BullMQ Pro v5 also introduced new Redis data structures for queue markers. Verify the migration guide at the official BullMQ site before upgrading from v4.
Trigger.dev v4 — managed queues with TypeScript-first DX
Trigger.dev v4 went GA on August 18, 2025. The brief for this article referenced v3, but v4 is the current stable version (v4.4.6 as of this writing).
The managed offering runs on dedicated VMs with warm starts (100–300ms vs. several seconds cold). Self-hosted is possible via Docker or Kubernetes (Helm chart added in v4), but you lose warm starts, auto-scaling, and checkpoints. Factor that in if self-hosting is the plan.
Pricing (as of May 30, 2026)
Source: trigger.dev/pricing.
| Plan | Monthly | Concurrent runs | Log retention |
|---|---|---|---|
| Free | $0 | 20 | 1 day |
| Hobby | $10 | 50 | 7 days |
| Pro | $50 | 200+ | 30 days |
Per-run billing on top: $0.000025/run ($25 per million), plus compute time by machine size. Additional concurrency is $10/month per 50 runs — 5× cheaper than the pre-November 2025 pricing after the November concurrency increases.
At 1M runs/month on the Pro plan: $50 base + $25 runs = $75 total. At 10M runs/month: $50 base + $250 runs = $300 total.
⚠️ The trigger.dev/docs/limits page still shows pre-November figures. Use the pricing page and the concurrency changelog as canonical sources.
What v4 adds
Waitpoints let a run pause until an external condition is met. Human-in-the-loop approval flows — where a job waits for a Slack response or a form submission — become first-class:
const token = await wait.createToken({ timeout: '24h' });
// send the token URL to a human for approval
const result = await wait.forToken<{ approved: boolean }>(token);
Idempotency keys prevent duplicate work on retry. HTTP callbacks resume paused runs via webhook. OpenTelemetry exporters let you correlate Trigger.dev traces with Datadog, Honeycomb, or whatever your stack uses.
The v4 dashboard is genuinely useful — environment-scoped navigation, per-queue statistics, and a trace view that shows exactly where a job spent its time.
v3 → v4 breaking changes
Two that matter in practice: custom queues must now be defined in code before deployment (no dynamic creation), and lifecycle hook parameters changed from positional to a single object ({ payload, ctx }). The Trigger.dev team describes the migration as “typically under 5 minutes.” v3 runs continue processing during the dual-engine transition.
Inngest — event-driven, per-step retries
Inngest SDK v4 went GA on March 3, 2026. The model is different from BullMQ or Trigger.dev: you don’t push jobs to a queue — you define functions that react to events. The Inngest platform routes events to your functions.
const processOrder = inngest.createFunction(
{ id: 'process-order', retries: 5 },
{ event: 'order/created' },
async ({ event, step }) => {
const payment = await step.run('charge-card', () =>
chargeCard(event.data.amount),
);
await step.sleep('wait-for-fulfillment', '2d');
await step.run('send-receipt', () =>
sendEmail(event.data.email),
);
},
);
The key difference: retries happen per step, not per function. If send-receipt fails, it retries from that step — charge-card does not run again. This matters for multi-step workflows where early steps have side effects.
Pricing (as of May 30, 2026)
Source: inngest.com/pricing.
| Plan | Monthly | Concurrent steps | Executions included |
|---|---|---|---|
| Hobby | $0 | 5 | 50,000 |
| Pro | $75 | 100+ | 1,000,000 |
Overages: $50 per million executions (up to 20M total). Additional concurrency: $25 per 25 concurrent steps.
The Hobby plan’s free execution ceiling could not be verified against a primary source during research for this article. Check inngest.com/pricing before relying on any specific free tier limit.
At 1M runs/month on Pro: $75/month flat. At 10M runs/month: $75 base + $450 overages = $525/month. At that scale, Inngest’s “step” billing model diverges significantly from Trigger.dev’s “run” model — and they are not directly comparable.
When Inngest fits
If your workload is naturally event-driven (a user action triggers a cascade of steps), Inngest’s model is cleaner than BullMQ’s queue.add() loop. The SDK v4 type inference for event payloads is strong. The framework integrations are deep — Next.js and Vercel get first-class treatment.
If your workload is batch-oriented, BullMQ or Trigger.dev are more natural.
Cloudflare Workers
Inngest’s deployment docs cover Cloudflare Pages. Workers compatibility could not be confirmed against a primary source. Do not assume it works inside a Cloudflare Worker without verifying at inngest.com/docs.
QStash — the only edge-native option
QStash (from Upstash) is not a queue in the BullMQ sense. It is an HTTP message relay. You publish a message via POST; QStash delivers it to a registered HTTP endpoint. Your endpoint is the worker.
The model has a hard constraint: your endpoint must be publicly accessible. No localhost, no internal VPCs. If your worker runs behind a VPN, QStash does not fit.
What it gives you: delayed delivery (up to unlimited delay on paid plans), configurable retries, scheduled messages, and a free 1,000 messages/day tier. The pricing is straightforward — $1 per 100,000 messages on pay-as-you-go.
Why it matters for edge
@upstash/qstash uses HTTP, not TCP sockets. Cloudflare Workers cannot open TCP connections — which is why every Redis client that uses ioredis (including BullMQ) is incompatible with Workers. QStash works because it uses plain HTTP.
Cloudflare’s own developer docs list Upstash as a first-party integration. If you’re building a queue-like pattern inside a Cloudflare Worker, QStash is the only viable option among the six libraries here. For a broader look at the hosting layer, see Cloudflare Workers vs Vercel Edge.
Do not publish specific Queue Parallelism numbers for QStash — the exact concurrency limits were not verifiable at primary sources. Check Upstash’s pricing page for current figures.
pg-boss v12 — Redis-free for Postgres teams
pg-boss puts your job queue in the same Postgres database you’re already running. No Redis. No new infrastructure. If you’re on Supabase, Neon, or self-hosted Postgres, jobs live in the same transaction boundary as your application data.
Version 12 requires Node 22.12+. If your runtime is below that, that is a blocker.
The v12 release is a full ESM + TypeScript rewrite. The most disruptive breaking change is the removal of the default export — import syntax changes from import PgBoss from 'pg-boss' to import { PgBoss } from 'pg-boss'. Static members previously accessed as class properties (e.g. PgBoss.getConstructionPlans()) are now named exports. Queue and scheduling key names now enforce character validation (letters, numbers, hyphens, underscores, periods only). Read the v12 release notes before upgrading from v11.
Weekly downloads: 640,137 — more than Trigger.dev’s 567,541. It is not a niche library. Teams running Postgres-native stacks use it in production.
The gap vs. BullMQ is in throughput ceiling and ecosystem. At moderate volume, Postgres keeps up fine. At very high queue throughput, Redis has a structural advantage. There is no official dashboard — community options exist but are not maintained by the pg-boss project.
Quirrel — do not use
1,577 weekly downloads. In maintenance mode since approximately February 2024 (confirmed by the author in a GitHub discussion). The original use case — serverless cron for Vercel and Netlify — is now covered by native platform features. The author recommends BullMQ for self-hosted replacements.
Mention it if someone sends you a tutorial from 2022. Otherwise, move on.
Decision framework
Use BullMQ v5 if:
- You already run Redis — marginal cost to add a queue is near zero
- You need the widest ecosystem and community (5.6M/week, 10× the next competitor)
- You want self-hosted with no per-execution pricing ever
- You need fine-grained priority queues and concurrency control in process
- You are on Node.js, not an edge runtime
Use Trigger.dev v4 if:
- You want zero infrastructure to operate
- Your workloads are burst-heavy — per-run pricing ($0.000025/run) rewards idle periods
- You need waitpoints or human-in-the-loop approval flows
- You want Bun runtime support (confirmed in v4)
- You want a first-party dashboard without setting up Bull Board
- Budget: $50/month base; 1M runs/month ≈ $75 total
Use Inngest if:
- Your mental model is event-driven: functions react to events, not direct queue pushes
- You need per-step retries for multi-step workflows with side effects
- You’re building on Next.js or Vercel and want deep framework integration
- Budget: $75/month base; 1M executions included, then $50/million
Use QStash if:
- You’re on Cloudflare Workers or another edge runtime
- You need HTTP-push semantics to publicly accessible endpoints
- Your workload is fire-and-forget fan-out
- You do not need in-process workers, priority queues, or complex job state
Use pg-boss v12 if:
- You run Postgres and explicitly want to avoid adding Redis
- You want jobs in the same transaction boundary as your application data
- Your Node runtime is 22.12+
- Your queue volume is moderate — Postgres handles production workloads well below the throughput ceiling where Redis’s advantage becomes decisive
Do not use Quirrel. Maintenance mode. The author has moved on. You should too.
Verdict
For most production SaaS backends on Node.js: BullMQ v5. The adoption gap is decisive. Redis is probably already in the stack. There is no per-execution cost. The full feature set — cron, retries, delays, priorities, concurrency — is covered.
For greenfield managed backends: Trigger.dev v4 over Inngest on price at equivalent scale, and comparable on DX. Choose Inngest if you prefer the event-driven model or live in the Next.js / Vercel ecosystem.
For Cloudflare Workers or pure serverless edge: QStash. Nothing else on this list works.
For Postgres-only teams: pg-boss v12 — but accept that you’re trading infrastructure simplicity for community size (640K/week vs BullMQ’s 5.6M/week) and a non-trivial v12 migration if you’re coming from v11.
Caveats
What we did not test: real-world throughput benchmarks under load — this is a library evaluation based on documented features, verified pricing, and npm download data, not a synthetic load test. High-volume use cases (millions of jobs/hour) need a production proof-of-concept before committing to any library.
Inngest free tier: 50,000 executions/month on the Hobby plan, verified at inngest.com/pricing.
QStash concurrency: specific Queue Parallelism limits were not verifiable from primary sources. Check upstash.com/pricing/qstash.
BullMQ v4→v5 migration: the Redis data structure changes in BullMQ Pro v5 were not fully characterized from primary sources. Verify the official migration guide before upgrading a production Pro installation.
Trigger.dev self-hosted: warm starts, auto-scaling, and checkpoints are cloud-only features. A self-hosted Trigger.dev v4 deployment lacks all three.
Affiliate disclosure: this article contains affiliate links to Upstash (QStash). See the disclosure at the top of the page.
References
- BullMQ on npm
- BullMQ Job Schedulers docs
- Trigger.dev v4 GA announcement
- Trigger.dev pricing
- Inngest TypeScript SDK v4 announcement
- Inngest pricing
- Upstash QStash pricing
- pg-boss on npm
- pg-boss v12.0.0 release notes
- Quirrel on npm
- Quirrel maintenance-mode confirmation (GitHub discussion #1169)
- npm downloads API