putyourlightson/laravel-datastar

A reactive hypermedia framework for Laravel.

1.0.0-RC.3 2025-08-10 06:14 UTC

README

Stable Version Total Downloads

Datastar Package for Laravel

A reactive hypermedia framework for Laravel.

Warning

Updating from a previous version? View the release notes.

This package integrates the Datastar hypermedia framework with Laravel, allowing you to create reactive frontends driven by Blade views or controllers. It aims to replace the need for front-end frameworks such as React, Vue.js and Alpine.js + htmx, and instead lets you manage state and use logic from your Laravel backend.

Use-cases:

  • Live search and filtering
  • Loading more elements / Infinite scroll
  • Paginating, ordering and filtering lists
  • Submitting forms and running actions
  • Pretty much anything to do with reactive front-ends

License

This package is licensed for free under the MIT License.

Requirements

This package requires Laravel 11.0.0 or later.

Installation

Install manually using composer, then run the artisan vendor:publish --tag=public command to publish the public assets.

composer require putyourlightson/laravel-datastar:^1.0.0-RC.3

php artisan vendor:publish --tag=public

Overview

The Datastar package for Laravel allows you to handle backend requests by sending SSE events using Blade directives in views or using controllers. The former requires less setup and is more straightforward, while the latter provides more flexibility.

Here’s a trivial example that toggles some backend state using the Blade view datastar/toggle.blade.php to handle the request.

{{-- main.blade.php --}}

<div data-signals-enabled="false">
    <div data-text="$enabled ? 'ON' : 'OFF'"></div>
    <button data-on-click="{{ datastar()->view('datastar.toggle') }}">
        <span id="button-text">Enable</span>
    </button>
</div>
{{-- datastar/toggle.blade.php --}}

@php
    $enabled = $signals['enabled'] ?? false;
@endphp

@patchsignals(['enabled' => $enabled])

@patchelements
    <span id="button-text">
        {{ $enabled ? 'Disable' : 'Enable' }}
    </span>
@endpatchelements

Usage

Start by reading the Getting Started guide to learn how to use Datastar on the frontend. The Datastar package for Laravel only handles backend requests.

Note

The Datastar VSCode extension and IntelliJ plugin have autocomplete for all data-* attributes.

When working with signals, note that you can convert a PHP array into a JSON object using the json_encode function.

{{-- main.blade.php --}}

@php
    $signals = ['foo' => 1, 'bar' => 2];
@endphp

<div data-signals="{{ json_encode($signals) }}"></div>

Datastar Helper

The datastar() helper function is available in Blade views and returns a Datastar helper that can be used to generate action requests to the Datastar controller. The Datastar controller can either render a view, run a controller action, or call a route, each of which respond by sending an event stream containing zero or more SSE events.

Signals are also sent as part of the request, and are made available in Datastar views using the $signals variable.

datastar()->view()

Returns a @get() action request to render a view. The value should be a dot-separated path to a Blade view.

// Sends a `GET` request that renders a Blade view
{{ datastar()->view('path.to.view') }}

Variables can be passed in as a second argument, that will be available in the rendered view.

{warning} Variables are tamper-proof yet visible in the source code in plain text, so you should avoid passing in any sensitive data. Only primitive data types can be used as variables: strings, numbers, booleans and arrays. Objects and models cannot be used.

// Sends a `GET` request that renders a Blade view
{{ datastar()->view('path.to.view', ['foo' => 'bar']) }}

Options can be passed into the @get() action using a third argument.

// Sends a `GET` request that renders a Blade view
{{ datastar()->view('path.to.view', ['foo' => 'bar'], ['contentType' => 'form']) }}

datastar()->action()

Returns a @post() action request to run a controller action. The value should be an array with a controller class name as the first value and an action name as the second. A CSRF token is automatically generated and sent along with the request.

// Sends a `POST` request that runs a controller action
{{ datastar()->action(['MyController', 'update']) }}

Params can be passed in as a second argument, that will be available as arguments to the controller action

{warning} Params are tamper-proof yet visible in the source code in plain text, so you should avoid passing in any sensitive data. Only primitive data types can be used as params: strings, numbers, booleans and arrays. Objects and models cannot be used. Route-model binding works with controller actions.

// Sends a `POST` request that runs a controller action
{{ datastar()->action(['MyController', 'update'], ['foo' => 'bar']) }}

Options can be passed into the @get() action using a third argument.

// Sends a `POST` request that runs a controller action
{{ datastar()->action(['MyController', 'update'], ['foo' => 'bar'], ['contentType' => 'form']) }}

datastar()->get()

Returns a @get() action request that calls a route. The value must be a defined route.

// Sends a `GET` request to a route
{{ datastar()->get('/uri') }}

Options can be passed into the @get() action using a second argument.

// Sends a `GET` request to a route
{{ datastar()->get('/uri', ['contentType' => 'form']) }}

datastar()->post()

Works the same as datastar()->get() but returns a @post() action request that calls a route. A CSRF token is automatically generated and sent along with the request.

// Sends a `POST` request to a route
{{ datastar()->post('/uri') }}

datastar()->put()

Works the same as datastar()->post() but returns a @put() action request that calls a route.

// Sends a `PUT` request to a route
{{ datastar()->put('/uri') }}

datastar()->patch()

Works the same as datastar()->post() but returns a @patch() action request that calls a route.

// Sends a `PATCH` request to a route
{{ datastar()->patch('/uri') }}

datastar()->delete()

Works the same as datastar()->post() but returns a @delete() action request that calls a route.

// Sends a `DELETE` request to a route
{{ datastar()->delete('/uri') }}

Blade Directives

Datastar Blade directives can patch and remove elements, patch signals, execute JavaScript and redirect the browser.

