Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Workflow Laravel Package

durable-workflow/workflow

Durable Workflow for Laravel lets you define workflows and activities as simple PHP classes, with queuing, parallel execution, retries, and error handling. Ideal for sagas, pipelines, AI agent flows, and complex business processes.

View on GitHub
Deep Wiki
Context7

Getting Started

Install. The README publishes a one-line installer (curl -fsSL https://durable-workflow.com/install-sdk.sh | sh -s -- workflow), which is a wrapper around adding durable-workflow/workflow plus the package's service provider wiring. If you prefer to control the version explicitly, pin it in composer.json yourself — see the Gotchas section, because this package ships a lot of alpha/RC tags and you do not want an unpinned ^2.0 resolution surprise in production.

Migrations and config. The package ships its own migrations and Eloquent models for workflow/run/replay persistence. Run php artisan migrate and you get the workflow tables. All of it lands on your default database connection unless you set storage.connection in config/workflows.php (env: DW_STORAGE_CONNECTION, fallback WORKFLOW_STORAGE_CONNECTION). For anything beyond a prototype, set that connection up front — retrofitting workflow state out of your primary DB later is a migration project, not a config change.

Where to look first, in this order:

  1. The docs at durable-workflow.com/docs/installation — the README is intentionally thin and the real API surface lives there.
  2. The sample app, which runs in the browser. It is the fastest way to see a complete, working wiring of controller → workflow → activity → queue worker.
  3. The Workflow\V2\* namespace in your vendor/ directory. The README uses Workflow\V2\Workflow, Workflow\V2\Activity, Workflow\V2\WorkflowStub, and the helper function Workflow\V2\activity(). Confirm which major you actually installed — v1 and v2 do not share a namespace, and much of the older Laravel Workflow material on the web is v1.

First use case — do it end to end before designing anything. Copy the README pair verbatim, then trigger it from php artisan tinker or a one-off command:

use Workflow\V2\WorkflowStub;

$workflow = WorkflowStub::make(MyWorkflow::class);
$workflow->start('world');
$workflow->output(); // 'Hello, world!'

Then confirm the workflow actually executed on a queue worker (php artisan queue:work) and that the Waterline UI shows the run. If output() never resolves, it is almost always "no worker was running" — that is the single most common first-day failure with this package.

A good second use case is a user signup flow: create-account activity → send-welcome-email activity → provision-defaults activity. It has real side effects, one obviously retryable step (email), and one obviously non-retryable step (account creation), which forces you to think about the design correctly on day two.

Implementation Patterns

The mental model that matters: a workflow is a replayable script, not a job. Durable execution works by persisting the history of activity results and re-running your handle() method from the top against that history whenever it wakes up. So the workflow class must be a pure orchestrator: it decides what to do next, it does not do things. Every side effect — HTTP call, DB write, mail, LLM invocation, file upload — belongs in an Activity. Once you internalize "this method will execute many times, and only the activity results are stable," most of the package's sharp edges disappear.

Practical patterns that cover most day-to-day work:

  • Sequential pipeline — the README example: activities chained, each result passed to the next. Use for data pipelines, order processing, document ingestion.
  • Fan-out — dispatch N activities and collect results. Use for anything batch-shaped (re-indexing, bulk notifications, multi-source enrichment). Keep N bounded; 10,000 parallel activities is a queue-capacity decision, not a workflow decision.
  • Saga / compensation — each step has an inverse; on failure you walk backwards through the completed steps. This is where the durability guarantee earns its keep, because the compensating activity itself can fail and be retried without losing track of where you are.
  • Human-in-the-loop / long waits — workflow parks (waiting for an approval, an external callback, a scheduled date), app resumes it. This is the pattern that justifies the package over plain queued jobs: your process may sleep for days and the state survives deploys and restarts.
  • Agentic loops — LLM call as an activity, decision in the workflow, loop until a termination condition. Because the model call is recorded, replay does not re-bill you for tokens. That property alone is a strong argument for using this package for AI workflows.
  • Child workflows — compose a big flow from smaller named workflows rather than one 400-line handle(). Better history readability in Waterline and easier to test individually.

Integration tips.

  • Treat WorkflowStub as the public handle to a run. Start workflows from controllers, commands, event listeners, or jobs; do not call a workflow's handle() directly.
  • Persist workflow_id and run_id on your own domain models (e.g. orders.workflow_id) at start time. This is your join key for support tickets, debugging, and dashboards. The README is explicit that business dashboards should read app-owned milestone projections keyed by workflow_id and run_id, not workflow runtime tables — write a order_fulfilment_state row from an activity at each meaningful milestone and let BI read that.
  • Make every activity idempotent. Activities can be retried; assume at-least-once. Use a natural key, an upsert, or an idempotency token passed from the workflow as an argument.
  • Pass only serializable data across activity boundaries — scalars, arrays, simple DTOs. Eloquent models with loaded relations are a trap.
  • Use Waterline (a separate install) as the operational UI, the way you would use Horizon for queues. It is a runtime diagnostics tool — fleet health, waits, retries, failures, repair — not a product surface.
  • For tests, lean on the sample app's structure and the shipped php artisan workflow:v2:replay-conformance command, which validates replay behaviour for the embedded runtime.

Gotchas and Tips

Determinism violations are the #1 bug class. No now(), Str::uuid(), rand(), config() reads that change over time, or DB queries inside handle(). Compute them in an activity and pass the result in, or derive them once and store them as workflow input. Symptom: a workflow that "sometimes" takes a different branch on resume, or replays into a different code path after a restart.

Deploying while workflows are in flight is the #2 bug class. Replay re-executes your current handle() against old recorded history. Changing branch order, renaming activities, or changing a handle() signature can corrupt or strand live runs. Practical rules: append new steps at the end, never reorder; version your workflow classes when a breaking change is unavoidable; drain or accept the risk consciously. This is the thing to raise in code review every single time someone touches a workflow class.

Storage connection details. storage.connection => null means "default connection", which is exactly unchanged behaviour. When you set it, the value must match a key under config('database.connections') — not a DSN. Both models and migrations route there, so php artisan migrate creates the workflow tables in that database. There is deliberately no separate schema option: use the connection's own search_path (PostgreSQL) or database (MySQL) for schema/database targeting. Get this right before your first production deploy; changing it later means moving tables and losing history continuity.

Upgrade hazards are real and documented. The 2.5.12 release note is worth reading as a template for how this package upgrades: it adds a migration that derives existing deadlines from original acceptance data, it requires you to back up the database and replace old application and worker processes, and it halts migration with the affected call ID when it encounters an unsupported stored policy. That last behaviour is good design but means you must rehearse the migration on a restored production copy, not just run migrate and hope. Also note the alpha/RC churn (2.0.0-alpha.179, rc.11 through rc.48, then a 2.5.x line) — pin exact versions, commit the lockfile, and read the release note before bumping.

Queue worker configuration matters more than usual. Workflows execute through Laravel's queue system. Set --timeout and --tries deliberately: too-low --timeout kills long steps mid-flight (which the runtime can usually recover from, but it makes history noisy), and --tries on the workflow job is not the same as retry policy on individual activities. Tune activity retries where they belong — per activity — not by cranking worker tries.

output() is not a synchronous return. It is a read of the persisted outcome. If nothing has completed, you are either blocking/polling or getting a null depending on version and configuration. Do not call output() in a hot request path expecting the workflow to have finished; design the UX around "started, here is the run reference" rather than "returned the result inline."

Scope check — embedded only. This package is the application-embedded runtime: your Laravel app owns durable state and queue execution. It does not ship a client or remote-worker runtime for the standalone server. If you later need Python or Rust workers in the same system, that is the standalone server plus durable-workflow/sdk, and it is a different deployment topology. Decide which mode you are in before you build out the operational story.

Debugging routine. When a workflow looks stuck: (1) is a worker running? (2) check Waterline for the run's history — the last completed activity tells you where it is parked; (3) check whether the workflow is waiting on a long timer or an external resume rather than failing; (4) reproduce with workflow:v2:replay-conformance or a local replay of the history. Log from activities, not from the workflow class — workflow-class log lines will print once per replay and badly mislead you.

Extension points worth knowing about. Activity middleware (for cross-cutting concerns like logging, metrics, or auth context) and the storage connection abstraction are the two hooks you are most likely to reach for. Before writing a bespoke retry/polling layer, check the docs — much of what you would hand-roll is already a first-class concern here, and re-implementing it is the fastest way to break replay.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names