Skip to content

Building blocks

Guards, effects, forms and resolvers used by several workflows, or chosen in a tenant configuration, are registered by key in a BlockProvider:

use Anvil\Schema\Fields\DateInput;
use Anvil\Workflow\BlockProvider;
use Anvil\Workflow\Blocks;
final class OrderBlocks implements BlockProvider
{
public function register(Blocks $blocks): void
{
$blocks->guard('order.has-items', static fn (array|object $order): bool => service('orders')->hasItems($order));
$blocks->effect('order.send-to-supplier', static fn (array|object $order, array $data) => service('orders')->send($order, $data['order_number']));
$blocks->form('order.delivery', static fn (): array => [DateInput::make('delivery_on')->required()]);
$blocks->resolver('line-manager', new LineManager());
}
}
app/Config/Anvil.php
public array $workflowBlocks = [\App\Workflows\OrderBlocks::class];
Kind Signature
guard fn (array|object $record, WorkflowContext $context): bool
effect fn (array|object $record, array $data, WorkflowContext $context): void
form fn (): array – a list of fields
resolver a WorkflowResolver, or fn (array|object $record, WorkflowContext $context): array of internal user ids

Keys start with a lower case letter, then letters, digits, _, . and - (up to 100 characters), e.g. order.has-items. Use a prefix per module. Keys are stored in the definition: renaming a key needs a new version of the workflows using it.

Closures written directly in a definition are building blocks too: they get a key of their workflow (inline:approve.guard.1). They are not offered to tenants, and changing their code does not change the stored definition – increase the version anyway when their behaviour changes.

The engine runs the stored definition (data), not the PHP class. Referencing code by key keeps definitions comparable between versions and lets tenant administrators choose resolvers without writing code. anvil:workflow:check reports keys that are not registered.