Skip to content

Response Builder ​

The hyper() helper provides a fluent interface for building server-sent event (SSE) responses that update your user interface. Instead of returning traditional views or JSON, you return a HyperResponse instance that can update signals, patch HTML fragments, execute JavaScript, and more—all in a single response.

The hyper() Helper ​

The hyper() helper returns a HyperResponse instance that you can chain methods on to build your response:

php
public function updateCounter()
{
    return hyper()->signals(['count' => 5]);
}

Every method returns $this, allowing you to chain multiple operations:

php
public function createTodo()
{
    $data = signals()->validate([
        'title' => 'required|string|min:3|max:255',
    ]);

    $todo = Todo::create($data);

    return hyper()
        ->fragment('pages.todos', 'todo-list', [
            'todos' => Todo::all()
        ])
        ->signals(['message' => 'Todo created!', 'todoForm' => []])
        ->js("console.log('Todo created successfully')");
}

The response is automatically converted to server-sent events (SSE) when returned from your controller. Datastar receives these events in the browser and processes them to update the interface.

Updating Signals ​

signals() ​

Update one or more signals in the browser:

php
// Update multiple signals
return hyper()->signals([
    'count' => 10,
    'message' => 'Updated!',
    'isLoading' => false
]);

// Update a single signal
return hyper()->signals('count', 10);

The signals() method merges with existing signals in the browser. If a signal doesn't exist, it's created. If it exists, its value is updated.

Type Conversion

Hyper automatically converts Laravel types to JavaScript-compatible values:

php
return hyper()->signals([
    'user' => $user,  // Eloquent model → toArray()
    'posts' => $posts,  // Collection → toArray()
    'products' => Product::paginate(10),  // Paginator → toArray()
    'date' => now()  // Carbon → ISO 8601 string
]);

forget() ​

Delete one or more signals from the browser:

php
// Delete a single signal
return hyper()->forget('tempData');

// Delete multiple signals
return hyper()->forget(['tempData', 'cache', 'preview']);

// Delete all signals
return hyper()->forget();

Deleting a signal removes it completely from the frontend state. Any elements depending on that signal will stop reacting to it.

Rendering Views ​

view() ​

Render a complete Blade view and patch it into the page:

php
public function dashboard()
{
    $stats = $this->getDashboardStats();

    return hyper()->view('dashboard', compact('stats'));
}

By default, the rendered HTML replaces the entire page content. You can target specific elements with options:

php
return hyper()->view('dashboard', $data, [
    'selector' => '#main-content',
    'mode' => 'inner'
]);

Available modes:

  • outer - Replace outer HTML (default)
  • inner - Replace inner HTML
  • replace - Complete replacement
  • append - Add as last child
  • prepend - Add as first child
  • before - Insert before element
  • after - Insert after element

fragment() ​

Render a specific fragment from a Blade view:

php
public function updateTodoList()
{
    $todos = Todo::all();

    return hyper()->fragment('todos.index', 'todo-list', compact('todos'));
}

Fragments are defined in your Blade templates using the @fragment directive:

blade
@fragment('todo-list')
<div id="todo-list">
    @foreach($todos as $todo)
        <x-todo-item :todo="$todo" />
    @endforeach
</div>
@endfragment