Patch Elements

The @patchelements directive allows you to patch elements into the DOM.

{{-- main.blade.php -}}

<div id="results"></div>

<div id="search">
    <button data-on-click="{{ datastar()->get('datastar.search') }}">
        Search
    </button>
</div>
{{-- datastar/search.blade.php -}}

@patchelements
    <div id="results">
        ...
    </div>
@endpatchelements

@patchelements
    <div id="search">
        Search complete!
    </div>
@endpatchelements

This will swap the elements with the IDs results and search into the DOM. Note that elements with those IDs must already exist in the DOM, unless a mode is specified (see below).

Element Patch Options

Elements are patched into the DOM based on element IDs, by default. It’s possible to pass other modes and other element patch options in as an argument.

@patchelements(['selector' => '#list', 'mode' => 'append'])
    <li>A new list item</li>
@endpatchelements
Automatic Element Patching

Any elements output in a Datastar template (outside any @patchelements tags) will be automatically wrapped in a @patchelements directive. This makes it possible to write your views in a way that makes them more reusable.

{{-- datastar/search.blade.php -}}

<div id="results"></div>

The view above is the equivalent of writing:

{{-- datastar/search.blade.php -}}

@patchelements
    <div id="results"></div>
@endpatchelements

While automatic element patching is convenient, it is less explicit and more restrictive (since element patch options cannot be used), so should only be used when appropriate.

Remove Elements

Elements can be removed from the DOM using the @removeelements directive, which accepts a CSS selector.

@removeelements('#list')

Patch Signals

The @patchsignals directive allows you to patch signals into the frontend signals.

{{- Sets the value of the `username` signal. -}}
@patchsignals(['username' => 'johnny'])

{{- Sets multiple signal values using an array of key-value pairs. -}}
@patchsignals(['username' => 'bobby', 'success' => true])

{{- Removes the `username` signal by setting it to `null`. -}}
@patchsignals(['username' => null])

{note} Signals patches cannot be wrapped in @patchelements directives, since each patch creates a server-sent event which will conflict with the element’s contents.

Signal Patch Options

It’s possible to pass signal patch options in as a second argument.

@patchsignals(['username' => 'johnny'], ['onlyIfMissing' => true])

Executing JavaScript

The @executescript directive allows you to send JavaScript to the browser to be executed on the front-end.

@executescript
    alert('Username is valid');
@endexecutescript

Execute Script Options

It’s possible to pass execute script options in as an argument. They are applied to the <script> tag that is appended to the DOM.

@executescript(['autoRemove' => true, 'attributes' => ['defer' => true]])
    alert('Username is valid');
@endexecutescript

Redirecting

The @location directive allows you to redirect the page by updating window.location on the front-end.

@location('/guide')

Location Options

It’s possible to pass location options in as a second argument. They are applied to the <script> tag that is appended to the DOM.

@location('/guide', ['autoRemove' => true, 'attributes' => ['defer' => true]])

Using Controllers

You can send SSE events from your own controller using the sse() helper. No routes are required, as Datastar will handle routing to the controller action you specify when using the Datastar helper.

{{-- main.blade.php --}}

// Sends a `POST` request that runs a controller action
{{ datastar()->action(['MyController', 'update']) }}
namespace App\Http\Controllers;

use Illuminate\Routing\Controller;
use Symfony\Component\HttpFoundation\StreamedResponse;

class MyController extends Controller
{
    public function index(): StreamedResponse
    {
        $signals = sse()->readSignals();
        sse()->patchSignals(['enabled' => $signals['enabled'] ? false : true]);
        sse()->patchElements('
            <span id="button-text">' . ($signals['enabled'] ? 'Enable' : 'Disable') . '</span>
        ');
        
        return sse()->getEventStream();
    }
    
    public function view(): StreamedResponse
    {
        sse()->renderView('path.to.view');
        
        return sse()->getEventStream();
    }
}

Controller actions must return a StreamedResponse created using the getEventStream() method.

sse() Helper

The sse() helper provides methods to patch elements, patch signals, execute scripts, redirect the browser and render Datastar views.

patchElements()

Patches elements into the DOM. Accepts element patch options as an optional second argument.

sse()->patchElements('<div id="new-element">New element</div>');

removeElements()

Removes elements that match the provided selector from the DOM.

sse()->removeElements('#list');

patchSignals()

Patches signals into the frontend. Accepts signal patch options as an optional second argument.

sse()->patchSignals(['foo' => 1, 'bar' => 2]);

executeScript()

Executes JavaScript in the browser. Accepts execute script options as an optional second argument, which are applied to the <script> tag that is appended to the DOM.

sse()->executeScript('alert("Hello, world!")');

location()

Redirects the browser by setting the location to the provided URI. Accepts location options as an optional second argument, which are applied to the <script> tag that is appended to the DOM.

sse()->location('/guide');

renderView()

Renders a Datastar view. Accepts the view path as the first argument and an optional array of variables as the second argument. The Blade view should output Datastar directives.

sse()->renderView('datastar.toggle', ['enabled' => true]);

Signals

Signals can be accessed within views rendered by Datastar using the signals variable, which is an array of signals received by the request that is automatically injected into the template.

<input data-bind-username>
<button data-on-click="{{ datastar()->get('path.to.view') }}">
    Check
</button>
@php
    $username = $signals['username'];
@endphp

If you ever need to read the signals in a request that is not handled by the Datastar package, you can do so as follows.

@php
    $signals = sse()->readSignals();
@endphp

Note

Signal patches cannot be wrapped in @patchelements directives, since each update creates a server-sent event which will conflict with the element’s contents.

Created by PutYourLightsOn.