A conventional application usually stores the current state of a record. A subscription is active or cancelled; an order is paid or refunded; a customer has a particular account balance. The database tells us what is true now, but it may not tell us exactly how the application arrived there.
Event sourcing approaches the problem differently. Instead of treating the latest value as the only important record, it preserves the sequence of business events that produced it: a subscription began, a payment succeeded, a plan changed, and eventually the subscription was cancelled.
Verbs is an open-source Laravel package built around that approach. Created by Thunk, it aims to make event sourcing feel approachable to PHP developers by organizing application behavior around events, state, and the actions that follow.
The idea: record what happened
Consider a subscription service. In a conventional design, the application might update a subscriptions table whenever a customer changes plans. The row contains the current plan, renewal date, and status. Unless the team has built a separate history, earlier values may be overwritten.
An event-sourced design can preserve a sequence such as TrialStarted, SubscriptionActivated, PlanChanged, and SubscriptionCancelled. Each event describes something that happened. The application derives the current state from those events and can maintain a separate representation for the screens or queries it needs.
This is useful when the sequence itself matters. A support agent may need to understand why a subscription is inactive. A business rule may depend on whether a customer has already used a trial. A team may want to build a new report from events that were recorded before the report existed.
None of those benefits comes automatically from installing a package. The events must contain the right information, the application must protect their integrity, and the team must decide how to handle mistakes, sensitive data, and changes to event structure over time.
What Verbs provides
Verbs gives a Laravel application a structure for defining and firing events, associating those events with state, validating business rules, and handling the effects of committed events. Its terminology is intentionally close to the actions a business recognizes: a customer began a trial or a player joined a game.
In the project’s model, events describe what happened, while states hold information affected by those events. A state can be used to determine whether a new event is allowed. Event handlers can then update models or other data used by the application.
The distinction matters: a Laravel model used to display a subscription is not necessarily the authoritative history. It can be a convenient representation derived from events. The Verbs documentation describes this flow as events affecting states first and models afterward.
Getting started
The project’s quickstart lists PHP 8.1 or later and Laravel 10 or later as its baseline requirements. Check the current package constraints against your own Laravel and PHP versions before installation.
Install the package with Composer, publish its migrations, and run them:
composer require hirethunk/verbs
php artisan vendor:publish --tag=verbs-migrations
php artisan migrateVerbs also includes Artisan commands for generating events and states. For example:
php artisan verbs:event CustomerBeganTrial
php artisan verbs:state CustomerStateThese commands create starting points; they do not determine the rules of the business. The developer still needs to decide what information an event must carry, which state it affects, and which conditions allow it to happen.
A trial subscription as an event
The official quickstart uses a trial subscription to explain the package. An event called CustomerBeganTrial carries a customer identifier. A CustomerState can store information about when the trial began, while an attribute associates the identifier with that state.
Conceptually, the relevant event properties look like this:
use App\States\CustomerState;
use Thunk\Verbs\Attributes\StateId;
use Thunk\Verbs\Event;
class CustomerBeganTrial extends Event
{
#[StateId(CustomerState::class)]
public int $customer_id;
}The event can then be fired with a named argument:
CustomerBeganTrial::fire(customer_id: 1);This small example illustrates the entry point, not a complete subscription implementation. In a real application, the event could validate a rule against the customer’s state and then apply the change to that state. The project’s quickstart expands the example to cover validation and state updates.
Also consider identifier design early. The documentation uses a simple integer in its introductory example while noting that event sourcing across an application requires unique identifiers. A production design should follow the package’s identifier guidance and account for how identifiers are generated across the system.
Validation, application, and handling
One useful feature of Verbs is its explicit event lifecycle. When an event is first fired, the package can check authorization and validation before applying the event to state. The state changes before the event is committed, making the updated state available within the event workflow.
After the event has been committed, a handler can perform work such as updating an Eloquent model. For the subscription example, that model might provide the efficient current view needed by an account page. The event history and the model serve related but different purposes: one records business changes, while the other supports a particular way of reading them.
Verbs normally commits queued events at the end of a Laravel request, console command, or queued job. Its documentation also describes manually calling Verbs::commit(), including when events must be committed inside a database transaction alongside other database work. This timing is worth understanding before combining Verbs with an existing application’s transaction boundaries.
Replay is powerful—and requires care
Event history can make it possible to rebuild application state or representations from past events. That possibility is one of the attractions of event sourcing: if the team introduces a new way to view data, it may be able to derive it from events already recorded.
Replay also raises an operational question: what happens to side effects? Rebuilding a subscription table is one thing; sending the customer another welcome email or charging a payment method again is quite another.
According to the event lifecycle documentation, Verbs calls both application and handling hooks during replay. The package provides a Once attribute and a Verbs::unlessReplaying helper for cases where handling work should not run again. Developers still need to identify side effects carefully and test replay behavior before relying on it.
External services introduce additional failure scenarios. A database transaction and an email provider do not automatically share the same guarantees. For important integrations, consider how the application will detect retries, prevent duplicate effects, and recover when a service is unavailable.
Where Verbs could fit
Verbs deserves a closer look when the history of a business process is as important as its latest state. Examples include subscriptions with meaningful lifecycle changes, booking workflows, inventory movements, or approval processes where the sequence of decisions matters.
It may also suit a Laravel team that wants to explore event sourcing in one bounded part of an application without immediately redesigning the entire system. A subscription lifecycle, for example, could be evaluated separately from a conventional content-management feature.
The package is Laravel-oriented. Teams using another PHP framework should assess whether its integrations and conventions fit their environment rather than assume it is a framework-independent event store.
When a conventional model may be enough
Event sourcing adds concepts and responsibilities. Teams need to design events, preserve compatibility with historical data, test reconstruction, manage projections, and decide how to correct mistakes without casually rewriting history.
If an application only needs the current value of a setting and an occasional audit entry, a normal database table with a well-designed change log may be easier to build and maintain. Event sourcing is a broader architectural choice, not simply a more detailed audit log.
Privacy also needs attention. Recording every change indefinitely can conflict with data-retention and deletion requirements. Before storing personal information in events, determine what must be retained, who can access it, and how the design will accommodate applicable obligations.
What to evaluate before adoption
Start with a small proof of concept based on an actual business workflow. Define a few events, write the validation rules, and build the current view the application needs. Then test the situations that are easy to overlook:
- Can the team rebuild the state and its read model from recorded events?
- What happens when two requests attempt conflicting changes?
- Are external side effects protected against replay and duplicate execution?
- How will older events remain readable when their structure changes?
- What information should never be placed in an event payload?
- Does the team understand the package’s commit and transaction behavior?
Check the project’s current release, supported dependencies, open issues, and maintenance activity before making a production commitment. As with any open-source dependency, suitability depends on the needs of the application and the team’s ability to support it.
The Product Radar verdict
Verbs makes an ambitious architectural idea more accessible to Laravel developers by giving events and state a recognizable place in the application. Its documentation and examples offer a practical entry point for teams that want to learn how a recorded sequence of business actions can shape current behavior.
Its strongest case is a domain where history and business rules justify the extra design work. For a simple application, that same structure may be unnecessary. The most useful first experiment is therefore not a complete rewrite, but a small workflow where the team can test whether the event history answers questions a conventional model cannot answer easily.
Explore the project: Official documentation · GitHub repository · Packagist package

























