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