Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Nova Workflow Laravel Package

orlyapps/nova-workflow

orlyapps/nova-workflow brings configurable workflows to Laravel Nova resources. Define states and transitions, show current status, and move models through approval or publishing steps using Nova actions, with transition buttons, guards, and activity history for clearer admin-side process control.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Confirm compatibility before anything else. The package's last release was April 2022, so check the composer constraint against your Nova major version (Nova 3 vs 4/5) and your Laravel version. Pin the package explicitly in composer.json rather than relying on a floating constraint.
  2. Install and publish config:
    composer require orlyapps/nova-workflow
    php artisan vendor:publish --provider="Orlyapps\\NovaWorkflow\\NovaWorkflowServiceProvider"
    
    You should end up with a workflow config file (typically config/workflow.php) where places, transitions, and the initial place are declared per workflow name.
  3. Prepare the model. The workflow engine stores its "marking" in a model attribute (e.g. a status or state column). Add that column via migration, make it fillable, and cast it if needed. The model also needs the accessor path the package uses to read/write the marking — verify this against the config rather than assuming.
  4. Register the field in the Nova resource:
    use Orlyapps\NovaWorkflow\Fields\Workflow;
    
    public function fields(Request $request)
    {
        return [
            Workflow::make('Status', 'status')
                ->workflow('post_approval'),
            // ... other fields
        ];
    }
    
  5. First use case: a simple draft → review → published state machine on a Post model. Render the field on the detail view first — the buttons map cleanly to allowed transitions, and you get immediate feedback on whether the marking store, guards, and permissions are wired correctly. Only move to index/list usage once detail works.

The first place to look when something is off is the generated config file, not the package source — 90% of "the button does nothing" reports trace back to a transition name mismatch or a marking-store attribute that isn't persisting.

Implementation Patterns

Start with state machines, graduate to workflows. If your domain has a single linear lifecycle (draft → review → published → archived), define it as a state machine. Reserve multi-place workflows (a subject occupying several places simultaneously, e.g. "content approved" and "legal signed off") for genuine parallel-approval cases. Over-modelling early makes the Nova UI unreadable because every legal transition becomes a button.

Centralise transition side effects in event listeners, not in the config. Subscribe to the workflow's transition/entered events and do the work there:

Event::listen('workflow.post_approval.transition', function ($event) {
    $event->getSubject()->recordAudit($event->getTransition());
});

This keeps the config declarative and makes side effects testable and queued independently.

Gate buttons by role/permission. Combine the field's visibility/authorization with the workflow's guard configuration so that a user who can't perform a transition doesn't see a dead button. Two layers: the field-level canSee/canRun on the Nova field (UI), and guards in the workflow definition (enforcement). Never rely on the Nova field alone — transitions can also be triggered programmatically.

Treat the Nova field as a thin control panel, not the source of truth. Put business rules in guards (can($subject, $transition)) and assert them again in the code path that actually performs the transition. The Nova button calls apply() under the hood; anything that can call apply() (an artisan command, a queued job, an API endpoint) must hit the same guards.

Audit trail pattern. Add a workflow_transitions table and write a row in a listener for every applied transition: subject, from-place, to-place, transition name, actor, timestamp. This is the single highest-value addition — it turns a fragile "the status is wrong" conversation into a query. It also lets you build a Nova Lens or a read-only HasMany field showing history on the detail page.

Multi-workflow models. When a model has two independent lifecycles (e.g. Order has both payment and fulfilment), give each a distinct workflow name and a distinct backing column. Render two separate Workflow fields, clearly labelled. Do not let both workflows share a single status column.

Testing transitions. Drive the unit tests through the package's API rather than through Nova HTTP tests:

$this->assertTrue($order->workflow_can('ship'));
$order->workflow_apply('ship');
$this->assertTrue($order->workflow_can('refund'));

Write one test per guard: allowed-from state, disallowed-from state, and role restriction. These tests catch 95% of regressions when you edit the config.

Gotchas and Tips

  • Version drift is the headline risk. A 2022 package against Nova 4/5 and Laravel 10/11 will likely need constraint fiddling or a fork. Before adopting, run composer why-not orlyapps/nova-workflow and read the package's own composer.json to see what Symfony Workflow version it needs. Budget for a fork if you're on current Nova — the field is small enough (a Vue component plus a PHP field class) that forking or vendoring is a reasonable fallback.
  • Config caching bites. Workflow definitions live in config; after editing transitions, php artisan config:clear (and re-cache in deploy). A stale cached config produces "transition does not exist" errors that look like typos but aren't.
  • The marking store must actually persist. If the transition button appears to work but the page reloads showing the old state, check: is the attribute in $fillable, is it being saved (the field may save via a separate request that bypasses your normal form save), and does the accessor path in config match the real column name exactly? A status vs state mismatch is the classic failure.
  • Closure-based guards and cached config/routes. If you configure guards as PHP closures, they cannot be serialized into a cached config or route cache in some setups. Prefer named guard classes or the workflow's built-in expression/guard string syntax for anything that needs to survive config:cache.
  • Transition buttons bypass form validation. Clicking a button performs the transition outside your typical FormRequest lifecycle. If the transition requires data (a rejection reason, an approver note), either use a Nova Action with a modal instead of the inline button, or enforce the requirement in a guard that reads the already-saved request data.
  • Don't stack two Workflow fields on one workflow. Rendering the same workflow twice on a detail page produces duplicate buttons and confusing double-apply behaviour. Use one field per workflow name.
  • Queue heavy listeners. Transition events fire synchronously inside the Nova request; a listener that sends emails or calls an external API will make the button feel broken (long spinner, possible timeout). Push that work onto a queue.
  • Extension points to prefer over editing vendor code:
    • A subclass of the Workflow field to add icons, colours, or confirmation modals.
    • Event listeners for audit logging and side effects.
    • A Nova Action wrapping apply() when you need a confirmation form or bulk transitions.
    • A HasMany history field on the detail page for visibility.
  • Debugging checklist, in order: (1) is the workflow name in Workflow::make(...)->workflow('x') exactly the config key? (2) does php artisan tinker show $model->workflow_can('transition') returning true? (3) does the button appear in the Network tab with a POST to the field's endpoint, and what does the response say? If step 2 fails, the problem is config/guards. If step 2 passes but the button fails, the problem is the Nova field or authorization.
  • Log transitions in dev. A quick Log::info('workflow', [...]) inside a listener will save you an afternoon the first time a guard silently rejects a transition.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource