Skip to main content
This guide walks you through creating a simple workflow with an activity, running it, and getting the result.

Prerequisites

Before starting, ensure you have:

Create your first workflow

1

Create a workflow class

Create a new workflow that greets a user:
app/Workflows/GreetingWorkflow.php
<?php

namespace App\Workflows;

use function Workflow\activity;
use Workflow\Workflow;

class GreetingWorkflow extends Workflow
{
    public function execute($name)
    {
        $result = yield activity(GreetingActivity::class, $name);

        return $result;
    }
}
The execute method is called when your workflow runs. Use yield to call activities and other async operations.
2

Create an activity class

Create the activity that performs the actual greeting:
app/Activities/GreetingActivity.php
<?php

namespace App\Activities;

use Workflow\Activity;

class GreetingActivity extends Activity
{
    public function execute($name)
    {
        return "Hello, {$name}!";
    }
}
Activities contain your business logic. They can:
  • Call external APIs
  • Query databases
  • Perform calculations
  • Send emails
  • Any other side-effecting operation
3

Start the workflow

Use WorkflowStub to start your workflow:
routes/web.php
use Workflow\WorkflowStub;
use App\Workflows\GreetingWorkflow;

Route::get('/greet/{name}', function ($name) {
    $workflow = WorkflowStub::make(GreetingWorkflow::class);
    $workflow->start($name);
    
    return response()->json([
        'workflow_id' => $workflow->id(),
        'status' => 'started'
    ]);
});
The workflow runs asynchronously on your queue. The start() method returns immediately.
4

Get the workflow result

Retrieve the workflow output once it completes:
routes/web.php
Route::get('/result/{workflowId}', function ($workflowId) {
    $workflow = WorkflowStub::load($workflowId);
    
    if (!$workflow->running()) {
        return response()->json([
            'output' => $workflow->output(),
            'status' => 'completed'
        ]);
    }
    
    return response()->json([
        'status' => 'running'
    ]);
});
Use $workflow->running() to check if a workflow is still executing. The output() method returns the final result from the execute() method.

Run the workflow

Now test your workflow:
curl http://your-app.test/greet/World
Response:
{
  "workflow_id": "01H9X7K8M5N6P7Q8R9S0T1V2W3",
  "status": "started"
}
Then check the result:
curl http://your-app.test/result/01H9X7K8M5N6P7Q8R9S0T1V2W3
Response:
{
  "output": "Hello, World!",
  "status": "completed"
}

Add multiple activities

Workflows can execute multiple activities in sequence:
app/Workflows/OnboardingWorkflow.php
<?php

namespace App\Workflows;

use function Workflow\activity;
use Workflow\Workflow;

class OnboardingWorkflow extends Workflow
{
    public function execute($userId)
    {
        // Create user account
        $user = yield activity(CreateUserActivity::class, $userId);
        
        // Send welcome email
        yield activity(SendWelcomeEmailActivity::class, $user);
        
        // Provision resources
        $resources = yield activity(ProvisionResourcesActivity::class, $user);
        
        return [
            'user' => $user,
            'resources' => $resources
        ];
    }
}
Each activity executes in order. If any activity fails, the workflow automatically retries with exponential backoff.

Add a timer

Delay execution using timers:
app/Workflows/ReminderWorkflow.php
<?php

namespace App\Workflows;

use function Workflow\{activity, timer};
use Workflow\Workflow;

class ReminderWorkflow extends Workflow
{
    public function execute($userId)
    {
        // Send first reminder immediately
        yield activity(SendReminderActivity::class, $userId, 1);
        
        // Wait 24 hours
        yield timer(60 * 60 * 24);
        
        // Send second reminder
        yield activity(SendReminderActivity::class, $userId, 2);
        
        // Wait another 24 hours
        yield timer(60 * 60 * 24);
        
        // Send final reminder
        yield activity(SendReminderActivity::class, $userId, 3);
        
        return 'reminders_sent';
    }
}
Timers are durable. Even if your server restarts during the wait period, the workflow will resume at the correct time.

Handle signals

Workflows can receive signals from external sources:
app/Workflows/ApprovalWorkflow.php
<?php

namespace App\Workflows;

use function Workflow\{activity, await};
use Workflow\{Workflow, SignalMethod};

class ApprovalWorkflow extends Workflow
{
    private bool $approved = false;
    private bool $rejected = false;
    
    #[SignalMethod]
    public function approve(): void
    {
        $this->approved = true;
    }
    
    #[SignalMethod]
    public function reject(): void
    {
        $this->rejected = true;
    }
    
    public function execute($requestId)
    {
        // Send approval request
        yield activity(SendApprovalRequestActivity::class, $requestId);
        
        // Wait for approval or rejection
        yield await(fn() => $this->approved || $this->rejected);
        
        if ($this->approved) {
            yield activity(ProcessApprovalActivity::class, $requestId);
            return 'approved';
        }
        
        yield activity(ProcessRejectionActivity::class, $requestId);
        return 'rejected';
    }
}
Send a signal to the workflow:
$workflow = WorkflowStub::load($workflowId);
$workflow->approve(); // or $workflow->reject()

Use dependency injection

Both workflows and activities support Laravel’s dependency injection:
app/Activities/NotifyUserActivity.php
<?php

namespace App\Activities;

use Illuminate\Contracts\Mail\Mailer;
use Workflow\Activity;

class NotifyUserActivity extends Activity
{
    public function execute(Mailer $mailer, $userId, $message)
    {
        // Laravel automatically injects the Mailer
        $mailer->to($userId)->send(new NotificationMail($message));
        
        return true;
    }
}
Only the first parameter(s) that don’t match the workflow arguments will be resolved from the container. In the example above, $mailer is injected, but $userId and $message come from the workflow.

Next steps

Now that you’ve created your first workflow, explore more advanced features:

Workflows

Learn about workflow concepts and patterns

Activities

Understand how to structure activities

Signals & Queries

Interact with running workflows

Error handling

Handle failures and implement retries

Child workflows

Compose complex workflows from smaller ones

Waterline UI

Monitor workflows visually

Build docs developers (and LLMs) love