Skip to content

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:

TokenMeaning
*Everyone, including guests
@authenticatedAny authenticated user
@ownerRecord owner (field determined by state_access.owner_field config)
@assignedAny user assigned to the record
@assigned:typeUser assigned with a specific type, e.g. @assigned:primary
role:nameUser with a specific role
role:name1,name2User with any of the listed roles
permission:nameUser 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.

php
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.

php
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.

php
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.

php
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.

php
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 ​

ContractWhat it gives the editor
FieldListProviderInterfaceWhich fields a formula may name, for a given context
FormulaCompletionProviderThe variables and the functions of one scope, as the editor reads them
FormulaConditionProviderWhether 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 ​

ContractWhat it gives the engine
NarrowsFieldPermissionsThe 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:

php
// config/filament-flow.php
'state_access' => [
    'role_resolver' => \App\Services\MyRoleResolver::class,
],

Full interface:

php
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:

php
// config/filament-flow.php
'state_access' => [
    'permission_resolver' => \App\Services\MyPermissionResolver::class,
],

Full interface:

php
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.

Proprietary software. All rights reserved.