Pixel-perfect, dark-first admin panel for padosoft/laravel-flow β runs, approvals, outbox & definitions in one Blade + Alpine cockpit.
π Quick Start Β· πΈ Screenshots Β· βοΈ Configuration Β· π Authorization Β· π€ Contributing
- β¨ Why this package
- π― Features
- πΈ Screenshots
- π¦ Requirements
- π Quick Start (5 minutes)
- π Step-by-Step Setup
- βοΈ Configuration
- π Authorization (mutations)
- π¨ Customization
- π§ͺ Demo Mode (no DB needed)
- πΊοΈ Routes
- ποΈ Architecture
- π€ AI Vibe Coding Pack
- βοΈ Comparison
- π£οΈ Roadmap
- β Quality Gates
- π€ Contributing
- π Security
- π License
- π Credits
padosoft/laravel-flow is intentionally headless β a deterministic, queue-driven workflow engine you can drop into any Laravel app.
laravel-flow-admin is the operator console for it. A production-style control plane for runs, approvals, outbox webhooks and configuration β without leaking the engine's internal namespaces into your app.
Think Horizon for queues, Pulse for metrics β and Flow Admin for the lifecycle of long-running, multi-step business workflows.
- π Overview dashboard β KPI tiles, sparklines, recent runs, queue health, error rate.
- π Runs index & detail β filterable list, full timeline (timeline / Gantt / DAG), payload diff, retry/cancel actions.
- β Approvals inbox β pending decisions with one-click approve / reject through your own authorizer.
- π€ Webhook outbox β delivery state, replay failed jobs, inspect headers/payloads.
- π Flow definitions β registered workflows, version, last activity at a glance.
- β‘ βK command palette β jump anywhere in two keystrokes.
- π¨ Pixel-perfect dark + light themes β persisted in cookie, switchable per user.
- π‘οΈ Deny-by-default authorizer β every mutation goes through your
ActionAuthorizer. No accidents. - π Auto-refreshing pages β configurable polling (
/flow/api/live). - π§± Adapter pattern β
eloquentfor prod,arrayfor demos / E2E (deterministic seed-42 fixtures). - π§ͺ Battle-tested β 101 PHPUnit tests, 18 Playwright scenarios across Chromium / Firefox / WebKit.
- π¦ Zero-coupling β built on a public
Contracts\*surface; engine internals stay@internal.
| Requirement | Version |
|---|---|
| PHP | ^8.3 (also tested on 8.4) |
| Laravel | ^13.0 |
padosoft/laravel-flow |
^1.0 |
| Node.js (only if you want to rebuild assets) | >=20 |
| Database | any Laravel-supported driver (or array adapter for demos) |
π‘ You do not need Node.js to use this package. Pre-built assets ship inside the package and are publishable via
vendor:publish.
# 1. Install both packages
composer require padosoft/laravel-flow-admin
# 2. Publish config + assets
php artisan vendor:publish --tag=flow-admin-config
php artisan vendor:publish --tag=flow-admin-assets
# 3. Run flow-engine migrations (from padosoft/laravel-flow)
php artisan migrate
# 4. Visit the admin panel
php artisan serve
# π http://localhost:8000/flowThat's it. The panel is read-only by default (deny-all authorizer) so you can safely browse production data on day 1, then opt-in to mutations when you've wired your permission rules.
laravel-flow-admin is a UI on top of padosoft/laravel-flow. If you don't already have it, install it first:
composer require padosoft/laravel-flow
php artisan vendor:publish --tag=flow-config
php artisan vendor:publish --tag=flow-migrations
php artisan migrateThis creates the flow_runs, flow_steps, flow_approvals, flow_webhook_outbox and related tables that this admin panel reads from.
π Full engine docs: github.com/padosoft/laravel-flow
composer require padosoft/laravel-flow-adminThe service provider is auto-discovered. Routes, views, config and migrations are loaded out-of-the-box from the package.
# Publish the config (config/flow-admin.php) β recommended
php artisan vendor:publish --tag=flow-admin-config
# Publish compiled CSS/JS to public/vendor/flow-admin (required for styling)
php artisan vendor:publish --tag=flow-admin-assets
# Optional: publish Blade views to resources/views/vendor/flow-admin (for customization)
php artisan vendor:publish --tag=flow-admin-views
β οΈ Don't skipflow-admin-assetsin production. The panel relies on the published CSS/JS bundle.
By default the panel mounts at /flow and runs through web,auth middleware. Override with environment variables:
FLOW_ADMIN_PREFIX=ops/flow
FLOW_ADMIN_MIDDLEWARE="web,auth,verified,can:access-flow-admin"
FLOW_ADMIN_THEME=dark
FLOW_ADMIN_STEP_VIZ=timeline
FLOW_ADMIN_POLLING_MS=4000π‘οΈ If you set
FLOW_ADMIN_MIDDLEWARE=""we fall back to['web']instead of leaving the panel unauthenticated. Setting it empty was a known footgun, so we close it explicitly.
The panel ships with DenyAllAuthorizer so every mutation (resume, reject, replay, cancel, retry-webhook) is blocked by default. To enable mutations, implement your own:
// app/Flow/AdminAuthorizer.php
namespace App\Flow;
use Padosoft\LaravelFlowAdmin\Contracts\ActionAuthorizer;
final class AdminAuthorizer implements ActionAuthorizer
{
public function canResume(string $runId): bool
{
return auth()->user()?->can('flow.runs.resume') ?? false;
}
public function canCancel(string $runId): bool
{
return auth()->user()?->can('flow.runs.cancel') ?? false;
}
public function canApprove(string $approvalId): bool
{
return auth()->user()?->can('flow.approvals.act') ?? false;
}
public function canReject(string $approvalId): bool
{
return auth()->user()?->can('flow.approvals.act') ?? false;
}
public function canReplayWebhook(string $outboxId): bool
{
return auth()->user()?->can('flow.outbox.replay') ?? false;
}
}Bind it in AppServiceProvider::register():
use Padosoft\LaravelFlowAdmin\Contracts\ActionAuthorizer;
use App\Flow\AdminAuthorizer;
$this->app->bind(ActionAuthorizer::class, AdminAuthorizer::class);β¦or set 'authorizer' => App\Flow\AdminAuthorizer::class in config/flow-admin.php.
php artisan serveOpen http://localhost:8000/flow and you should see the dashboard. Press βK (or Ctrl+K) anywhere to open the command palette.
All keys live in config/flow-admin.php. They are also overridable via environment variables.
| Key | Env | Default | Description |
|---|---|---|---|
prefix |
FLOW_ADMIN_PREFIX |
flow |
URI prefix for all routes (/flow, /flow/runs, β¦). |
middleware |
FLOW_ADMIN_MIDDLEWARE |
web,auth |
Comma-separated middleware stack. Empty/whitespace falls back to ['web']. |
adapter |
FLOW_ADMIN_ADAPTER |
eloquent |
eloquent (prod) or array (deterministic demo fixtures). |
authorizer |
β | DenyAllAuthorizer |
FQCN of your ActionAuthorizer implementation. |
polling_interval_ms |
FLOW_ADMIN_POLLING_MS |
4000 |
Auto-refresh interval for live pages. |
theme_default |
FLOW_ADMIN_THEME |
dark |
dark or light. Per-user override stored in flow_admin_theme cookie. |
step_viz_default |
FLOW_ADMIN_STEP_VIZ |
timeline |
Default visualization on run detail: timeline, gantt, or dag. |
Every mutation route (resume, reject, replay, cancel, retry-webhook) consults your ActionAuthorizer before the controller runs. This is non-negotiable: there is no "global admin" bypass and no way to short-circuit the gate from a Blade view.
Public extension surface (semver-stable from v0.1.0 β):
Padosoft\LaravelFlowAdmin\Contracts\ActionAuthorizerPadosoft\LaravelFlowAdmin\Contracts\ReadModelAdapterPadosoft\LaravelFlowAdmin\Contracts\ViewModelFactory(and family)config/flow-admin.phpkeys- Publish tags:
flow-admin-config,flow-admin-views,flow-admin-assets - Route names:
flow-admin.*
Everything under Adapters\, Http\Controllers\, Support\, ViewModels\ is internal and may change between minor versions until v1.0.
php artisan vendor:publish --tag=flow-admin-viewsEdit anything under resources/views/vendor/flow-admin/. Component slots and named layout sections are preserved across upgrades.
The published bundle exposes CSS custom properties for colors, radii, spacing and font sizing. Wrap the panel in a custom theme by overriding tokens:
:root[data-flow-admin-theme="dark"] {
--flow-admin-color-accent: #6366f1;
--flow-admin-color-bg: #0b0d12;
}POST /flow/theme
X-CSRF-TOKEN: β¦
theme=dark|lightFor showcases, screenshots, or end-to-end tests you can bypass the database entirely:
FLOW_ADMIN_ADAPTER=arrayThe ArrayReadModelAdapter produces deterministic fixtures (seed=42) so KPI numbers, run IDs and timelines are reproducible across screenshots and Playwright runs.
All routes live under the configured prefix (default /flow) and the flow-admin.* route-name namespace.
| Method | URI | Name | Purpose |
|---|---|---|---|
GET |
/ |
flow-admin.overview |
Dashboard |
GET |
/runs |
flow-admin.runs.index |
Runs list |
GET |
/runs/{id} |
flow-admin.runs.show |
Run detail + timeline |
GET |
/approvals |
flow-admin.approvals.index |
Approvals inbox |
GET |
/outbox |
flow-admin.outbox.index |
Webhook outbox |
GET |
/definitions |
flow-admin.definitions.index |
Registered flows |
GET |
/settings |
flow-admin.settings.index |
Effective configuration |
GET |
/api/search |
flow-admin.api.search |
βK palette backend |
GET |
/api/live |
flow-admin.api.live |
Live polling JSON |
POST |
/theme |
flow-admin.theme.toggle |
Persist theme cookie |
HTTP request
β
βββΊ routes/flow-admin.php (prefix + middleware + name)
β
βββΊ Http/Controllers/*Controller βββ thin: request β DTO β factory β view
β β
β βββΊ Http/Requests/*Request (validation, sorting, filtering DTOs)
β βββΊ ViewModels/*Factory (read-side view assembly)
β βββΊ Contracts/ActionAuthorizer (gate for any mutation)
β
βββΊ Adapters/Eloquent | Array (ReadModelAdapter implementations)
β βββΊ reads flow_* tables OR seed-42 fixtures
β
βββΊ resources/views/* + Alpine stores + Vite bundle
Design source-of-truth lives under .design-source/project/ (pixel reference) and is enforced through Playwright visual regression on chromium / firefox / webkit.
This repository ships a contributor pack under .claude/:
- Rules β Laravel 13 defaults, admin-panel UX, query optimization, naming conventions, exception handling, logging security.
- Skills β orchestrators for
create-admin-interface,playwright-enterprise-tester,copilot-pr-review-loop,pre-push-self-review,test-count-readme-sync. - Runbooks β macro/subtask branch workflow used during initial implementation.
If you build with Claude Code or another agent, copy .claude/ into your downstream project for a head start.
| Tool | Workflow runs lifecycle | Approvals UI | Webhook outbox | Drop-in for Laravel Flow |
|---|---|---|---|---|
| Laravel Flow Admin | β | β | β | β |
| Laravel Horizon | β | β | β | |
| Laravel Pulse | β | β | β | |
| Custom dashboard | depends | depends | depends | β³ slow to bootstrap |
| Temporal UI | β (for Temporal) | β | β |
- v0.1 β core pages, eloquent + array adapters, theme cookie, βK palette, Playwright matrix.
- v0.1.1 β public release hardening, README polish, GitHub release artifacts.
- v0.2 β bulk actions on runs, saved filter presets, CSV/JSON export.
- v0.3 β Pulse-style sparkline cards, alerting hooks.
- v1.0 β frozen public surface, SemVer guarantees, downstream-stable Adapters.
Every push runs through this gate (matrix php: 8.3, 8.4 Γ laravel: 13):
composer validate --strict --no-check-publish
composer format:test # Laravel Pint
composer analyse # PHPStan / Larastan level 8
composer test # PHPUnit β 101 tests, 584 assertions
npm run lint # ESLint flat config
npm run build # Vite build verification
npm run test:e2e # Playwright on chromium + firefox + webkitLatest local run: 101 tests / 584 assertions / 18 E2E scenarios passed.
PRs welcome! Please:
- Open an issue first for non-trivial changes.
- Branch from
mainastask/<short-name>orsubtask/<short-name>. - Run the full local gate (above) before pushing.
- Add tests β unit, feature, or Playwright depending on the change.
- Update
docs/PROGRESS.mdif your PR closes a roadmap item, anddocs/LESSON.mdif you discover a reusable insight.
See CONTRIBUTING.md for the full workflow and CODE_OF_CONDUCT.md.
If you discover a security vulnerability, please do not open a public issue. Email hello@padosoft.com directly. See SECURITY.md for our disclosure policy.
Apache-2.0 Β© Padosoft. See LICENSE for the full text.
- Padosoft β package author and maintainer.
padosoft/laravel-flowβ the headless workflow engine this panel operates.- Laravel, Alpine.js, Vite, Playwright β the giants whose shoulders this is built on.
Made with β€οΈ by Padosoft β workflows are hard, the UI shouldn't be.






