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

Laravel Flow Laravel Package

jobmetric/laravel-flow

jobmetric/laravel-flow adds structured, code-driven workflows to Laravel: define flows, states, and transitions, move records through each stage, and track progress with events and database history. Great for approvals, order lifecycles, and multi-step business processes.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish:

composer require jobmetric/laravel-flow
php artisan vendor:publish --provider="Jobmetric\Flow\FlowServiceProvider"
php artisan migrate

Publish in this order: config, migrations, then run migrate. If you skip migrations the package will silently fail on every call — this is the #1 first-hour trap.

Where to look first:

  1. config/flow.php — the shape of states, transitions, and any default workflow definitions live here. Skim it before touching code; most surprises come from defaults you didn't read.
  2. database/migrations/*_create_flow_* — confirms the exact table names and columns the package exposes. Reading these saves you from guessing at ->transitionTo() semantics later.
  3. The HasFlow (or similarly named) trait in src/Traits/ — this is the public surface you attach to your own models. Everything else is plumbing.

First use case — a status lifecycle on an existing model:

The canonical entry point is "I have an Order and it moves through pending → paid → shipped → delivered, and I want that lifecycle enforced and logged." Attach the trait, register the workflow, and let transitions go through the package instead of setting order->status = ... directly.

use Jobmetric\Flow\Traits\HasFlow;

class Order extends Model
{
    use HasFlow;
}

Then, from a service or controller:

$order->transitionTo('paid', ['payment_id' => $payment->id]);

That single call should validate the transition is legal, persist it, and fire the package's transition event — which is where you hook side effects.


Implementation Patterns

Define workflows in config or seeders, not in models

Models are for behavior; workflow graphs are data. Put workflow definitions in config/flow.php for static lifecycles (order status, invoice state) and in a seeder for anything DB-configurable (per-tenant approval chains). Keeps your model classes from accumulating arrays of states.

Guard before acting, then transition

Never call transitionTo() blind. The idiomatic pattern is:

if (! $order->canTransitionTo('shipped')) {
    abort(422, 'Order is not shippable yet.');
}

$order->transitionTo('shipped');

This makes your failure mode a domain error instead of an exception from inside the package, and it lets you give the user a meaningful message. Wrap both in a form request or action class so authorization happens outside the workflow layer.

Use transition events for side effects, not for the transition itself

The right place for "send the shipping email," "charge the card," or "notify the sales rep" is the package's transition event:

Event::listen(FlowTransitioned::class, function (FlowTransitioned $event) {
    if ($event->flow->to === 'shipped') {
        Mail::to($event->model->customer)->queue(new OrderShipped($event->model));
    }
});

Keep the event listener side effects idempotent where possible — if your queue retries, you don't want two charge attempts. Check ->to state explicitly; a single listener catching all transitions is a debugging nightmare.

Wrap multi-model transitions in a DB transaction

Workflows that touch more than one record (order + invoice + inventory) must be transactional:

DB::transaction(function () use ($order) {
    $order->transitionTo('paid');
    $order->invoice->transitionTo('paid');
});

The package persists the new state but won't roll back related models for you. If you don't wrap this, a crash between the two transitions leaves inconsistent data.

Model workflows per "thing that has a lifecycle"

This package scales best when each model owns its own workflow. Don't try to model "Order approval → Inventory → Shipping" as one mega-workflow. Instead, let the Order workflow reach a fulfilled state and trigger a shipping workflow from the listener. Small, composable workflows are the whole point — hence "Make Work Flow your Way."

Test with state factories, not manual setup

Create factory states for each workflow state so tests read like the domain:

$order = Order::factory()->paid()->create();
$order->transitionTo('shipped');

This keeps test setup aligned with the workflow definition (if you add a new prerequisite state, the factory state updates, not 40 tests).

Integrate with policies, not inside the workflow

Authorization belongs in Laravel policies, not in workflow guards. Use the workflow to answer "can this state change happen?" and the policy to answer "can this user cause it?". Trying to encode roles into workflow definitions is a common early mistake and it doesn't scale.


Gotchas and Tips

Config caching bites hard

Because workflow definitions are read from config, php artisan config:cache will freeze them for the process. If you change config/flow.php and nothing happens in production-like environments, run php artisan config:clear first. This is the single most common "the package is broken" false alarm.

Migration publishing order matters

If vendor:publish runs the config but not the migrations (because you were asked "which provider?" and picked wrong), everything looks installed but migrate creates no tables. The package will only fail on the first transitionTo(). Confirm the tables exist (php artisan db:show --table=flows) before debugging anything else.

transitionTo() from an unexpected state is silent or loud depending on version

Some releases throw FlowTransitionException; others return false. Never assume. Check canTransitionTo() first and treat the result of transitionTo() as advisory. If you rely on exceptions and a version bump relaxes it, you get silent no-ops in production.

Guards vs. events: pick one home

The package likely exposes guard/condition hooks on transitions. Don't put business rules in both guards and event listeners. Guards should be pure checks (does the required field exist?), listeners should be effects (send mail, hit webhook). Mixed responsibilities make workflow bugs almost unfindable.

Concurrency: transitions are not atomic by default

Two requests arriving simultaneously on paid → shipped can both pass a pre-check and both transition. If that matters, wrap in DB::transaction with a lockForUpdate() read of the record first, or add a unique index on (model_id, to_state) in the flows table. This is a subtle bug that only shows up under load.

Extend via the trait, not via fork

If the package doesn't support a feature you need (e.g., transition reasons, actor IDs), add your own HasFlow trait that uses the package trait and overrides the transition method, then attach your trait to models. Forking loses you upgrades; the trait composition pattern is what the package is designed for.

Low community surface = read the source

With fewer than 30 stars and a niche scope, expect sparse docs, few Stack Overflow answers, and a limited changelog. Budget time to read src/ — specifically the trait and the transition service — before assuming a feature exists. When you find a behavior that isn't documented, add a comment in your own wrapper trait so the next developer on your team doesn't rediscover it.

Log the transition history

Even if the package stores transitions, also let your application listen and record them in an audit table or activity log. Packages change; audit history is a compliance asset. The transition event is your one cheap hook to capture actor, IP, reason, and timestamp in a schema you control.

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.
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
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core