Skip to content

Getting started

A workflow is a class that defines states and transitions. It is attached to a resource.

namespace App\Workflows;
use Anvil\Schema\Fields\TextInput;
use Anvil\Workflow\Actor;
use Anvil\Workflow\Escalation;
use Anvil\Workflow\Definition;
use Anvil\Workflow\State;
use Anvil\Workflow\Transition;
use Anvil\Workflow\Workflow;
final class OrderWorkflow extends Workflow
{
public function key(): string
{
return 'order-approval';
}
public function version(): int
{
return 1;
}
public function define(Definition $workflow): Definition
{
return $workflow
->column('approval_state')
->states([
State::make('draft')->label('Draft')->initial(),
State::make('submitted')->label('Submitted')->duration(5)
->whenOverdue(Escalation::make()->remind()->run('reject', 'Not reviewed in time')),
State::make('approved')->label('Approved')->locked(),
State::make('rejected')->label('Rejected'),
State::make('ordered')->label('Ordered')->locked()->final()->goal(),
State::make('cancelled')->label('Cancelled')->final(),
])
->transitions([
Transition::make('submit')->label('Submit')->from('draft', 'rejected')->to('submitted')
->by(Actor::creator())
->guard('order.has-items')
->confirm('Submit the order for approval?'),
Transition::make('approve')->label('Approve')->from('submitted')->to('approved')
->by(Actor::slot('approver', default: Actor::resolver('line-manager')))
->fourEyes('submit'),
Transition::make('reject')->label('Reject')->from('submitted')->to('rejected')
->by(Actor::sameAs('approve'))
->fourEyes('submit')
->reason(required: true),
Transition::make('order')->label('Order')->from('approved')->to('ordered')
->by(Actor::role('Purchasing'))
->form([TextInput::make('order_number')->required()->maxLength(30)])
->effect('order.send-to-supplier'),
Transition::make('cancel')->label('Cancel')->from('draft', 'submitted', 'rejected')->to('cancelled')
->by(Actor::creator())
->reason()
->optional()
->inbox(false),
])
->defaultDuration(2)
->whenOverdue(Escalation::make()->notify(Actor::slot('escalation', default: Actor::role('Department heads'))));
}
}
Method Default Purpose
key() – (required) unique key, [a-z][a-z0-9-]* (up to 64 characters); used in permissions (workflows.<key>.override), tables, commands and the API
define(Definition) – (required) the definition
version() 1 increase it whenever the definition changes, see Versions
label() key, humanized name in the panel and in notifications; may be a language key
Method of Definition Purpose
column('approval_state') column of the record that holds the state (default status)
states([...]) the states
transitions([...]) the transitions
defaultDuration(), deadline(), clockStartsIn(), expectedPath(), whenAtRisk(), whenOverdue() the schedule of the cases, see Durations and deadline
required() always active in every tenant, without availability or activation
fourEyesRelease() tenant settings need the release of a second administrator, see Tenant configuration
final class OrderResource extends Resource
{
protected array $workflows = [OrderWorkflow::class];
public function collection(Collection $collection): Collection
{
$workflow = $this->workflow('order-approval');
return $collection
->columns([TextColumn::make('number'), $workflow->stateColumn(), $workflow->nextActorsColumn()])
->filters([$workflow->stateFilter(), $workflow->myTasksFilter()]);
}
}

A resource can have several workflows, each with its own state column. Transition names must be unique among the workflows of a resource. Columns and filters: Lists and detail page.

The column (e.g. approval_state VARCHAR(40) NULL) must exist in the table and must not be a field of the form: only transitions change it. Add it with a migration:

$this->forge->addColumn('orders', [
'approval_state' => ['type' => 'VARCHAR', 'constraint' => 40, 'null' => true],
]);

Records created in the panel or through the API start in the initial state; records created elsewhere start on their first transition, see Running transitions.

Terminal window
php spark anvil:workflow:check

validates all definitions, stores new versions and reports records in unknown states; run it after every change and on deployment, see Commands.

Tenants activate workflows on the page Workflows (Access::pages()), after an operator made them available – or mark the definition required() to have it active everywhere. See Tenant configuration.