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:
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:
$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. |