# Worker Manager
> Dashboard for BullMQ and Bull job queues, plus a pg-boss engine.
## Guide
- [Introduction](/worker-manager/guide/introduction.md): Worker Manager is a dashboard for BullMQ and Bull. It shows you what is in your queues and lets you act on it. You still use Bull or BullMQ to enqueue and process jobs, Worker Manager only visualises them. Want to see it before installing? Open the live demo.
## API Reference
- [API Reference](/worker-manager/api/index.md): Interactive reference for every route the Worker Manager dashboard serves.
## Reference
- [UIConfig](/worker-manager/configuration/ui-config.md): Applies to: all adapters. UIConfig controls the visual shell of the dashboard: title, logo, favicon, locale, polling, misc links. Pass it via setUIConfig() on the server adapter, or forward it through createWorkerManagerBoard({ options: { uiConfig } }).
## Others
- [Production checklist](/worker-manager/configuration/production-checklist.md): Quick gate before exposing the dashboard to real traffic.
- [Set up with an AI agent](/worker-manager/guide/ai-agent-setup.md): Install the Worker Manager agent skill (Claude Code plugin, zip or one-line CLI), or paste a prompt, so a coding agent wires the dashboard into your app with the right engine, adapter and auth.
- [Standalone CLI](/worker-manager/guide/cli.md): Sometimes you don't want to wire Worker Manager into an app at all, you just want to look at a Redis instance (or a PostgreSQL database holding BullMQ v6 queues). @worker-manager/cli does that: point it at a Redis URL and it finds the Bull and BullMQ queues stored there, then serves the same dashboard UI you'd get from any server adapter. See PostgreSQL queues for the database side. It needs Node.js 20 or newer. That starts the dashboard on http://127.0.0.1:3000 and opens it in a browser. This is a tool for local development, evaluating Worker Manager before wiring it into your app, or looking at a queue on infrastructure you've tunnelled to. It is not a replacement for mounting the adapter in your own server: there's no framework-level auth to inherit (though it has Basic and Keycloak auth built in), and every option has to be passed on the command line, an env var, or a config file instead of code.
- [Run with Docker](/worker-manager/guide/docker.md): Run the Worker Manager dashboard in a container with the official ghcr.io/naldomadeira/worker-manager image. Docker Compose, environment variables, config files and basic auth.
- [Exploring the dashboard](/worker-manager/guide/exploring-the-dashboard.md): A tour of how the dashboard is laid out and the controls you'll use day to day: grouped queues, the collapsible sidebar, search, and the per-queue info panel.
- [Installation](/worker-manager/guide/getting-started.md): Install the core @worker-manager/api plus one adapter for your framework.
- [Playground](/worker-manager/guide/playground.md): The repository ships a playground, a small NestJS app in playground/ that exercises the whole stack before you wire the board into your own service. It exists to validate changes to the library itself, and to show a complete, working setup you can copy from. It brings up: Redis for six classic BullMQ queues (notifications.*, payments.*, reports.generate, orders.pipeline), with flows and two job schedulers.PostgreSQL for two BullMQ v6 queues stored in Postgres (pg.invoices, pg.data-exports).A second board over pg-boss at /pg-boss, on a pgboss schema in the same PostgreSQL. See the pg-boss board.Keycloak 26 with a pre-imported realm, a confidential client, and two users.Synthetic traffic: workers with random latency, progress, logs and failures, so every view of the board has something in it.
- [Your first dashboard](/worker-manager/guide/your-first-dashboard.md): Express and BullMQ. Every other adapter follows the same three-step shape, see the adapter pages for specifics.
- [BullAdapter](/worker-manager/queue-adapters/bull.md): For the Bull queue library.
- [BullMQ Pro](/worker-manager/queue-adapters/bullmq-pro.md): For the BullMQ Pro queue library. Import BullMQProAdapter instead of BullMQAdapter to get awareness of Pro groups: the jobs held by waiting/limited/maxed/paused groups are folded into the waiting/delayed/paused job counts, those jobs are listed alongside regular jobs in the same tabs, and the group id is shown next to the job name in the UI.
- [BullMQAdapter](/worker-manager/queue-adapters/bullmq.md): For the BullMQ queue library.
- [Queue engines](/worker-manager/queue-adapters/index.md): A board runs one engine. The BullMQ engine is the default and has been there all along: it drives Bull, BullMQ and BullMQ Pro queues through queue adapters, on Redis or on PostgreSQL. The pg-boss engine, stable since 2.4.0, mounts a board over a pg-boss schema, with pages of its own. The shell around them (sidebar, command palette, themes, auth, server adapters, NestJS module, CLI) is the same. The rest of this page is about the BullMQ engine's queue adapters, which @worker-manager/api ships with; third-party queue systems can add their own. The pg-boss engine takes no queue adapters: it lists the queues of its schema itself. See its page. BullMQProAdapter extends BullMQAdapter to handle Pro groups. All BullMQAdapter options work the same way on it. BullMQAdapter covers BullMQ v5 and v6, including v6 queues stored in PostgreSQL. See supported versions for the two differences you can see in the UI.
- [pg-boss](/worker-manager/queue-adapters/pg-boss.md): pg-boss is a job queue that lives entirely in PostgreSQL. @worker-manager/pg-boss mounts a whole board over one pg-boss schema: the same shell, sidebar, command palette, themes and auth as a BullMQ board, with pages built around what pg-boss actually stores. It lists queues with their policy and cached counters, shows jobs in all six pg-boss states, and lets you retry, cancel, resume, delete and send jobs and edit schedules. A board runs one engine. BullMQ and pg-boss queues never share a board. If your app has both, mount two boards side by side. Open the pg-boss demo to click around it. The header's links menu switches between the BullMQ and pg-boss boards.
- [Access control hooks](/worker-manager/recipes/access-control-hooks.md): Gate individual Worker Manager API calls with before and after hooks, for per-role or per-action access control beyond read-only mode and visibility guards.
- [Alerting on failed jobs](/worker-manager/recipes/alerting.md): Applies to: all adapters. Worker Manager is a viewer, not a monitor. It shows the state of your queues while you have the tab open. It doesn't watch them for you and it sends nothing anywhere, so "how do I get alerted when a job fails?" isn't a question the dashboard answers. That part is on you, and it belongs in your worker code, not the board. BullMQ already emits the events you need. Wire your alerting to those, and use Worker Manager to investigate once an alert fires.
- [Add basic auth](/worker-manager/recipes/basic-auth.md): Don't expose the dashboard on the open internet without auth. The quickest route is the built-in middleware from @worker-manager/auth, which works on every Node framework. The framework-native approaches further down remain valid alternatives when you already have a login in your app.
- [Polling interval](/worker-manager/recipes/change-polling-interval.md): The dashboard polls GET /api/queues to refresh. Default is 5 seconds. Two knobs in UIConfig.
- [CSRF protection](/worker-manager/recipes/csrf-protection.md): The dashboard's destructive actions (retry, clean, pause, obliterate) are state-changing PUT/POST calls. If the dashboard lives on the same origin as an untrusted user session, protect those calls with CSRF tokens. From examples/express/csrf. The example uses csrf-csrf (double-submit cookie pattern). The older csurf package is deprecated, don't reach for it. Two pieces are at play. doubleCsrfProtection rejects any non-GET request to /ui that lacks a valid token. The XSRF-TOKEN cookie is readable by the dashboard's bundled Axios, which mirrors it into the x-xsrf-token header on every request. That matches what getTokenFromRequest pulls off. The extra res.cookie dance exists because the base cookie set by csrf-csrf is httpOnly (tracked in a pending upstream PR). Dashboard JS needs a readable copy, hence the second cookie. If you're behind a login with SameSite=Lax session cookies, modern browsers already block most cross-site POSTs. Belt-and-suspenders CSRF on top doesn't hurt for high-value dashboards.
- [Custom auth](/worker-manager/recipes/custom-auth.md): The custom strategy of @worker-manager/auth hands the decision to your code: validate a Cloudflare Access JWT, reuse your app's API-key check, trust a header set by an authenticating proxy. The rest of the middleware behaves as for the built-in strategies: req.user is set, the onAuthenticated hook runs, GET ${basePath}/auth/me answers, and the dashboard header shows who is signed in.
- [Link jobs to your own admin](/worker-manager/recipes/external-job-url.md): If your jobs exist in your own admin app too (an order, an email, a rendered report), you can link each dashboard job card to the corresponding page in your app. Pass externalJobUrl to the queue adapter: The function receives the job's JSON representation and returns { href, displayText? }. The dashboard adds a link next to the job name pointing at your URL. Useful when debugging from the dashboard and you want to jump straight to the real-world record.
- [Formatters](/worker-manager/recipes/formatters.md): Applies to: all adapters. Formatters rewrite how a job's fields render in the dashboard without touching producer or worker code. Useful when data, returnValue, name, or progress need a presentation layer: masking secrets, summarising big payloads, humanising enums, prefixing names in multi-tenant setups.
- [Global concurrency](/worker-manager/recipes/global-concurrency.md): BullMQ supports a global cap on concurrent jobs across all workers for a queue. The dashboard can read and change it.
- [Historical metrics](/worker-manager/recipes/historical-metrics.md): Applies to: BullMQ and pg-boss boards. Worker Manager is a viewer, not a monitor, and its built-in throughput chart reflects that: it reads BullMQ's native queue.getMetrics(), a per-minute ring buffer capped at maxDataPoints, scoped to a single queue, and only as deep as that buffer's window. Restart the buffer's window, or just wait long enough, and the older points are gone. There's no long history and no cross-queue total, because BullMQ was never asked to keep one. @worker-manager/metrics is an opt-in companion package that fills that gap. It doesn't replace the live chart, it adds a second, longer-retention path behind it: a recorder that snapshots the native metrics into Redis (or PostgreSQL) before they roll off, and a history provider you register with createWorkerManagerBoard that lets the UI read them back.
- [Recipes](/worker-manager/recipes/index.md): Short, code-first walkthroughs for common setups. Each recipe is a page; each page ties back to a runnable example in the repo. Most recipes link into the live demo so you can see the shape before porting it.
- [Job logs and flows](/worker-manager/recipes/job-logs-and-flows.md): Two features people often miss.
- [Keycloak auth](/worker-manager/recipes/keycloak-auth.md): @worker-manager/auth puts a Keycloak (OpenID Connect) login in front of the dashboard. Browsers go through the authorization code flow with PKCE and keep an encrypted session cookie; scripts and other services send a bearer token. It is one framework-agnostic middleware, so the same options work on Express, Fastify, Koa, NestJS and the CLI.
- [Add and remove queues at runtime](/worker-manager/recipes/manage-queues-at-runtime.md): Applies to: all adapters. createWorkerManagerBoard isn't a one-shot call. It also returns four functions that change which queues the board shows while it's running, with no rebuild or restart: Use them when queues aren't all known at startup: per-tenant queues created on demand, queues discovered from a registry, or a dashboard that follows queues as workers spin them up and down.
- [Multiple dashboards in one app](/worker-manager/recipes/multiple-dashboards.md): You might want separate dashboards for different queue groups. One per team, or one read-only and one read-write. From examples/express/multiple-boards. Each adapter is independent. Pass a different UIConfig per instance (boardTitle, boardLogo, environment badge) to make them visually distinct.
- [Next.js & Vercel](/worker-manager/recipes/nextjs.md): There is no dedicated Next.js adapter. Worker Manager runs inside a Next.js API route using an existing adapter. Two runnable examples: examples/nextjs/app-router: App Router, @worker-manager/hono adapter.examples/nextjs/pages-router: Pages Router, @worker-manager/express adapter. Both deploy to Vercel. The mounting differs by router; the Vercel-specific config is identical and is the part most people miss.
- [Per-tenant visibility](/worker-manager/recipes/per-tenant-visibility.md): Show each user only the queues they're allowed to see. One shared dashboard, per-request filtering. See also: Visibility guard for the full reference. From examples/fastify/visibility-guard (Fastify + cookie auth + JWT).
- [PostgreSQL backend](/worker-manager/recipes/postgres-backend.md): BullMQ v6 can store queues in PostgreSQL instead of Redis. Worker Manager reads those queues the same way it reads Redis ones, so there is nothing extra to configure on the board.
- [Rate limits](/worker-manager/recipes/rate-limits.md): BullMQ has two things called a rate limit and the dashboard exposes both, because they answer different questions. The configured limit is a setting: how many jobs this queue may process across all its workers in a window of time. It lives beside global concurrency and you edit it the same way. The active limit is a state: what a worker wrote when it called queue.rateLimit(ms), usually because a third-party API pushed back. While it is set, nothing on the queue runs. It expires on its own, and until then a queue with healthy workers and a full waiting list does nothing at all.
- [Read-only mode](/worker-manager/recipes/read-only-mode.md): Applies to: all adapters, and pg-boss boards. Read-only mode disables every destructive action on a queue. No retries, no removals, no queue operations (pause, resume, empty, clean, obliterate), no adding jobs. Use it to share the dashboard with stakeholders, support, or shared dev environments without risking anything.
- [Token auth](/worker-manager/recipes/token-auth.md): The token strategy of @worker-manager/auth protects the dashboard with one or more static tokens, with no usernames and no identity provider. Scripts send the token in a header; a browser types it once into a small login form and gets an encrypted session cookie. It suits internal boards where a Keycloak realm is overkill and a shared Basic password is not wanted in the browser's credential cache.
- [Troubleshooting](/worker-manager/recipes/troubleshooting.md): Applies to: all adapters. The failure modes below account for most "it won't load" reports. Nearly all of them come down to one thing: the path Worker Manager thinks it's mounted at doesn't match the path the browser actually requests.
- [Visibility guard](/worker-manager/recipes/visibility-guard.md): Applies to: all adapters. A visibility guard is a per-request predicate on a queue adapter that decides whether the requester can see that queue. Use it for multi-tenant setups, or for role-based access on a shared dashboard.
- [Whitelabel the dashboard](/worker-manager/recipes/whitelabel-theming.md): The board is styled entirely from CSS custom properties, and uiConfig.theme overrides them per colour scheme. You do not fork the UI or ship a stylesheet over the top of it. The tokens you set are written into the page at boot. Token names follow the shadcn theme contract, so a palette from any shadcn-compatible theme editor drops straight in.
- [HTTP API reference](/worker-manager/reference/http-api.md): The JSON API the Worker Manager dashboard serves, generated from the route table so it always matches the routes the board registers.
- [Bun](/worker-manager/server-adapters/bun.md): Bun. @worker-manager/bun targets Bun's native HTTP server.
- [Elysia](/worker-manager/server-adapters/elysia.md): Elysia on Bun. @worker-manager/elysia is an Elysia plugin.
- [Express](/worker-manager/server-adapters/express.md): Express.js. @worker-manager/express mounts as a sub-router under any path.
- [Fastify](/worker-manager/server-adapters/fastify.md): Fastify. @worker-manager/fastify registers as a plugin.
- [H3](/worker-manager/server-adapters/h3.md): H3. @worker-manager/h3 plugs in as an H3 event handler.
- [Hapi](/worker-manager/server-adapters/hapi.md): Hapi. @worker-manager/hapi registers as a Hapi plugin.
- [Hono](/worker-manager/server-adapters/hono.md): Hono. @worker-manager/hono gives you a Hono sub-app.
- [Server Adapters](/worker-manager/server-adapters/index.md): One server adapter per framework. The core @worker-manager/api package is shared. Pick your framework below.
- [Koa](/worker-manager/server-adapters/koa.md): Koa. @worker-manager/koa gives you middleware to mount on your app.
- [NestJS](/worker-manager/server-adapters/nestjs.md): NestJS. Worker Manager ships a NestJS module plus a plain adapter you can wire manually.