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
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.
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
Start the workflow
Use WorkflowStub to start your workflow: 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.
Get the workflow result
Retrieve the workflow output once it completes: 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