Workbench
The dashboard — served by your worker, or mounted in your own app.
Workbench is OpenQueue's dashboard: queue overview with live counters and latency percentiles, a virtualized runs table with payloads and logs, flow graphs, schedule management, error triage grouped by class, and a test console for enqueueing jobs by hand.
Served by the worker
The zero-effort path — enable it in your config and the worker hosts it:
export default defineConfig({
// …
workbench: {
enabled: true,
title: 'Jobs',
basePath: '/workbench',
auth: {
username: process.env.WORKBENCH_USER!,
password: process.env.WORKBENCH_PASSWORD!,
},
},
});Mounted in Next.js
Serve the dashboard from the app you already deploy, behind your existing auth. Create a catch-all route:
import { workbench } from '@openqueue/workbench/next';
export const { GET, POST, PUT, PATCH, DELETE } = workbench({
redis: { url: process.env.REDIS_URL! },
basePath: '/admin/jobs',
});Leave basePath unset when the route lives at the app root.
Mounted in h3
import { createWorkbenchApp } from '@openqueue/workbench/h3';
const dashboard = await createWorkbenchApp({
redis: { url: process.env.REDIS_URL! },
});
app.mount('/workbench', dashboard);When redis is provided without an explicit queue list, Workbench discovers
queues automatically from the published queue catalog.
Mounted in other hosts
The dashboard is a plain fetch handler, so it mounts into any fetch-native host —
no shipped adapter needed. createFetchHandler returns a .fetch(request);
createWorkbenchApp returns an H3 app you can hand to h3's Node adapter.
// Hono — mount the fetch handler under a prefix
import { createFetchHandler } from '@openqueue/workbench';
honoApp.mount(
'/admin/jobs',
createFetchHandler({ redis: { url: process.env.REDIS_URL! }, basePath: '/admin/jobs' })
.fetch,
);// Express — h3's Node adapter bridges the app onto a middleware
import { toNodeHandler } from 'h3';
import { createWorkbenchApp } from '@openqueue/workbench/h3';
const app = await createWorkbenchApp({ redis: { url: process.env.REDIS_URL! } });
expressApp.use('/admin/jobs', toNodeHandler(app));Read-only mode
Set readonly: true to expose the dashboard to a wider audience without
retry/enqueue/pause actions — useful for support teams and stakeholders.
On non-BullMQ worlds
Workbench can mount on a worker backed by a non-BullMQ world (a world in the
config instead of redis). Much of it still works; the parts that read BullMQ
directly degrade gracefully rather than erroring. This matrix is the code truth:
- Fully works: the shell, config, and auth; the test console (enqueues
through the registry →
runtime.trigger); dynamic schedule CRUD; alert contact-point and rule CRUD (persisted in the world's own store). - Inert: alert firing — it evaluates BullMQ queue metrics, which a non-BullMQ world doesn't expose, so rules never trigger.
- Empty: the Runs page reads BullMQ, not the core run store, so run history
for these worlds lives on the control API (
/openqueue/v1) and the client, not the dashboard. Overview, counts, metrics, errors, activity, search, and tags render as zeros; queue pages, job detail/logs/spans, the retry/remove/promote/clean/bulk/pause actions, repeatable schedulers, and the Prometheus endpoint all read raw BullMQ and stay empty.
When the worker mounts Workbench on a world with no BullMQ queues it logs a
one-line reminder pointing at /openqueue/v1 for run history.
Prior art
Hat tip to pontusab/workbench, the BullMQ dashboard that set the bar for what queue tooling should feel like. OpenQueue ships that caliber of dashboard as one piece of a larger machine — tasks, retries, flows, schedules, and persistence, designed together with the UI rather than bolted on next to it.