https://github.com/pixelworxio/livewire-workflows
Build multi-step workflows in Laravel / Livewire with zero boilerplate
https://github.com/pixelworxio/livewire-workflows
laravel livewire livewire-laravel livewire-packages livewire3 multi-step multi-step-form php workflow workflows
Last synced: 22 days ago
JSON representation
Build multi-step workflows in Laravel / Livewire with zero boilerplate
- Host: GitHub
- URL: https://github.com/pixelworxio/livewire-workflows
- Owner: pixelworxio
- License: mit
- Created: 2025-11-04T17:04:00.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-01-12T05:41:19.000Z (about 2 months ago)
- Last Synced: 2026-01-12T15:42:10.843Z (about 2 months ago)
- Topics: laravel, livewire, livewire-laravel, livewire-packages, livewire3, multi-step, multi-step-form, php, workflow, workflows
- Language: PHP
- Homepage:
- Size: 230 KB
- Stars: 70
- Watchers: 2
- Forks: 5
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE.md
Awesome Lists containing this project
README

---
# Livewire Workflows (BETA)
**Build powerful multi-step workflows in Laravel with zero boilerplate.** Define complex user journeys—onboarding, checkouts, surveys—using an expressive, route-like DSL. Get automatic route registration, guard-based navigation, state persistence, and full Livewire 4.x integration out of the box. Livewire 3.x is also supported.
```php
// Define a complete workflow in seconds
Workflow::flow('onboarding')
->entersAt(name: 'onboarding.start', path: '/onboarding')
->finishesAt('dashboard')
->step('verify-email')
->goTo(VerifyEmail::class)
->unlessPasses(EmailVerifiedGuard::class)
->order(10)
->step('profile')
->goTo(EditProfile::class)
->unlessPasses(ProfileCompletedGuard::class)
->order(20);
```
That's it. No manual routes. No state management headaches.
View the testbench repo, https://github.com/pixelworxio/livewire-workflows-testbench, for examples of how this package can help you with your project.
---
## ✨ Why Livewire Workflows?
| Feature | Livewire Workflows | Manual Implementation |
|---------|-------------------|----------------------|
| **Route Management** | ✅ Auto-generated | ❌ Define every route manually |
| **State Persistence** | ✅ Built-in (Session/DB) | ❌ Roll your own |
| **Navigation Logic** | ✅ Guard-based pipeline | ❌ Complex conditionals everywhere |
| **Back Button** | ✅ History tracking | ❌ Custom session juggling |
| **Progress Tracking** | ✅ One method call | ❌ Calculate yourself |
| **Events & Analytics** | ✅ Fire automatically | ❌ Remember to dispatch |
**Result:** Spend time building features, not workflow infrastructure.
---
## 📦 Installation
```bash
composer require pixelworxio/livewire-workflows
```
### Quick Setup (Session-Based)
```bash
php artisan workflows:install
```
### Production Setup (Database-Backed)
```bash
php artisan workflows:install --with-db
php artisan migrate
```
---
## 🚀 Quick Start (3 Minutes)
### 1. Define Your Workflow
Open `routes/workflows.php`:
```php
use Pixelworxio\LivewireWorkflows\Facades\Workflow;
Workflow::flow('onboarding')
->entersAt(name: 'onboarding.start', path: '/onboarding')
->finishesAt('dashboard')
->step('verify-email')
->goTo(\App\Livewire\Onboarding\VerifyEmail::class)
->unlessPasses(\App\Guards\EmailVerifiedGuard::class)
->order(10)
->step('profile')
->goTo(\App\Livewire\Onboarding\EditProfile::class)
->unlessPasses(\App\Guards\ProfileCompletedGuard::class)
->order(20);
```
**Routes auto-registered:**
- Entry: `GET /onboarding` → `onboarding.start`
- Step 1: `GET /onboarding/verify-email` → `onboarding.verify-email`
- Step 2: `GET /onboarding/profile` → `onboarding.profile`
### 2. Create a Guard
**Quick Generation:**
```bash
php artisan make:workflow-guard EmailVerified
```
This generates a guard at `App\Guards\EmailVerifiedGuard.php` with the proper structure.
```php
namespace App\Guards;
use Illuminate\Http\Request;
use Pixelworxio\LivewireWorkflows\Contracts\GuardContract;
class EmailVerifiedGuard implements GuardContract
{
public function passes(Request $request): bool
{
return true;
}
public function onEnter(Request $request): void {}
public function onExit(Request $request): void {}
public function onPass(Request $request): void {}
public function onFail(Request $request): void {}
}
```
**Guard Logic:** `passes() = true` → skip step. `passes() = false` → show step.
### 3. Build Your Livewire Component
```php
namespace App\Livewire\Onboarding;
use Livewire\Component;
use Pixelworxio\LivewireWorkflows\Attributes\WorkflowStep;
use Pixelworxio\LivewireWorkflows\Livewire\Concerns\InteractsWithWorkflows;
#[WorkflowStep(flow:'onboarding', key:'verify-email')]
class VerifyEmail extends Component
{
use InteractsWithWorkflows;
public function resend()
{
auth()->user()->sendEmailVerificationNotification();
session()->flash('message', 'Verification email sent!');
}
public function goToNextStep(): void
{
// ... handle your logic
$this->continue('onboarding'); // workflow name is optional when using WorkflowStep attribute
}
public function render()
{
return view('livewire.onboarding.verify-email');
}
}
```
**And in your Blade view:**
```blade
Verify Your Email
Check your inbox for the verification link.
Resend Email
I've Verified — Continue
```
### 4. Done!
Visit `/onboarding`. The package:
1. Evaluates guards in order
2. Redirects to first incomplete step
3. Tracks progress automatically
4. Completes when all steps pass
---
## 📖 Core Concepts
### Auto-Generated Routes
Routes are **automatically registered** from your DSL:
```php
->entersAt(name: 'checkout.start', path: '/checkout') // Entry route
->step('shipping') // Auto: /checkout/shipping
->step('payment') // Auto: /checkout/payment
```
**Generated:**
- `checkout.start` → `/checkout`
- `checkout.shipping` → `/checkout/shipping`
- `checkout.payment` → `/checkout/payment`
### Dynamic Routes with Parameters
Workflows support **dynamic route parameters** and **route model binding**:
```php
Workflow::flow('user-checkout')
->entersAt(name: 'user-checkout.start', path: '/user/{user}/checkout/{product}')
->finishesAt('dashboard')
->step('shipping')
->goTo(\App\Livewire\Checkout\Shipping::class)
->unlessPasses(\App\Guards\HasShippingAddress::class)
->order(10)
->step('payment')
->goTo(\App\Livewire\Checkout\Payment::class)
->unlessPasses(\App\Guards\HasPaymentMethod::class)
->order(20);
```
**Generated Routes:**
- `user-checkout.start` → `/user/{user}/checkout/{product}`
- `user-checkout.shipping` → `/user/{user}/checkout/{product}/shipping`
- `user-checkout.payment` → `/user/{user}/checkout/{product}/payment`
**Route Model Binding:**
```php
// Supports Laravel's route model binding syntax
->entersAt(name: 'checkout.start', path: '/user/{user:id}/product/{product:slug}')
```
**Navigation Preserves Parameters:**
Route parameters are automatically passed through all workflow navigation:
```php
class Shipping extends Component
{
use InteractsWithWorkflows;
protected ?string $workflowName = 'user-checkout';
// Livewire receives route parameters
public function mount($user, $product)
{
// $user and $product are automatically injected
}
public function submit()
{
// Parameters are automatically preserved when continuing
$this->continue($this->workflowName); // Still navigates with {user} and {product}
}
}
```
The `continue()` and `back()` methods automatically extract and pass route parameters from the current request, ensuring seamless navigation throughout the workflow.
### Guard Behavior
Guards use **positive semantics**:
```php
class ProfileCompleteGuard implements GuardContract
{
public function passes(Request $request): bool
{
return $request->user()->profile_completed;
}
}
```
| Guard Result | Step Behavior |
|--------------|---------------|
| `passes() = true` | **Skip** this step |
| `passes() = false` | **Show** this step |
Use `unlessPasses(Guard::class)`: "Show step UNLESS guard passes."
### Navigation Flow
1. User visits entry route (`/onboarding`)
2. Package evaluates guards in `order`
3. Redirects to first failing guard's step
4. User completes step, calls `$this->continue('onboarding')`
5. Re-evaluates from entry → next unmet step or finish
---
## 🧩 Advanced Features
### State Management
#### Setting Workflow Name
Use the `#[WorkflowName]` attribute to explicitly declare your component's workflow, when not using the WorkflowStep attribute:
```php
use Pixelworxio\LivewireWorkflows\Attributes\WorkflowName;
#[WorkflowName('checkout')]
class CheckoutShipping extends Component
{
use InteractsWithWorkflows;
// protected ?string $workflowName = 'checkout'; // No need to set
}
```
**Benefits:**
- ✅ Eliminates boilerplate
- ✅ Makes workflow association explicit
- ✅ Auto-detected during component boot
- ✅ Falls back to route-based detection if not present
#### Persisting State
Persist data across workflow steps with the `#[WorkflowState]` attribute:
```php
use Pixelworxio\LivewireWorkflows\Attributes\WorkflowName;
use Pixelworxio\LivewireWorkflows\Attributes\WorkflowState;
#[WorkflowName('checkout')]
class CheckoutShipping extends Component
{
use InteractsWithWorkflows;
#[WorkflowState]
public ?string $address = null;
#[WorkflowState(encrypt: true)]
public ?string $creditCard = null;
#[WorkflowState(namespace: 'shipping')]
public ?string $method = null;
}
```
**Features:**
- Auto-hydration on mount
- Auto-persistence on dehydrate
- Encryption support
- Namespace grouping
- Session or database storage
[Full state management guide →](STATE_MANAGEMENT.md)
### Progress Tracking
```php
$progress = workflow('onboarding')->progressFor($request);
// Returns:
[
'total' => 3,
'completed' => 1,
'remaining' => 2,
'percentage' => 33.33,
'current_step' => 'verify-email',
'next_step' => 'profile',
'is_complete' => false,
]
```
### Events
Listen to workflow lifecycle:
```php
use Pixelworxio\LivewireWorkflows\Events\{WorkflowAdvanced, WorkflowCompleted};
Event::listen(WorkflowAdvanced::class, function ($event) {
Log::info("User {$event->userKey} moved from {$event->fromKey} to {$event->toKey}");
});
Event::listen(WorkflowCompleted::class, function ($event) {
Mail::to($user)->send(new OnboardingComplete());
});
```
### CLI Tools
```bash
# Generate a new workflow
php artisan make:workflow checkout
# Generate a guard class
php artisan make:workflow-guard EmailVerified
# Add a step to existing workflow
php artisan make:workflow-step checkout payment \
--component=App\\Livewire\\Checkout\\Payment \
--guard=App\\Guards\\CartNotEmptyGuard \
--order=20
# Validate and document all workflows
php artisan workflows:scan
```
---
## 🎯 Real-World Examples
### Multi-Page Checkout
```php
Workflow::flow('checkout')
->entersAt(name: 'checkout.start', path: '/checkout')
->finishesAt('orders.confirmation')
->step('cart')
->goTo(ReviewCart::class)
->unlessPasses(CartNotEmptyGuard::class)
->order(10)
->step('shipping')
->goTo(ShippingAddress::class)
->unlessPasses(ShippingAddressGuard::class)
->order(20)
->step('payment')
->goTo(PaymentMethod::class)
->unlessPasses(PaymentMethodGuard::class)
->order(30);
```
### Employee Onboarding
```php
Workflow::flow('employee-onboarding')
->entersAt(name: 'onboard.start', path: '/onboard')
->finishesAt('employee.dashboard')
->step('paperwork')
->goTo(Paperwork::class)
->unlessPasses(PaperworkCompleteGuard::class)
->order(10)
->step('it-setup')
->goTo(ITSetup::class)
->unlessPasses(ITAccountGuard::class)
->order(20)
->step('training')
->goTo(TrainingModules::class)
->unlessPasses(TrainingCompleteGuard::class)
->order(30);
```
### User Registration Flow
```php
Workflow::flow('signup')
->entersAt(name: 'signup.start', path: '/signup')
->finishesAt('welcome')
->step('account')
->goTo(CreateAccount::class)
->unlessPasses(AccountCreatedGuard::class)
->order(10)
->step('verify-email')
->goTo(VerifyEmail::class)
->unlessPasses(EmailVerifiedGuard::class)
->order(20)
->step('preferences')
->goTo(SetPreferences::class)
->unlessPasses(PreferencesSetGuard::class)
->order(30);
```
---
## 🔧 Configuration
Publish and customize the config:
```bash
php artisan vendor:publish --tag=livewire-workflows-config
```
**`config/livewire-workflows.php`:**
```php
return [
// State persistence: 'null', 'session', or 'eloquent'
'repository' => env('WORKFLOWS_REPOSITORY', 'session'),
// Middleware applied to all workflow routes
'middleware' => ['web', 'auth'],
];
```
### State Repository Options
| Repository | Use Case | Persistence |
|------------|----------|-------------|
| `null` | Stateless workflows | None |
| `session` | Guest users, simple flows | Session lifetime |
| `eloquent` | Authenticated users, production | Database |
---
## 📊 API Reference
### Workflow DSL
```php
Workflow::flow(string $name)
->entersAt(name: string, path: string)
->finishesAt(string $routeName)
->step(string $key)
->goTo(string $componentClass)
->unlessPasses(string $guardClass)
->order(int $order);
```
### Livewire Trait Methods
```php
use InteractsWithWorkflows;
$this->continue(string $flow): RedirectResponse
$this->back(string $flow, string $currentKey): ?RedirectResponse
$this->syncState(): void // Manually persist state
```
Note: When using the `#[WorkflowStep]` attribute, the `$flow` and `$currentKey` properties are optional
```
#[WorkflowStep(flow: 'my-flow', key: 'current-step', middleware: ['auth'])]
use InteractsWithWorkflows;
$this->continue(): RedirectResponse
$this->back(): ?RedirectResponse
```
### Helper Functions
```php
workflow(string $flow)->redirect(Request $request, ?string $doneRoute = null)
workflow(string $flow)->nextRouteNameFor(Request $request, ?string $doneRoute = null)
workflow(string $flow)->previousRouteNameFor(string $currentKey, Request $request)
workflow(string $flow)->progressFor(Request $request)
```
### State Management Helpers
```php
// Automatic with attributes
#[WorkflowState] public ?string $email = null;
#[WorkflowState(encrypt: true)] public ?string $password = null;
#[WorkflowState(namespace: 'profile')] public ?string $name = null;
// Manual helpers
$this->putWorkflowState(string $key, mixed $value)
$this->getWorkflowState(string $key, mixed $default = null)
$this->hasWorkflowState(string $key)
$this->forgetWorkflowState(string $key)
$this->clearWorkflowState(?string $namespace = null)
$this->allWorkflowState()
```
---
## 🆚 Comparison with Alternatives
**Or so Claude.ai says...
| Feature | Livewire Workflows | Spatie Laravel Wizard | Custom Solution |
|---------|-------------------|----------------------|-----------------|
| Route Auto-Registration | ✅ | ❌ | ❌ |
| Guard-Based Navigation | ✅ | ❌ | Custom |
| State Persistence | ✅ Built-in | ❌ | Custom |
| Livewire 4 & 3 Native | ✅ | ⚠️ Limited | N/A |
| History/Back Support | ✅ | ⚠️ Basic | Custom |
| Progress Tracking | ✅ | ❌ | Custom |
| Events | ✅ | ❌ | Custom |
| Learning Curve | Low | Medium | High |
---
## 💬 FAQ
Does this support Livewire v4?
Yes. The package is built and certified for Livewire v4, with full backward compatibility for Livewire v3. All lifecycle hooks and the redirect/navigation API are identical across both versions.
Do I need to define routes manually?
No. Step routes are auto-registered from your DSL. You only need to define the finish route in your regular `routes/web.php`, if not already present.
Can I protect workflows with authentication?
Yes. Add `'auth'` to the `middleware` array in `config/livewire-workflows.php`.
How do I skip steps dynamically?
Make the guard's `passes()` method return `true` when the step should be skipped.
Can workflows share state?
No. State is scoped per workflow and per user. This is by design for data isolation.
What happens if a user jumps to a step URL directly?
The step page loads, but calling `continue()` will re-evaluate guards and redirect appropriately.
---
## 🛠️ Requirements
- PHP 8.3+
- Laravel 11.x or 12.x
- Livewire 4.x (3.x also supported)
---
## 🤝 Contributing
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
### Development Setup
```bash
git clone https://github.com/pixelworxio/livewire-workflows.git
cd livewire-workflows
composer install
composer test
```
---
## 📜 License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
---
## 🙏 Credits
- [Whoisthisstud](https://github.com/whoisthisstud)
[//]: # (- [All Contributors](../../contributors))
Built with ❤️ using [Laravel](https://laravel.com), [Livewire](https://livewire.laravel.com), and [Spatie's Package Tools](https://github.com/spatie/laravel-package-tools).
---
## 🌟 Show Your Support
Give a ⭐️ if this project helped you!