---
title: "The Workflows facade — Workflows for Laravel | Roundly"
description: "Tour the Workflows facade — start, emit, for($subject), the definition handle, versions, instances, registries, the graph builder, wake and prune."
url: https://roundly-consulting.com/open-source/docs/workflows-for-laravel/facade
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Workflows for Laravel](https://roundly-consulting.com/open-source/docs/workflows-for-laravel.md)

# The Workflows facade

Everything goes through one facade, RoundlyConsulting\\Workflows\\Facades\\Workflows (global alias Workflows), sugar over the WorkflowsManager container singleton. The manager holds no request state, so it is Octane-safe. Every mutating method has a non-null return type:

```php
use RoundlyConsulting\Workflows\Facades\Workflows;

// Start and emit
Workflows::start('purchase-approval', $request, $team)->input([...])->by($user)->idempotencyKey($key)->dispatch();
Workflows::emit('purchase_request.submitted', $request, ['amount' => '1200'], idempotencyKey: "sub:{$request->id}", user: $user);
Workflows::for($request)->start('purchase-approval')->dispatch();   // the subject owner's definition, else the app-wide one

// Definitions and versions
Workflows::definitions()->create(key: 'purchase-approval', name: 'Purchase approval', subjectType: 'purchase_request', owner: $team);
Workflows::definition('purchase-approval', $team)->edit($graph);   // save the draft — see the definition handle
Workflows::definition('purchase-approval', $team)->publish(by: $admin);
Workflows::versions()->diff($v2, $v3);
Workflows::versions()->export($version);

// Runs
Workflows::instances()->find($ulid);
Workflows::instances()->retry($instance);
Workflows::instances()->cancel($instance, 'withdrawn');

// Registries a builder reads
Workflows::catalogue()->export();               // every step type with its config schema and outcomes
Workflows::subjects()->export('purchase_request');
Workflows::calendars()->names();
Workflows::triggers()->events();
Workflows::schema()->graph();                    // the graph JSON Schema

// The graph builder, wake and prune
Workflows::graph()->trigger('start', Trigger::manual())->end('done')->chain('start', 'done');
Workflows::wake();                               // what workflows:wake runs
Workflows::prune();                              // what workflows:prune runs
```

## Manager methods

| Method | Returns | Does |
| --- | --- | --- |
| `start(string $key, ?Model $subject = null, ?Model $owner = null)` | `PendingStart` | A manual start; ->dispatch() runs StartInstanceAction. |
| `startWith(StartData $data)` | `StartResult` | The same from a DTO. |
| `emit(string $event, ?Model $subject = null, array $payload = [], ?string $idempotencyKey = null, ?Model $user = null, ?Model $owner = null)` | `EmitResult` | Starts every matching active definition and signals waiting steps; a definition that refuses to start is listed in failed. |
| `emitWith(EmitData $data)` | `EmitResult` | The same from a DTO. |
| `for(Model $subject)` | `SubjectHandle` | Start, emit, history, cancel and touch for one subject. |
| `definition(string $key, ?Model $owner = null)` | `DefinitionHandle` | One definition of one owner (null = app-wide). |
| `definitions()` | `DefinitionsAccessor` | create, createWith, find, exists, all, sync, restore. |
| `versions()` | `VersionsAccessor` | validate, diff, overlay, export, import, dryRun. |
| `instances()` | `InstancesAccessor` | find, recent, detail, steps, retry, cancel. |
| `catalogue()` | `CatalogueAccessor` | register, all, find, has, export — the step types. |
| `subjects()` | `SubjectsAccessor` | register, find, export, all — the subject types. |
| `subject(string $key, string $model)` | `SubjectTypeBuilder` | Describe a subject type to register. |
| `calendars()` | `CalendarsAccessor` | register, find, names — business calendars. |
| `triggers()` | `TriggersAccessor` | webhook, map, listen, events. |
| `schema()` | `SchemaAccessor` | graph, template, definitions — the JSON Schemas. |
| `graph()` | `GraphBuilder` | The fluent graph builder; pure, passes through the fake. |
| `wake(?CarbonInterface $now = null)` | `WakeReport` | One wake pass (WakeAction); the whole pass runs at $now. |
| `prune()` | `PruneReport` | Deletes past-retention data (PruneAction). |

signal(), perform() and make() are public on the manager but internal — the bridges’ and the handles’ plumbing. Don’t call them from app code.

## Workflows::definition($key, $owner)

One definition of one owner (null = app-wide); another owner’s key is never found. Every method that needs the row throws UnknownDefinitionException (404) when it is missing:

```php
$handle = Workflows::definition('purchase-approval', $team);

$handle->model();                         // WorkflowDefinition (UnknownDefinitionException if missing)
$handle->draft();                         // the draft version or null
$handle->edit($graph, $expectedChecksum); // save the draft (a graph array or a GraphBuilder; created as a copy of the current version)
$handle->discardDraft();
$handle->validate();                      // ValidationReport of the draft
$handle->publish(by: $admin);             // the draft becomes the current version
$handle->retire();                        // no new starts; running instances finish
$handle->activate();                      // allow starts again
$handle->resolveFlag($node, $path);       // dismiss a template review flag
$handle->rotateWebhook();                 // a new webhook id
$handle->versions();                      // every version, by number
$handle->version(3);
$handle->instances(InstanceStatus::Failed, perPage: 25);   // cursor-paginated history
$handle->start($subject);                 // PendingStart
$handle->dryRun(?int $version);           // PendingDryRun (NoVersionException for a missing version)
$handle->export(?int $version);           // TemplatePayload (NoVersionException for a missing version)
```

| Method | Returns | Does |
| --- | --- | --- |
| `model()` | `WorkflowDefinition` | The row; UnknownDefinitionException (404) when missing. |
| `draft()` | `?WorkflowVersion` | The draft or null. |
| `edit(array\|GraphBuilder $graph, ?string $expectedChecksum = null)` | `WorkflowVersion` | Save the draft; a stale checksum throws DraftConflictException. |
| `discardDraft()` | `WorkflowDefinition` | Drop the draft. |
| `validate()` | `ValidationReport` | Full validation of the draft. |
| `publish(?Model $by = null)` | `WorkflowVersion` | The draft becomes the current version. |
| `retire() / activate()` | `WorkflowDefinition` | Stop or allow new starts; running instances finish. |
| `resolveFlag(string $node, string $path)` | `WorkflowVersion` | Dismiss a template review flag. |
| `rotateWebhook()` | `WorkflowDefinition` | A new webhook id. |
| `versions() / version(int $number)` | `Collection / ?WorkflowVersion` | Every version by number, or one. |
| `instances(?InstanceStatus $status = null, int $perPage = 25)` | `CursorPaginator` | History, newest first. |
| `start(?Model $subject = null)` | `PendingStart` | A manual start of this definition. |
| `dryRun(?int $version = null)` | `PendingDryRun` | The current version, else the draft; NoVersionException for none or a missing number. |
| `export(?int $version = null)` | `TemplatePayload` | A portable template; NoVersionException likewise. |

## Workflows::definitions()

| Method | Returns | Does |
| --- | --- | --- |
| `create(string $key, string $name, ?string $subjectType = null, ?Model $owner = null, ?string $description = null)` | `WorkflowDefinition` | A draft definition with version 1 holding the minimal graph (manual trigger → end). |
| `createWith(CreateDefinitionData $data)` | `WorkflowDefinition` | The same from a DTO. |
| `find(string $key, ?Model $owner = null)` | `?WorkflowDefinition` | Null when missing. |
| `exists(string $key, ?Model $owner = null)` | `bool` | Whether the key exists in that scope. |
| `all(?Model $owner = null)` | `Collection` | Every definition of the owner. |
| `sync(?string $path = null, ?Model $owner = null, bool $publish = false, bool $dryRun = false)` | `SyncReport` | Create, draft and publish from JSON files ($path null = workflows.sync.path). |
| `restore(string $key, ?Model $owner = null)` | `WorkflowDefinition` | Un-trash a soft-deleted definition. |

## Workflows::versions()

| Method | Returns | Does |
| --- | --- | --- |
| `validate(array\|GraphBuilder $graph, ?string $subjectType = null, ?Model $owner = null)` | `ValidationReport` | Validate any graph. |
| `diff(WorkflowVersion $from, WorkflowVersion $to)` | `VersionDiff` | Node and edge changes between two versions of one definition. |
| `overlay(WorkflowVersion $version)` | `OverlayReport` | Running, waiting and failed instances per current node. |
| `export(WorkflowVersion $version)` | `TemplatePayload` | A roundly.workflows.template/v1 payload. |
| `import(TemplatePayload\|array $payload, ?Model $owner = null, ?string $key = null)` | `ImportResult` | A new draft definition with remapped ids and review flags. |
| `dryRun(WorkflowVersion\|array\|GraphBuilder $graph, ?WorkflowDefinition $definition = null)` | `PendingDryRun` | A dry run of an unsaved graph or any version. |

## Workflows::instances()

| Method | Returns | Does |
| --- | --- | --- |
| `find(int\|string $id)` | `?WorkflowInstance` | By ULID (any case) or primary key; a value the key column can’t hold is a miss. |
| `recent(?InstanceStatus $status = null, ?string $definition = null, ?Model $owner = null, int $limit = 25)` | `Collection` | Newest first. |
| `detail(WorkflowInstance $instance)` | `WorkflowInstance` | Loads the definition, version, step log, pending waits and pending escalations. |
| `steps(WorkflowInstance $instance)` | `Collection` | Every attempt, in order. |
| `retry(WorkflowInstance $instance)` | `WorkflowInstance` | Retry a failed instance from its failed step. |
| `cancel(WorkflowInstance $instance, ?string $reason = null)` | `WorkflowInstance` | Cancel a running or waiting instance. |

## Workflows::for($subject) and $subject->workflows()

| Method | Returns | Does |
| --- | --- | --- |
| `start(string $key, ?Model $owner = null)` | `PendingStart` | The subject owner’s definition, else the app-wide one; an explicit owner searches only that scope. |
| `emit(string $event, array $payload = [], ?string $idempotencyKey = null, ?Model $user = null)` | `EmitResult` | Emit about this subject. |
| `instances(int $perPage = 25)` | `CursorPaginator` | Newest first, owner and starter eager-loaded. |
| `running()` | `Collection` | Running or waiting instances. |
| `cancelAll(?string $reason = null)` | `int` | Instances cancelled — children a cancelled parent took with it count; one that ended meanwhile is skipped. |
| `touch()` | `int` | Condition waits re-evaluated. |

## Other accessors

| Accessor | Method | Returns |
| --- | --- | --- |
| `catalogue()` | `register(string\|StepType $type)` | `StepType` |
| `catalogue()` | `all() / find($key) / has($key)` | `array / ?StepType / bool` |
| `catalogue()` | `export()` | `CatalogueExport` |
| `subjects()` | `register(SubjectTypeBuilder\|SubjectTypeDefinition $type)` | `SubjectTypeDefinition` |
| `subjects()` | `find($key) / export($key) / all()` | `?SubjectTypeDefinition / SubjectTypeDefinition / array` |
| `calendars()` | `register(string $name, Closure $resolver)` | `self` |
| `calendars()` | `find(string $name, ?Model $owner = null, ?string $timezone = null)` | `BusinessCalendar` |
| `calendars()` | `names()` | `list<string>` |
| `triggers()` | `webhook(string $hookId, array $payload, array $headers = [])` | `StartResult` |
| `triggers()` | `map(string $event, string $key, ?string $subject, ?string $user, list\|string\|null $payload, ?string $idempotencyKey)` | `void` |
| `triggers()` | `listen(string $event) / events()` | `void / array<string, string>` |
| `schema()` | `graph() / template() / definitions()` | `array` |

## Builders

| Builder | Methods |
| --- | --- |
| `PendingStart` | input(array $input), by(?Model $user), idempotencyKey(?string $key), withForm(Request $request, bool $submit = false) → self; dispatch(): StartResult |
| `PendingDryRun` | subject(Model), sample(array), input(array), event(string $key, array $payload = \[\]), assume(array), at(CarbonInterface), log(bool = true), by(?Model) → self; run(): DryRunResult, explore(): DryRunTree |
| `SubjectTypeBuilder` | label(string), ownedBy(string $relation), ownerType(bool = true), ability(string), field(string $name, FieldType\|string $type, ?string $label = null, string\|array\|null $options = null, ?string $path = null, bool $writable = false) → self; key(), build() |

## Value objects, DTOs, models and enums

- DTOs (all final readonly, public promoted properties): StartData, StartResult (wasSkipped(), wasRefused()), EmitData, EmitResult (started, resumed, failed; skipped()), EmitFailure (definition, exception; code()), CreateDefinitionData, DryRunData, DryRunResult, DryRunStep, DryRunTree, WouldBeAction, SideEffect, SimulatedStep, ValidationReport, ValidationError, EdgeReference, VersionDiff, NodeChange, EdgeChange, OverlayReport, NodeOverlay, TemplatePayload, ImportResult, ReviewFlag, SyncReport, SyncedDefinition, WakeReport, PruneReport, CatalogueExport, CatalogueEntry, SubjectTypeDefinition, FieldDefinition, EventMapping, PartyReference, Duration, RetryPolicy, Actor, Signal, StepAuthorization, TransitionOutcome, DoctorFinding.
- Graph\\Graph, Graph\\Node and Graph\\Edge are the parsed, read-only graph (StepContext::graph(), node(), WorkflowVersion::parsedGraph()): node($id), nodesOfType($type), trigger(), outgoing($id), incoming($id), edgeFor($from, $outcome), setting($key, $default), toArray().
- Models (swappable via workflows.models.\*) have $guarded = \[\], a factory, a configurable key and UTC timestamps. All soft-delete except WorkflowIdempotencyKey; workflows:prune force-deletes.
- Enums are string-backed with translated labels through enums-for-laravel’s helpers.

| Model | Relations | Methods |
| --- | --- | --- |
| `WorkflowDefinition` | owner() MorphTo, versions() HasMany, currentVersion() BelongsTo, instances() HasMany | isActive() |
| `WorkflowVersion` | definition() BelongsTo, publisher() MorphTo | reviewFlags(), setReviewFlags(array), isPublished(), parsedGraph(): Graph |
| `WorkflowInstance` | definition(), version(), parent() BelongsTo; owner(), subject(), starter() MorphTo; children(), stepRuns(), waits(), escalations() HasMany | failure(): FailureCode\|string\|null, path(): list<string> |
| `StepRun` | instance() BelongsTo, waits(), escalations() HasMany | error(): FailureCode\|string\|null |
| `WorkflowWait` | instance(), stepRun() BelongsTo | — |
| `WorkflowEscalation` | instance(), stepRun() BelongsTo | — |
| `WorkflowIdempotencyKey` | instance() BelongsTo | — |
| `WorkflowDryRun` | definition() BelongsTo, requester() MorphTo | — |

| Enum | Cases |
| --- | --- |
| `ActorMode` | system, on\_behalf\_of |
| `ConditionOperator` | equals, in, gt, lt, between, empty, contains, before, after |
| `DefinitionStatus` | draft, active, retired |
| `DoctorLevel` | ok, warn, fail |
| `DurationUnit` | minutes, hours, days, weeks, months, business\_minutes, business\_hours, business\_days; isBusiness() |
| `EscalationStatus` | pending, claimed, fired, failed, cancelled |
| `FieldType` | string, text, integer, decimal, boolean, date, datetime, enum, party, list |
| `InstanceStatus` | running, waiting, completed, failed, cancelled; isTerminal(), isActive() |
| `Severity` | error, warning |
| `SignalKind` | timer, event, condition, probe, retry, timeout |
| `StepRunStatus` | queued, running, waiting, completed, failed, cancelled |
| `SyncStatus` | created, drafted, published, unchanged, invalid, failed |
| `TriggerType` | manual, event, schedule, webhook |
| `VersionStatus` | draft, published, superseded |
| `WaitKind` | timer, event, condition, retry, timeout |
| `WaitStatus` | pending, claimed, fired, cancelled |
| `ValidationCode` | Every publish code — see Publish validation. |
| `FailureCode` | Every engine, built-in and bridge failure code; fromStored(), detail(), retryable(), message() — see Troubleshooting. |
| `WorkflowOperation` | What the fake records — see Testing. |

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/workflows-for-laravel.md)
