Laravel Actions are a design pattern used to encapsulate specific units of work—such as creating a user, sending an email, or processing a queue—into individual, reusable classes. Instead of bloating your controller or service classes, Actions help you isolate the logic into small, single-purpose files.
Think of an Action as a method that does one thing and does it well, but it's wrapped inside a class to promote clarity, testability, and reuse.
Why Use Laravel Actions?
Using Actions in Laravel can greatly enhance your codebase. Here’s why:
1. Separation of Concerns
Instead of having bloated controllers or services with mixed responsibilities, each Action handles a single task.
Example: You can move the logic for creating a user from your controller into a CreateUserAction class.
2. Reusability
Actions can be used across controllers, jobs, commands, or even tests without duplicating logic.
Example: The same CreateInvoiceAction can be used in a web controller and a scheduled command.
3. Testability
Since Actions are standalone classes, they can be easily tested in isolation without relying on the full controller or service environment.
Example: You can write a unit test for SendWelcomeEmailAction without mocking any controller behaviour.
4. Cleaner Codebase
Controllers become thin and focused on request/response handling. Business logic lives in Actions.
Example: A controller method becomes just 3 lines: validate, call action, return response.
Installing Laravel Actions (by Loris Leiva)
You can build Actions manually, but the lorisleiva/laravel-actions package makes it much more elegant.
composer require lorisleiva/laravel-actions
Practical Examples:
Example 1: Action for Validation and Creating a Post
app/Actions/Post/CreatePostAction.php
namespace App\Actions\Post;
use App\Models\Post;
use Lorisleiva\Actions\Concerns\AsAction;
class CreatePost
{
/**
* Handle the creation of a new post.
*/
public function handle(array $data): Post
{
return Post::create($data);
}
/**
* Validation rules for the request.
*/
public function rules(): array
{
return [
'title' => 'required|string|max:255',
'content' => 'required|string',
];
}
/**
* Invoke method used by Laravel's route to execute the action.
*/
public function __invoke(Request $request): Post
{
$validated = $request->validate($this->rules());
return $this->handle($validated);
}
}
Route Example
use App\Actions\Post\CreatePost;
Route::post('/posts', CreatePost::class);
Example 2: Action for Running a Queue Job
// app/Actions/User/SendWelcomeEmailAction.php
namespace App\Actions\User;
use App\Jobs\SendWelcomeEmail;
class SendWelcomeEmailAction
{
public function execute($user)
{
SendWelcomeEmail::dispatch($user);
}
}
Usage in Controller or Listener:
$action = app(SendWelcomeEmailAction::class);
$action->execute($user);
How to Implement Actions
There are multiple ways to structure your Actions:
Manually create them in a Actions namespace.
Example using the package:
php artisan make:action CreateUser
And then:
public function handle(array $data)
{
// Business logic
}
When to Use Laravel Actions?
Use Actions when:
- A piece of logic is repeated across different parts of your application.
- Your controller methods are growing too large.
- You want to improve unit testing and readability.
- You are building a service-oriented or domain-driven architecture.
Conclusion
Laravel Actions are a powerful tool to improve the architecture of your Laravel application. They promote clean code, reusability, and testability, while reducing the cognitive load in your controllers. Whether you're validating requests, running queues, or performing business logic, wrapping that logic in Actions will lead to a more maintainable and scalable application.
Top comments (3)
While I like the novelty of the idea behind the library, I think it is the wrong abstraction.
From the readme;
The reason we have Controllers, listeners and repositories is to create a loose coupling.
The action in this example has tight coupling because the controller and listener are tied to the
handle
method.In the
Createpost
example therules
method is tied to theasController
method. But it means nothing for anasListener
method if that is added.I also find
Route::post('/posts', CreatePost::class);
confusing, because I would look for an__invoke
method in that class.I think the larger the application gets the harder it is going to be to use actions, because almost everything is an action.
Thanks you for you comment.
I totally get your concerns, and they’re valid — especially around coupling and scaling clarity.
That said, the Laravel Actions pattern offers a different way of organising logic that can still maintain separation of concerns, if used intentionally.
This is a fair concern. The key is to use Actions to encapsulate business logic, not everything. I avoid putting trivial or CRUD-only operations in actions unless they encapsulate domain-specific behaviour or are reused elsewhere.
In large applications, actions help maintain flat controller files, promote reusability, and simplify testing, especially when business logic isn't scattered across multiple places.
And last part is, thanks for the great eye and thoughtful feedback. I've updated the code to reflect your points.
When I first saw the word action I thought of the Action-Domain-Responder pattern. And it is partially right.
But after seeing the documentation I'm more leaning to use cases as the main pattern that the library is build on.
The biggest problem I have is that an Action is a jack-of-all-trades object.