Dashboard
Monitor, debug, and inspect your Ablauf workflows with the built-in dashboard and observability tools.
Dashboard
Ablauf includes built-in observability and a React dashboard for monitoring your workflows. Whether you're debugging in development or monitoring production, you have full visibility into workflow state, step execution, and errors.
What You Get
- Workflow Table — Full-width data table with status indicators, type badges, and relative timestamps. Filter by status and type, or search by workflow ID with the
/keyboard shortcut - Detail Panel — Slide-over sheet (65% viewport) for inspecting payload, result, errors, and step execution for any workflow
- Gantt Timeline — Visualize step execution with a Gantt chart showing retry history bars
- Step List — Collapsible step rows with duration, attempt counts, error details, and stack traces
- Error Panel — Aggregated error view with per-step error messages, retry history, and expandable stack traces
- Live Updates — Real-time WebSocket updates as workflows execute, with polling fallback
Setup
Install
npm install @der-ablauf/dashboardAdd the API Handlers
The dashboard needs an RPC endpoint to communicate with your worker. Add this to your worker:
import { Ablauf } from '@der-ablauf/workflows';
import { Hono } from 'hono';
import { env } from 'cloudflare:workers';
const ablauf = new Ablauf(env.WORKFLOW_RUNNER, {
workflows: [OrderWorkflow, PaymentWorkflow],
});
const { rpcHandler, openApiHandler } = ablauf.createHandlers();
app.all('/__ablauf/*', async (c) => {
const { matched, response } = await openApiHandler.handle(c.req.raw, {
prefix: '/__ablauf',
context: ablauf.getDashboardContext(),
});
if (matched) return c.newResponse(response.body, response);
const { matched: matchedRpc, response: rpcResponse } = await rpcHandler.handle(c.req.raw, {
prefix: '/__ablauf',
context: ablauf.getDashboardContext(),
});
if (matchedRpc) return c.newResponse(rpcResponse.body, rpcResponse);
return new Response('Not Found', { status: 404 });
});The /__ablauf prefix can be anything you want, but make sure it matches when you run the dashboard CLI. Also, don't expose this in
production without adding authentication — it provides full read access to all workflow data.
Run the Dashboard
Once your worker is running, start the dashboard:
bunx @der-ablauf/dashboard --url https://my-worker.example.com --port 3000The dashboard opens in your browser and connects to your worker's RPC endpoint automatically.
Listing Workflows
Ablauf tracks all workflow instances by type, allowing you to query and filter them programmatically:
// List all instances of a workflow type
const orders = await ablauf.list('process-order');
// Filter by status
const running = await ablauf.list('process-order', { status: 'running' });
// Limit results (sorted by updatedAt, newest first)
const recent = await ablauf.list('process-order', { limit: 10 });Each entry in the list is a WorkflowIndexEntry:
{
id: string;
status: string;
createdAt: number;
updatedAt: number;
}For full details about a specific workflow, use ablauf.get(OrderWorkflow, { id }).getStatus().
Shard Configuration
The built-in ShardObservabilityProvider uses shard-based indexing with dedicated Durable Objects for index storage. When observability is enabled, every workflow state change is recorded in one or more index shards.
By default, Ablauf uses a single shard per workflow type. For high-volume workflows (thousands of instances), you can increase the shard count to distribute load:
const ablauf = new Ablauf(env.WORKFLOW_RUNNER, {
workflows: [
[HighVolumeWorkflow, { shards: 8 }],
[LowVolumeWorkflow, { shards: 1 }], // default
],
});You can also configure shards directly via ShardObservabilityProvider:
import { Ablauf, ShardObservabilityProvider } from '@der-ablauf/workflows';
const ablauf = new Ablauf(env.WORKFLOW_RUNNER, {
workflows: [HighVolumeWorkflow],
observability: new ShardObservabilityProvider(env.WORKFLOW_RUNNER, {
shards: { 'high-volume': { shards: 8 } },
workflowTypes: ['high-volume'],
}),
});More shards = better write throughput for high-volume workflow types, but queries need to fan out to all shards. Start with the default (1) and scale up as needed.
Disabling Observability
If you don't need the list/dashboard features, you can disable observability to save on Durable Object operations:
const ablauf = new Ablauf(env.WORKFLOW_RUNNER, {
workflows: [OrderWorkflow],
observability: false,
});
// ablauf.list() will throw ObservabilityReadNotConfiguredErrorThis skips all index updates, reducing the number of Durable Object writes. You can still use ablauf.get(Workflow, { id }).getStatus() to check individual workflows if you know their ID.
Custom Observability Provider
You can replace the built-in shard-based indexing entirely with your own storage backend by providing a custom ObservabilityProvider. This is useful if you want to send workflow events to an external database, logging service, or tracing system.
See the Server docs for the full provider interface and examples.
Production Considerations
In production, you'll likely want:
- Authentication on the
/__ablaufendpoint to prevent unauthorized access - Rate limiting to protect against abuse
- CORS headers if your dashboard is hosted on a different domain
- Reasonable shard counts for high-volume workflow types (typically 2-8 shards)
Observability adds minimal overhead (one additional Durable Object write per status change), but for extremely high-volume workflows, disabling it and using custom metrics/logging may be more cost-effective.