Async job queue dashboard for Python — Sidekiq Web UI for ARQ
Project description
Worq
The Sidekiq Web UI for Python job queues.
Mount a production-grade dashboard inside your FastAPI app in two lines. Monitor jobs, retry failures, search history, track metrics — all from a single interface.
ARQ — fully tested in production. Celery and RQ adapters included in beta (community testing welcome).
More screenshots
| Queues | Scheduled Jobs |
|---|---|
| Failed Jobs | Workers & Active Jobs |
|---|---|
| Cron Jobs |
|---|
from worq import create_dashboard
from worq.adapters.arq import ARQAdapter
app.mount("/worq", create_dashboard(ARQAdapter(redis_url="redis://localhost")))
No separate process. No external service. Your dashboard lives inside your app.
Adapter Status
| Adapter | Status | Tested With |
|---|---|---|
| ARQ | Stable — production-tested | Real ARQ workers, multiple queues, 10K+ jobs |
| Celery | Beta — code complete, needs real-world validation | Seeded Redis data matching Celery format |
| RQ | Beta — code complete, needs real-world validation | Seeded Redis data matching RQ format |
| Custom | SDK available — AdapterBase class + helpers |
Compliance test harness included |
Celery and RQ adapters are architecturally sound (read the correct Redis keys, handle all statuses) but haven't been tested against real Celery/RQ workers in production. Community testing and bug reports welcome.
The Problem
Every Python job queue has the same gap: no management UI.
When a production job fails at 2 AM:
| Rails developer | Python developer |
|---|---|
| Opens Sidekiq UI | SSHes into server |
| Sees the failed job | Opens Redis CLI |
| Reads the traceback | Searches for the key |
| Clicks Retry | Writes a retry script |
| Done in 30 seconds | Hopes it works |
Flower requires a separate deployment. rq-dashboard is Flask-only and stale. ARQ has nothing.
Worq closes this gap.
What You Get
Dashboard Overview
- 5 stat cards — Enqueued, Active, Scheduled, Failed, Complete (live-updating)
- Active jobs — Currently executing jobs with function name, queue, running duration
- Throughput metrics — Processed count, failure rate, average duration (last 24h)
- Recent failures — Quick retry buttons for the last 5 failed jobs
Per-Queue Breakdown
- Each queue listed with its own enqueued/active/scheduled/failed/complete counts
- Total row at the bottom
- See which queue is backed up at a glance
Job Management
- Search by function name, status, queue, time range
- Retry failed jobs with one click
- Delete jobs from any state
- Run Now — execute scheduled jobs immediately
- Bulk operations — select up to 500 jobs, retry or delete all at once
- Paginated — handles 100K+ jobs without performance degradation
Worker Health & Active Jobs
- Per-queue health status (ALIVE / NO WORKERS) from ARQ health-check keys
- Active jobs table with function, queue, and running duration
- Color-coded badges: >60s yellow, >300s red
Cron Jobs
- View configured cron schedules with last run and result
- Trigger manually from the dashboard
- Validates function names — no orphan jobs
Alerting
from worq.alerts import AlertRule, SlackTarget
dashboard = create_dashboard(
adapter,
alerts=[
AlertRule(
name="high-failure-rate",
condition="failure_rate > 5",
targets=[SlackTarget(webhook_url="https://hooks.slack.com/...")],
cooldown_minutes=30,
),
],
)
Safe condition DSL — no eval(), no exec(). Whitelist parser only.
Audit Log
from worq.audit import AuditLog
dashboard = create_dashboard(
adapter,
allow_write=True,
audit_log=AuditLog(redis_url="redis://localhost"),
)
Records every retry, delete, and enqueue with user identity, timestamp, and job IDs.
Authentication
from worq.auth.basic import BasicAuth, hash_password
dashboard = create_dashboard(
adapter,
auth=BasicAuth("admin", hash_password("secret")),
)
Or bring your own: any async (request) -> bool callable works.
Worq vs Alternatives
| Feature | Worq | Flower (Celery) | rq-dashboard |
|---|---|---|---|
| Mountable in FastAPI | 2 lines | Separate process | Separate process |
| ARQ + Celery + RQ | ARQ stable, Celery/RQ beta | Celery only | RQ only |
| Live updates (WebSocket) | Yes (WS → SSE → polling) | Polling only | Polling only |
| Job search & filtering | Full | Limited | Limited |
| Bulk retry/delete | Yes (500/batch) | No | No |
| Per-queue breakdown | Yes | No | Basic |
| Throughput metrics | Yes | No | No |
| Cron management | Yes | No | No |
| Alerting (Slack/webhook) | Yes | No | No |
| Audit log | Yes | No | No |
| Dark mode | Yes | No | No |
| Mobile responsive | Yes | Partial | No |
| Pagination | Yes | Limited | No |
| JSON only (no pickle) | Yes | Pickle default | Pickle default |
| Security headers | Yes | No | No |
Install
pip install worq-dashboard[ui] # Dashboard + ARQ (production-ready)
pip install worq-dashboard[ui,celery] # + Celery adapter (beta)
pip install worq-dashboard[ui,rq] # + RQ adapter (beta)
Note: The PyPI package is
worq-dashboardbut you import asfrom worq import ...
Requires: Python 3.12+, Redis
Quick Start
ARQ (most common)
from fastapi import FastAPI
from worq import create_dashboard
from worq.adapters.arq import ARQAdapter
app = FastAPI()
adapter = ARQAdapter(redis_url="redis://localhost:6379")
app.mount("/worq", create_dashboard(adapter))
Important: ARQ uses pickle by default. Worq requires JSON:
import json class WorkerSettings: job_serializer = json.dumps job_deserializer = json.loads
Celery
from worq.adapters.celery import CeleryAdapter
adapter = CeleryAdapter(redis_url="redis://localhost:6379")
app.mount("/worq", create_dashboard(adapter))
RQ
from worq.adapters.rq import RQAdapter
adapter = RQAdapter(redis_url="redis://localhost:6379")
app.mount("/worq", create_dashboard(adapter))
Open http://localhost:8000/worq — that's it.
Multi-Queue Setup
adapter = ARQAdapter(
redis_url="redis://localhost:6379",
queue_names=["arq:queue:critical", "arq:queue:high", "arq:queue:default", "arq:queue:low"],
allow_write=True,
)
The Queues page shows each queue separately. See which one is backed up.
Multi-Backend
Monitor ARQ and Celery from one dashboard:
dashboard = create_dashboard(
adapters={
"arq": ARQAdapter(redis_url="redis://localhost"),
"celery": CeleryAdapter(redis_url="redis://localhost:6380"),
}
)
Multi-Tenant
Separate staging and production:
dashboard = create_dashboard(
tenants={
"production": ARQAdapter(redis_url="redis://prod:6379"),
"staging": ARQAdapter(redis_url="redis://staging:6379"),
}
)
Production Ready
Worq is designed for production from day one:
- Stateless — pure Redis reads, no in-process state. Run 100 dashboard instances behind a load balancer.
- O(1) page loads — atomic Redis counters for complete/failed counts. No scanning at page-load time.
- 2-second cache — in-process cache prevents redundant Redis queries from concurrent users.
- Security headers — X-Frame-Options, X-Content-Type-Options, Referrer-Policy on every response.
- Read-only by default — write actions require explicit
allow_write=True. - XSS-safe — all dynamic HTML is escaped. No
eval(), noexec(), no pickle.
API
Full REST API for programmatic access. Every dashboard feature is available via API.
| Endpoint | Description |
|---|---|
GET /api/queues |
Queue stats |
GET /api/queues/{queue}/jobs |
Job listing with status filter |
GET /api/jobs/{id} |
Job detail |
GET /api/search |
Search with filters |
GET /api/workers |
Worker status |
GET /api/scheduled |
Scheduled jobs |
GET /api/failed |
Failed jobs |
GET /api/metrics |
Throughput metrics |
GET /api/cron |
Cron job list |
GET /api/alerts |
Alert rules |
GET /api/audit |
Audit log |
POST /api/jobs/{id}/retry |
Retry a job |
DELETE /api/jobs/{id} |
Delete a job |
POST /api/scheduled/{id}/enqueue |
Run scheduled job now |
POST /api/bulk/retry |
Bulk retry (up to 500) |
POST /api/bulk/delete |
Bulk delete (up to 500) |
POST /api/cron/{name}/trigger |
Trigger cron job |
WS /ws/stats |
Live stats stream |
GET /api/sse/stats |
SSE stats fallback |
Full schemas: API Reference
Documentation
| Guide | What you'll learn |
|---|---|
| Getting Started | Install, configure, mount — 5 minutes to first dashboard |
| Configuration | Every create_dashboard() parameter explained |
| Adapters | ARQ, Celery, RQ deep-dive + how to write custom adapters |
| Features | Search, bulk ops, metrics, live updates, pagination |
| Authentication | Basic auth, custom auth, session integration |
| Alerting | Condition DSL, webhook/Slack targets, cooldown |
| Audit Log | Write action tracking with user attribution |
| Cron Jobs | View schedules, trigger manually |
| Multi-Backend & Multi-Tenant | Multiple queues, multiple environments |
| ARQ Production Guide | Recommended ARQ settings for production |
| Deployment | Docker, Kubernetes, nginx, scaling |
| Troubleshooting | Common issues and fixes |
| API Reference | All endpoints with request/response examples |
| UI Guide | Dark mode, mobile, accessibility, HTMX |
Tech Stack
| Layer | Choice | Why |
|---|---|---|
| Language | Python 3.12+ | Modern typing, mypy --strict |
| Framework | FastAPI / Starlette | Mountable sub-app, async native |
| Data | Redis (async redis-py) | Same store ARQ/Celery/RQ use |
| Models | Pydantic v2 | Validation + serialization |
| Frontend | Jinja2 + HTMX | Server-rendered, no JS build step |
| Logging | structlog | Structured JSON |
| Security | JSON only | No pickle, no eval, no exec |
Quality
| Metric | Value |
|---|---|
| Tests | 482 (real Redis via testcontainers) |
| Coverage | 100% line coverage |
| Type safety | mypy --strict, zero errors |
| Lint | ruff, zero warnings |
| Accessibility | WCAG 2.1 AA |
| License | MIT |
Contributing
Worq is open source under the MIT license. Issues, feature requests, and pull requests welcome.
git clone https://github.com/NitinSingh8/worq.git
cd worq
uv sync --all-extras # Install everything
uv run pytest tests/ -v # Run tests (needs Docker for Redis)
uv run mypy src/worq --strict
License
MIT — same as FastAPI, ARQ, and Pydantic.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file worq_dashboard-0.5.0.tar.gz.
File metadata
- Download URL: worq_dashboard-0.5.0.tar.gz
- Upload date:
- Size: 1.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea5e091f696c71e0dabbb06be45085c5fabcf2c2c5f5fa8e32454233734bf590
|
|
| MD5 |
05e70f56baaee06b1fbc1edb268d3d11
|
|
| BLAKE2b-256 |
33a1f5d2b280b9ffc36d087030f6633f346ad26588b277a927aaf7ba6933548f
|
File details
Details for the file worq_dashboard-0.5.0-py3-none-any.whl.
File metadata
- Download URL: worq_dashboard-0.5.0-py3-none-any.whl
- Upload date:
- Size: 89.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cb409bdd2750f2f4f935d050058fe2db90832b353c89d45a8ac6362516715a01
|
|
| MD5 |
42829c29e983400fcc78259899594c1e
|
|
| BLAKE2b-256 |
13db468c5fa08ee12091f48d48b14e802ad666c321b84f95bd71db4eaf90fa41
|