Until it gets proper support I thought to share some work here in case others wants to also use latest bull board with groupMQ.
Since It was giving all sorts of errors, I've run through it with Claude so that AT LEAST it works in a read-only manner.. :/ I wanted to display both bullmq and groupmq queues in the same admin UI. (using groupMQ for group based jobs and bullmq when we need priority queues and other features incompatible)
So I'm sharing it here to save some tokens for those who are in the same boat:
Summary
groupmq's BullBoardGroupMQAdapter was built against @bull-board/api@^6.13.0 (its own devDependency), but this project has @bull-board/api@9.3.2 installed. Between v6 and v9, BaseAdapter gained new abstract members that groupmq never implemented: getGlobalConcurrency,
setGlobalConcurrency, getJobSchedulers, getJobSchedulersCount, removeJobScheduler, updateJobScheduler, getMetrics, obliterate.
Two consequences, one of them severe:
- Runtime crash of the whole board — handlers/queues.js in @bull-board/api calls queue.getGlobalConcurrency() and queue.getJobSchedulersCount() unconditionally for every queue when building /api/queues, inside a single Promise.all with no per-queue error handling. Since those
methods don't exist on the groupmq adapter, this throws and breaks the entire dashboard, including your working BullMQAdapter queue — not just the GroupMQ tab.
- It doesn't even type-check — I confirmed tsc fails on groupmq's own shipped .d.ts (TS2655: BullBoardGroupMQAdapter is missing 8 BaseAdapter members) as soon as @bull-board/api@9.x types are in node_modules, independent of any code you write.
Fix applied:
- Added /groupMQAdapter.ts — a GroupMQAdapter subclass of BullBoardGroupMQAdapter implementing the 8 missing methods as honest stubs (groupmq has no queue-level concurrency setting or job-scheduler registry, so these
return null/0/[]/false/'not-found' rather than faking support; getMetrics returns an empty series; obliterate throws a clear "not supported" error).
- Updated ui.ts to use GroupMQAdapter instead of BullBoardGroupMQAdapter directly.
- Verified with tsc --noEmit that this compiles cleanly with skipLibCheck: true (you'll need that flag in any tsconfig you use here, since groupmq's own declarations don't satisfy v9's BaseAdapter regardless of the fix above).
One minor cosmetic note (not fixed, low priority): groupmq passes its Redis namespace as the adapter's type field, where bull-board expects 'bull' | 'bullmq'. This only gates BullMQ Job-Flow features (correctly falls through to "unsupported" for GroupMQ), so it's harmless but
worth knowing if you see an unexpected type value in the API response.
import { BullBoardGroupMQAdapter } from 'groupmq';
import type {
JobSchedulerRepeatOptions,
JobSchedulerUpdateResult,
MetricsType,
ObliterateOptions,
QueueDefaultJobOptions,
QueueJob,
QueueMetrics,
QueueWorker,
RedisStats,
} from '@bull-board/api/typings/app';
/**
* groupmq's BullBoardGroupMQAdapter was built against @bull-board/api ^6.13.0.
* Its bundler vendored that version's BaseAdapter directly into groupmq's dist
* output instead of importing the one actually installed here (9.x), so this
* adapter is missing everything BaseAdapter grew since v6 - both the abstract
* members (getGlobalConcurrency/getJobSchedulersCount etc, patched below) and
* the non-abstract ones that used to just come from the base class default
* (getWorkers, getDatastoreStats, getArmedJobSchedulerId, getQueueDefaultJobOptions).
* getWorkers() in particular is called unconditionally for every queue on every
* board load, so without it the whole board 500s, not just the GroupMQ queue.
* GroupMQ has no queue-level concurrency setting, job-scheduler registry, or
* worker/connection introspection, so these are honest stubs rather than real
* mappings.
*/
export class GroupMQAdapter extends BullBoardGroupMQAdapter {
async getWorkers(): Promise<QueueWorker[] | null> {
return null;
}
getJobDataSchema(): Record<string, any> | undefined {
return undefined;
}
async getDatastoreStats(): Promise<RedisStats | null> {
return null;
}
async getArmedJobSchedulerId(_job: QueueJob): Promise<string | null> {
return null;
}
getQueueDefaultJobOptions(): QueueDefaultJobOptions {
return {};
}
get supportsJobSchedulerUpdate(): boolean {
return false;
}
async getMetrics(_type: MetricsType, _start?: number, _end?: number): Promise<QueueMetrics> {
return { meta: { count: 0, prevTS: 0, prevCount: 0 }, data: [], count: 0 };
}
async obliterate(_opts?: ObliterateOptions): Promise<void> {
if (this.readOnlyMode) {
throw new Error('This adapter is in read-only mode. Mutations are disabled.');
}
throw new Error('GroupMQ does not support obliterating a queue.');
}
async getGlobalConcurrency(): Promise<number | null> {
return null;
}
async setGlobalConcurrency(_concurrency: number): Promise<void> {
throw new Error('GroupMQ does not support setting a global concurrency limit.');
}
async getJobSchedulers() {
return [];
}
async getJobSchedulersCount(): Promise<number> {
return 0;
}
async removeJobScheduler(_id: string): Promise<boolean> {
return false;
}
async updateJobScheduler(
_id: string,
_repeat: JobSchedulerRepeatOptions
): Promise<JobSchedulerUpdateResult> {
return 'not-found';
}
}
with the above the latest bull board admin UI works again - using the example from the home page as a baseline:
// @ts-ignore
import express from 'express';
import { createBullBoard } from '@bull-board/api';
import { ExpressAdapter } from '@bull-board/express';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
import { Queue as GroupMQQueue } from 'groupmq';
import { Queue as BullMQQueue } from 'bullmq';
import Redis from "ioredis";
import { GroupMQAdapter } from './groupMQAdapter';
// GroupMQ queue configuration
const app = express();
const serverAdapter = new ExpressAdapter();
const redis = new Redis("redis://127.0.0.1:6379");
const groupMQQueue = new GroupMQQueue({ redis, namespace: 'group' });
const groupMQAdapter = new GroupMQAdapter(groupMQQueue, { displayName: 'group (groupMQ)' });
// BullMQ queue configuration
const redisOptions = { host: 'localhost', port: 6379, password: undefined, };
const bullMQQueue = new BullMQQueue ( 'bull', { connection: redisOptions } );
const bullMQAdapter = new BullMQAdapter ( bullMQQueue, { displayName: 'bull (bullMQ)' } );
createBullBoard({
queues: [
groupMQAdapter,
bullMQAdapter
],
serverAdapter,
});
serverAdapter.setBasePath('/admin/queues');
app.use('/admin/queues', serverAdapter.getRouter());
app.listen(4000);
console.log( 'UI started on http://localhost:4000/admin/queues' );
Until it gets proper support I thought to share some work here in case others wants to also use latest bull board with groupMQ.
Since It was giving all sorts of errors, I've run through it with Claude so that AT LEAST it works in a read-only manner.. :/ I wanted to display both bullmq and groupmq queues in the same admin UI. (using groupMQ for group based jobs and bullmq when we need priority queues and other features incompatible)
So I'm sharing it here to save some tokens for those who are in the same boat:
Summary
groupmq's BullBoardGroupMQAdapter was built against @bull-board/api@^6.13.0 (its own devDependency), but this project has @bull-board/api@9.3.2 installed. Between v6 and v9, BaseAdapter gained new abstract members that groupmq never implemented: getGlobalConcurrency,
setGlobalConcurrency, getJobSchedulers, getJobSchedulersCount, removeJobScheduler, updateJobScheduler, getMetrics, obliterate.
Two consequences, one of them severe:
methods don't exist on the groupmq adapter, this throws and breaks the entire dashboard, including your working BullMQAdapter queue — not just the GroupMQ tab.
Fix applied:
return null/0/[]/false/'not-found' rather than faking support; getMetrics returns an empty series; obliterate throws a clear "not supported" error).
One minor cosmetic note (not fixed, low priority): groupmq passes its Redis namespace as the adapter's type field, where bull-board expects 'bull' | 'bullmq'. This only gates BullMQ Job-Flow features (correctly falls through to "unsupported" for GroupMQ), so it's harmless but
worth knowing if you see an unexpected type value in the API response.
with the above the latest bull board admin UI works again - using the example from the home page as a baseline: