Contracts & Interfaces
All contracts live in the RoBYCoNTe\FilamentFlow\Contracts namespace.
Implement in your State classes
HasAccessRules
Defines who can create, view, edit, or transition a record while it is in that state. Rules are evaluated with OR logic — access is granted when the user satisfies at least one rule token.
Available rule tokens:
| Token | Meaning |
|---|---|
* | Everyone, including guests |
@authenticated | Any authenticated user |
@owner | Record owner (field determined by state_access.owner_field config) |
@assigned | Any user assigned to the record |
@assigned:type | User assigned with a specific type, e.g. @assigned:primary |
role:name | User with a specific role |
role:name1,name2 | User with any of the listed roles |
permission:name | User with a specific permission |
getCreateAccessRules() only applies to the initial state. The @owner and @assigned tokens have no effect there since the record does not exist yet.
use RoBYCoNTe\FilamentFlow\Contracts\HasAccessRules;
final class PendingState extends OrderState implements HasAccessRules
{
public static function getCreateAccessRules(): array
{
return ['role:sales,admin'];
}
public static function getViewAccessRules(): array
{
return ['@authenticated'];
}
public static function getEditAccessRules(): array
{
return ['@owner', '@assigned:primary'];
}
public static function getTransitionAccessRules(): array
{
return ['role:manager,admin'];
}
}HasStateNotifications
Defines notifications to dispatch when the record enters or exits the state. Both methods return an array of WorkflowNotificationBuilder instances.
use RoBYCoNTe\FilamentFlow\Builders\WorkflowNotificationBuilder;
use RoBYCoNTe\FilamentFlow\Contracts\HasStateNotifications;
final class ProcessingState extends OrderState implements HasStateNotifications
{
public function onEnterNotifications(): array
{
return [
WorkflowNotificationBuilder::make()
->channel('database')
->recipients(['@assigned', 'role:manager'])
->title('Order moved to Processing')
->body('Order #{{record.id}} is now being processed.')
->immediate(),
];
}
public function onExitNotifications(): array
{
return [
WorkflowNotificationBuilder::make()
->channel('mail')
->recipients(['@owner'])
->subject('Your order has moved on')
->body('Your order #{{record.id}} has left the processing stage.')
->immediate(),
];
}
}HasStateSortOrder
Provides a custom sort position for the state. Lower values appear first. Used by StateSelectColumn, StateColumn, and StateGroup when sorting.
use RoBYCoNTe\FilamentFlow\Contracts\HasStateSortOrder;
final class PendingState extends OrderState implements HasStateSortOrder
{
public static function getSortOrder(): int
{
return 1;
}
}
final class ProcessingState extends OrderState implements HasStateSortOrder
{
public static function getSortOrder(): int
{
return 2;
}
}
final class CompletedState extends OrderState implements HasStateSortOrder
{
public static function getSortOrder(): int
{
return 3;
}
}HasStateMetadata
Combines Filament's HasColor, HasDescription, HasIcon, and HasLabel contracts. Implement all four methods to give the state a display label, icon, colour, and description used throughout Filament components.
use Filament\Support\Colors\Color;
use Filament\Support\Enums\IconSize;
use RoBYCoNTe\FilamentFlow\Contracts\HasStateMetadata;
final class CompletedState extends OrderState implements HasStateMetadata
{
public function getColor(): string|array|null
{
return Color::Green;
}
public function getDescription(): string|\Illuminate\Contracts\Support\Htmlable|null
{
return 'The order has been fulfilled and closed.';
}
public function getIcon(): string|\BackedEnum|null
{
return 'heroicon-o-check-circle';
}
public function getLabel(): string|\Illuminate\Contracts\Support\Htmlable|null
{
return 'Completed';
}
}Implement in your Transition classes
HasTransitionNotifications
Defines notifications to dispatch when the transition is executed. Returns an array of WorkflowNotificationBuilder instances.
use RoBYCoNTe\FilamentFlow\Builders\WorkflowNotificationBuilder;
use RoBYCoNTe\FilamentFlow\Contracts\HasTransitionNotifications;
use Spatie\ModelStates\Transition;
class ApproveOrderTransition extends Transition implements HasTransitionNotifications
{
public function __construct(private readonly Order $order) {}
public function handle(): Order
{
$this->order->state->transitionTo(ProcessingState::class);
return $this->order;
}
public function notifications(): array
{
return [
WorkflowNotificationBuilder::make()
->channel('database')
->recipients(['@owner'])
->title('Order Approved')
->body('Your order #{{record.id}} has been approved.')
->immediate(),
WorkflowNotificationBuilder::make()
->channel('mail')
->recipients(['role:warehouse'])
->subject('New order ready for fulfilment')
->body('Order #{{record.id}} is ready to be dispatched.')
->delay(5),
];
}
}Implement to feed the formula editor
| Contract | What it gives the editor |
|---|---|
FieldListProviderInterface | Which fields a formula may name, for a given context |
FormulaCompletionProvider | The variables and the functions of one scope, as the editor reads them |
FormulaConditionProvider | Whether a formula condition holds for a record, and with which message |
The first two are what makes the completions appear while a person types: see the formula editor. The third one is how a host keeps the evaluation of its own formulas, instead of letting the package guess.
Implement to narrow permissions by data
| Contract | What it gives the engine |
|---|---|
NarrowsFieldPermissions | The fields the data of a record takes out of the picture, asked once per validation pass |
The permissions of a state are the same for every record in it. A host that hides a field because of what another field holds binds this contract: it receives the values the validation runs on and the permissions of the state, and returns them with those fields marked visible => false. They are then neither required nor validated, from whichever source the rule comes. It only narrows — a field the state hides stays hidden — and without a binding nothing changes.
Contracts of the package itself
HasStateAction, HasStateAttributes and HasFieldLabels are implemented by the package's own classes — an action, something that carries a state attribute, a relation manager — rather than by the states of a host. They are listed in the API reference.
Implement for custom integration
RoleResolver
Customize how user roles are resolved. Useful when integrating with Spatie Permission, Bouncer, or a bespoke role system.
Register in config:
// config/filament-flow.php
'state_access' => [
'role_resolver' => \App\Services\MyRoleResolver::class,
],Full interface:
use Illuminate\Database\Eloquent\Model;
use RoBYCoNTe\FilamentFlow\Contracts\RoleResolver;
class MyRoleResolver implements RoleResolver
{
public function getRoles(Model $user): array
{
return $user->roles->pluck('slug')->toArray();
}
public function hasAnyRole(Model $user, array $roles): bool
{
return count(array_intersect($this->getRoles($user), $roles)) > 0;
}
public function hasAllRoles(Model $user, array $roles): bool
{
return count(array_intersect($this->getRoles($user), $roles)) === count($roles);
}
public function isSuperAdmin(Model $user): bool
{
return in_array('super_admin', $this->getRoles($user), true);
}
}See Access Control for more details on registering resolvers.
PermissionResolver
Customize how permissions are checked against users. Supports optional record context for policies that depend on the record being accessed.
Register in config:
// config/filament-flow.php
'state_access' => [
'permission_resolver' => \App\Services\MyPermissionResolver::class,
],Full interface:
use Illuminate\Database\Eloquent\Model;
use RoBYCoNTe\FilamentFlow\Contracts\PermissionResolver;
class MyPermissionResolver implements PermissionResolver
{
public function hasPermission(Model $user, string $permission, ?Model $record = null): bool
{
return $user->can($permission, $record ?? $permission);
}
public function hasAnyPermission(Model $user, array $permissions, ?Model $record = null): bool
{
foreach ($permissions as $permission) {
if ($this->hasPermission($user, $permission, $record)) {
return true;
}
}
return false;
}
public function hasAllPermissions(Model $user, array $permissions, ?Model $record = null): bool
{
foreach ($permissions as $permission) {
if (! $this->hasPermission($user, $permission, $record)) {
return false;
}
}
return true;
}
}See Access Control for full registration and usage details.