Hyper renders only the content between @fragment and @endfragment, avoiding the overhead of compiling the entire view. The fragment is automatically targeted to the element with matching ID (#todo-list).

Explicit Targeting

Override the automatic targeting with options:

php
return hyper()->fragment('todos.index', 'todo-list', $data, [
    'selector' => '#todo-list',
    'mode' => 'append'
]);

See the Fragments section for detailed information on fragment rendering.

fragments() ​

Update multiple fragments in a single response:

php
public function refreshDashboard()
{
    return hyper()->fragments([
        [
            'view' => 'dashboard',
            'fragment' => 'stats',
            'data' => ['stats' => $this->getStats()]
        ],
        [
            'view' => 'dashboard',
            'fragment' => 'recent-activity',
            'data' => ['activities' => $this->getActivities()]
        ]
    ]);
}

Each fragment renders and patches independently. This is useful when you need to update multiple parts of your interface from different views.

DOM Manipulation ​

Hyper provides methods that map directly to Datastar's patch modes, making DOM manipulation intuitive.

append() ​

Add HTML as the last child of an element:

php
public function addNotification()
{
    $notification = view('components.notification', [
        'message' => 'New comment added'
    ])->render();

    return hyper()->append('#notifications', $notification);
}

prepend() ​

Add HTML as the first child of an element:

php
public function addTodo()
{
    $data = signals()->validate([
        'title' => 'required|string|min:3|max:255',
    ]);

    $todo = Todo::create($data);
    $html = view('components.todo-item', compact('todo'))->render();

    return hyper()->prepend('#todo-list', $html);
}

replace() ​

Replace an element completely:

php
return hyper()->replace('#user-profile', $html);

Unlike outer mode which morphs the element (preserving event listeners and state), replace completely removes the old element and inserts the new one.

inner() ​

Replace the inner HTML of an element:

php
return hyper()->inner('#stats-container', $statsHtml);

outer() ​

Replace the outer HTML with intelligent morphing:

php
return hyper()->outer('#todo-item-5', $updatedHtml);

The outer mode uses Datastar's morphing algorithm to preserve JavaScript state, event listeners, and focus when possible.

before() ​

Insert HTML before an element:

php
return hyper()->before('#submit-button', '<p class="help-text">Please review before submitting</p>');

after() ​

Insert HTML after an element:

php
return hyper()->after('#form', '<div class="success-message">Saved!</div>');

remove() ​

Remove an element from the DOM:

php
public function deleteTodo($id)
{
    Todo::destroy($id);

    return hyper()->remove("#todo-{$id}");
}

JavaScript Execution ​

js() ​

Execute JavaScript in the browser:

php
return hyper()->js("alert('Todo created!')");

Common Uses

Scroll to an element:

php
return hyper()->js("document.getElementById('new-todo').scrollIntoView({ behavior: 'smooth' })");

Focus an input:

php
return hyper()->js("document.querySelector('[name=email]').focus()");

Trigger a custom event:

php
return hyper()->js("window.dispatchEvent(new CustomEvent('todo:created', { detail: { id: {$todo->id} } }))");

Script Options

Control script attributes and behavior:

php
return hyper()->js($script, [
    'attributes' => [
        'type' => 'module',
        'defer' => true
    ],
    'autoRemove' => true  // Remove script tag after execution (default)
]);

Method Chaining ​

All methods return $this, allowing you to build complex responses:

php
public function createContact()
{
    $validated = signals()->validate([
        'name' => 'required|string',
        'email' => 'required|email|unique:contacts'
    ]);

    $contact = Contact::create($validated);

    return hyper()
        ->fragment('contacts.index', 'contact-list', [
            'contacts' => Contact::latest()->get()
        ])
        ->signals([
            'message' => 'Contact created successfully!',
            'errors' => [],
            'contactForm' => []  // Clear the form
        ])
        ->js("console.log('Contact {$contact->id} created')");
}

Order Matters

Events are sent to the browser in the order you chain them:

php
return hyper()
    ->signals(['isLoading' => true])  // 1. Show loading state
    ->fragment('results', 'search-results', $data)  // 2. Update results
    ->signals(['isLoading' => false]);  // 3. Hide loading state

Common Patterns ​

Update and Clear Form ​

php
public function store()
{
    $product = Product::create(signals()->validate([
        'name' => 'required',
        'price' => 'required|numeric'
    ]));

    return hyper()
        ->fragment('products.index', 'product-list', [
            'products' => Product::latest()->paginate(10)
        ])
        ->signals([
            'name' => '',
            'price' => '',
            'errors' => []
        ]);
}

Show Temporary Message ​

php
return hyper()
    ->signals(['message' => 'Saved!'])
    ->js("setTimeout(() => window.ds.store.signals.message = '', 3000)");

Optimistic Update with Fallback ​

php
public function toggle($id)
{
    try {
        $todo = Todo::findOrFail($id);
        $todo->update(['completed' => !$todo->completed]);

        return hyper()->signals([
            'todos.' . $id . '.completed' => $todo->completed
        ]);
    } catch (\Exception $e) {
        return hyper()
            ->signals(['error' => 'Failed to update todo'])
            ->js("console.log('Toggle failed: ' . $e->getMessage(), 'error')");
    }
}

Conditional Operations ​

Use the when() method for conditional chaining:

php
return hyper()
    ->fragment('posts.index', 'post-list', compact('posts'))
    ->when($user->isAdmin(), function ($hyper) {
        $hyper->js("console.log('Admin view loaded')")
console();
    })
    ->signals(['message' => 'Posts loaded']);

See the Conditional Logic section for more conditional patterns.

Learn More ​

Released under the MIT License.