Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Admin UI

Graphile Worker RS includes an embedded admin UI for inspecting queues, jobs, and workers, plus a JSON API for the same operations. The implementation is split across three crates:

  • graphile_worker_admin_api defines shared request and response types, and SQLx-backed read queries when built with the sqlx feature.
  • graphile_worker_admin_ui serves the native Axum application, the HTML page, embedded CSS, JavaScript, WASM, and JSON API routes.
  • graphile_worker_admin_ui_client contains the WASM client that runs in the browser.

The CLI uses these crates to expose the graphile-worker admin command.

Starting the Server

The admin command starts a native HTTP server and connects it to the same Postgres schema and WorkerUtils used by the rest of the CLI.

graphile-worker admin

By default it listens on 127.0.0.1:5678, uses HTTP Basic authentication with username admin, and generates a password when none is provided.

Useful options include:

graphile-worker admin --listen 127.0.0.1:5678
graphile-worker admin --auth basic --username admin
graphile-worker admin --auth bearer
graphile-worker admin --auth header --header-name x-graphile-worker-admin-token
graphile-worker admin --read-only

The CLI also reads these environment variables for configured secrets:

GRAPHILE_WORKER_ADMIN_PASSWORD="<password>" graphile-worker admin --auth basic
GRAPHILE_WORKER_ADMIN_BEARER_TOKEN="<token>" graphile-worker admin --auth bearer
GRAPHILE_WORKER_ADMIN_HEADER_TOKEN="<token>" graphile-worker admin --auth header

When a Basic password, bearer token, or header token is generated, the CLI prints it at startup. Configured secrets are not printed.

Native Embedding

Applications can build the Axum server directly with graphile_worker_admin_ui::AdminServerConfig and graphile_worker_admin_ui::serve.

use graphile_worker::{Schema, WorkerUtils};
use graphile_worker_admin_ui::{AdminAuthConfig, AdminServerConfig};
use sqlx::PgPool;

async fn serve_admin(pool: PgPool, utils: WorkerUtils) -> anyhow::Result<()> {
    let config = AdminServerConfig::builder(pool, utils)
        .schema(Schema::default())
        .schema_name("graphile_worker")
        .listen_addr("127.0.0.1:5678".parse()?)
        .auth(AdminAuthConfig::basic_with_random_password("admin"))
        .read_only(false)
        .build()?;

    graphile_worker_admin_ui::serve(config).await?;
    Ok(())
}

When embedding the admin UI directly, the config builder defaults to:

  • schema Schema::default()
  • schema name graphile_worker
  • listen address 127.0.0.1:4000
  • generated Basic auth for username admin
  • read-write mode

Authentication

The server supports four auth modes:

ModeBehavior
basicRequires HTTP Basic credentials. This is the default CLI mode.
bearerRequires Authorization: Bearer <token>.
headerRequires a configured header name whose value matches the token.
noneRequires no auth, but is only allowed on loopback listen addresses.

Both the CLI auth builder and the native config validation reject unauthenticated servers bound to non-loopback addresses.

For Basic auth, the server also applies page authentication to the browser page itself. API routes are authenticated in every mode except none.

CSRF and Security Headers

The server generates a CSRF token when it builds application state. Every mutating API request must include that token in the x-graphile-worker-admin-csrf header. The browser client reads the header name and token from the rendered page/session data and sends it on writes.

The server adds security headers to all responses:

  • Cache-Control: no-store for pages and API responses
  • Cache-Control: public, max-age=3600 for /assets/* and /favicon.ico
  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Referrer-Policy: no-referrer
  • a content security policy limited to same-origin assets and API calls

Routes

The native server exposes the browser page, embedded assets, and JSON API from one Axum router.

RouteMethodPurpose
/GETRender the admin page.
/assets/admin.cssGETEmbedded stylesheet.
/assets/admin.jsGETEmbedded JavaScript entrypoint.
/assets/admin_ui.jsGETWASM bindgen JavaScript.
/assets/admin_ui_bg.wasmGETWASM client module.
/favicon.icoGETEmbedded SVG favicon.
/api/sessionGETReturn schema, read-only state, CSRF header name, and public auth summary.
/api/overviewGETReturn job counts, queues, locked workers, and active workers.
/api/jobsGETList jobs.
/api/jobsPOSTAdd a job.
/api/jobs/{id}GETFetch one listed job.
/api/jobs/actionPOSTComplete, fail, run now, or reschedule selected jobs.
/api/jobs/remove-by-keyPOSTRemove a job by key.
/api/maintenancePOSTRun maintenance operations.

Job Visibility

GET /api/jobs accepts these query parameters:

  • state: all, ready, scheduled, locked, or failed
  • identifier: task identifier filter
  • queue: queue name filter
  • search: text search filter
  • limit: result limit, capped by the native route at 500
  • offset: result offset

The response contains ListedJob rows with job metadata such as id, task identifier, queue name, payload, priority, run time, attempts, last error, key, lock owner, revision, flags, and availability.

curl -u "admin:${GRAPHILE_WORKER_ADMIN_PASSWORD}" \
  "http://127.0.0.1:5678/api/jobs?state=failed&limit=50"

Job Mutations

Mutating job routes are disabled when the server runs with --read-only or AdminServerConfig::read_only(true).

Adding a job uses the shared AddJobRequest contract:

{
  "identifier": "send_email",
  "payload": { "user_id": 123 },
  "queue": "mailers",
  "max_attempts": 25,
  "key": "email:123",
  "job_key_mode": "replace",
  "priority": 0,
  "flags": ["transactional"]
}

Supported job key modes are replace, preserve-run-at, and unsafe-dedupe. When job_key_mode is set, key is required.

POST /api/jobs/action accepts an action and job ids:

{
  "action": "reschedule",
  "ids": [42, 43],
  "run_at": "2026-01-01T00:00:00Z",
  "priority": 10,
  "attempts": 0,
  "max_attempts": 25
}

Supported actions are:

  • complete
  • fail
  • run-now
  • reschedule

fail uses the provided reason when present and non-empty. Otherwise it uses the admin UI default reason. reschedule requires at least one of run_at, priority, attempts, or max_attempts.

Jobs can also be removed by key:

{
  "key": "email:123"
}

Maintenance

POST /api/maintenance exposes selected WorkerUtils maintenance operations. It is also disabled in read-only mode.

Supported actions are:

  • migrate
  • cleanup
  • force-unlock
  • sweep-stale-workers

Cleanup can run all cleanup tasks by omitting cleanup_tasks, or a selected subset:

{
  "action": "cleanup",
  "cleanup_tasks": [
    "delete-permanently-failed-jobs",
    "gc-task-identifiers",
    "gc-job-queues"
  ]
}

Force unlock requires at least one worker id:

{
  "action": "force-unlock",
  "worker_ids": ["worker-1"]
}

Stale worker sweeping accepts optional thresholds in seconds and supports dry runs:

{
  "action": "sweep-stale-workers",
  "dry_run": true,
  "sweep_threshold_secs": 300,
  "recovery_delay_secs": 60
}

Assets and Client Behavior

The native crate embeds the admin stylesheet, JavaScript, WASM bindgen output, WASM module, and SVG favicon into the server binary. The browser client uses same-origin requests, sends Accept: application/json, includes credentials, and adds the CSRF header on writes.

For bearer and header auth modes, the browser client sends the token on API requests after it has been entered in the UI. For Basic auth, the browser uses same-origin credentials from the HTTP auth challenge.