hotwired-laravel / developing-with-turbo-streams

Basics of developing with Turbo Streams in web applications. Activate when working on projects that utilize Turbo Streams for enhancing user experience through real-time updates, dynamic content changes, and partial page updates without full reloads.

0 views
0 installs

Skill Content

---
name: developing-with-turbo-streams
description: >-
  Develops with Turbo Streams for partial page updates and real-time broadcasting. Activates when using turbo_stream() or
  turbo_stream_view() helpers; working with stream actions like append, prepend, replace, update, remove, before, after,
  or refresh; using the Broadcasts trait, broadcastAppend, broadcastPrepend, broadcastReplace, broadcastRemove, or
  broadcastRefresh methods; listening with x-turbo::stream-from; using the TurboStream facade for handmade broadcasts;
  combining multiple streams; or when the user mentions Turbo Stream, broadcasting, real-time updates, WebSocket streams,
  or partial page changes.
---

# Turbo Streams

Turbo Streams let you change any part of the page using eight actions: `append`, `prepend`, `replace`, `update`, `remove`, `before`, `after`, and `refresh`. They work as HTTP responses (after form submissions) and as real-time broadcasts over WebSocket.

## HTTP Turbo Streams

### Detecting Turbo Stream Requests

Check if the request accepts Turbo Stream responses before returning them:

@verbatim

<code-snippet name="Detecting" lang="php">
public function store(Request $request)
{
    $post = Post::create($request->validated());

    if ($request->wantsTurboStream()) {
        return turbo_stream($post);
    }

    return redirect()->route('posts.show', $post);
}
</code-snippet>

@endverbatim

### The `turbo_stream()` Helper

@verbatim

<code-snippet name="turbo_stream helper" lang="php">
// Auto-detect action from context (uses model state: created → append, updated → replace, deleted → remove)
return turbo_stream($model);
return turbo_stream($model, 'prepend');

// Fluent builder (no arguments returns a PendingTurboStreamResponse)
return turbo_stream()->append('posts', view('posts._post', ['post' => $post]));
return turbo_stream()->prepend('posts', view('posts._post', ['post' => $post]));
return turbo_stream()->before(dom_id($post), view('posts._post', ['post' => $newPost]));
return turbo_stream()->after(dom_id($post), view('posts._post', ['post' => $newPost]));
return turbo_stream()->replace($post, view('posts._post', ['post' => $post]));
return turbo_stream()->update($post, view('posts._post', ['post' => $post]));
return turbo_stream()->remove($post);
return turbo_stream()->refresh();
</code-snippet>

@endverbatim

### Targeting Multiple Elements

Use the `*All` methods or `targets()` to target multiple elements by CSS selector:

@verbatim

<code-snippet name="Multiple targets" lang="php">
return turbo_stream()->appendAll('.comment', view('comments._comment', ['comment' => $comment]));
return turbo_stream()->replaceAll('.notification', view('notifications._notification'));
return turbo_stream()->removeAll('.old-item');
</code-snippet>

@endverbatim

### Morph Method

Use `morph()` on replace/update to morph content instead of replacing it:

@verbatim

<code-snippet name="Morph" lang="php">
return turbo_stream()->replace($post, view('posts._post', ['post' => $post]))->morph();
</code-snippet>

@endverbatim

### Combining Multiple Streams

Pass an array or collection to return multiple stream actions in one response:

@verbatim

<code-snippet name="Multiple streams" lang="php">
return turbo_stream([
    turbo_stream()->append('posts', view('posts._post', ['post' => $post])),
    turbo_stream()->update('post_count', view('posts._count', ['count' => Post::count()])),
    turbo_stream()->remove('empty_state'),
]);
</code-snippet>

@endverbatim

### Turbo Stream Views

Render a full Blade view with the Turbo Stream content type. Useful for complex multi-stream responses:

@verbatim

<code-snippet name="Stream view" lang="php">
return turbo_stream_view('posts.turbo.created', ['post' => $post]);
</code-snippet>

<code-snippet name="Stream view template" lang="blade">
{{-- resources/views/posts/turbo/created.blade.php --}}
<x-turbo::stream action="append" target="posts">
    @include('posts._post', ['post' => $post])
</x-turbo::stream>

<x-turbo::stream action="update" target="post_count">
    {{ Post::count() }} posts
</x-turbo::stream>
</code-snippet>

@endverbatim

### The Stream Blade Component

@verbatim

<code-snippet name="Blade component" lang="blade">
{{-- Single target by ID --}}
<x-turbo::stream action="append" target="messages">
    <div id="message_1">My new message!</div>
</x-turbo::stream>

{{-- Target by model (auto-generates DOM ID) --}}
<x-turbo::stream action="replace" :target="$post">
    @include('posts._post', ['post' => $post])
</x-turbo::stream>

{{-- Multiple targets by CSS selector --}}
<x-turbo::stream action="remove" targets=".notification" />
</code-snippet>

@endverbatim

## Broadcasting (Real-Time Streams)

### The `Broadcasts` Trait

Add the `Broadcasts` trait to your Eloquent model:

@verbatim

<code-snippet name="Broadcasts trait" lang="php">
use HotwiredLaravel\TurboLaravel\Models\Broadcasts;

class Post extends Model
{
    use Broadcasts;
}
</code-snippet>

@endverbatim

### Manual Broadcasting

Call broadcast methods directly on a model instance:

@verbatim

<code-snippet name="Manual broadcasting" lang="php">
$comment->broadcastAppend();
$comment->broadcastPrepend();
$comment->broadcastReplace();
$comment->broadcastUpdate();
$comment->broadcastRemove();
$comment->broadcastBefore('some_target');
$comment->broadcastAfter('some_target');
$comment->broadcastRefresh();

// Broadcast only to other users (exclude current user)
$comment->broadcastAppend()->toOthers();

// Queue the broadcast for async processing
$comment->broadcastAppend()->later();
</code-snippet>

@endverbatim

### Directed Broadcasting

Broadcast to a specific model's channel:

@verbatim

<code-snippet name="Directed broadcasting" lang="php">
// Broadcast to the post's channel instead of the comment's own channel
$comment->broadcastAppendTo($post);
$comment->broadcastPrependTo($post);
$comment->broadcastReplaceTo($post);
$comment->broadcastUpdateTo($post);
$comment->broadcastRemoveTo($post);
$comment->broadcastRefreshTo($post);
</code-snippet>

@endverbatim

### Automatic Broadcasting

Enable automatic broadcasts on model lifecycle events:

@verbatim

<code-snippet name="Auto broadcasting" lang="php">
class Comment extends Model
{
    use Broadcasts;

    // Enable auto-broadcasting (broadcasts on create, update, delete)
    protected $broadcasts = true;

    // Customize insert action (default is 'append')
    protected $broadcasts = ['insertsBy' => 'prepend'];

    // Specify which model's channel to broadcast to
    protected $broadcastsTo = 'post';

    // Or define dynamically
    public function broadcastsTo()
    {
        return $this->post;
    }
}
</code-snippet>

@endverbatim

### Page Refresh Broadcasting

Instead of granular stream actions, broadcast a page refresh signal:

@verbatim

<code-snippet name="Refresh broadcasting" lang="php">
class Post extends Model
{
    use Broadcasts;

    // Auto-broadcast page refreshes on model changes
    protected $broadcastsRefreshes = true;
}
</code-snippet>

@endverbatim

This works best with `<x-turbo::refreshes-with method="morph" scroll="preserve" />` in the layout.

### Listening for Broadcasts

Use the `<x-turbo::stream-from>` component in your Blade views to subscribe to a channel:

@verbatim

<code-snippet name="Listening" lang="blade">
{{-- Private channel (default) — requires channel auth --}}
<x-turbo::stream-from :source="$post" />

{{-- Public channel — no auth needed --}}
<x-turbo::stream-from :source="$post" type="public" />
</code-snippet>

@endverbatim

Define the channel authorization in `routes/channels.php`:

@verbatim

<code-snippet name="Channel auth" lang="php">
use App\Models\Post;

Broadcast::channel(Post::class, function ($user, Post $post) {
    return $user->belongsToTeam($post->team);
});
</code-snippet>

@endverbatim

### Handmade Broadcasts (via Facade)

Use the `TurboStream` facade for broadcasts not tied to a model:

@verbatim

<code-snippet name="Facade broadcasting" lang="php">
use HotwiredLaravel\TurboLaravel\Facades\TurboStream;

TurboStream::broadcastAppend(
    content: view('notifications._notification', ['notification' => $notification]),
    target: 'notifications',
    channel: 'general',
);

TurboStream::broadcastRemove(target: 'notification_1', channel: 'general');
TurboStream::broadcastRefresh(channel: 'general');
</code-snippet>

@endverbatim

### Broadcasting from Response Builder

Chain `broadcastTo()` on a Turbo Stream response to also broadcast it:

@verbatim

<code-snippet name="Response broadcasting" lang="php">
turbo_stream()
    ->append('posts', view('posts._post', ['post' => $post]))
    ->broadcastTo('general');
</code-snippet>

@endverbatim

### Global Broadcast Scope

Exclude the current user from all broadcasts in a request:

@verbatim

<code-snippet name="Broadcast to others" lang="php">
use HotwiredLaravel\TurboLaravel\Facades\Turbo;

// In a controller or middleware
Turbo::broadcastToOthers();

// Anywhere
Turbo::broadcastToOthers(function () {
  // Turbo Streams broadcasted here will not be delivered to the current user...
});
</code-snippet>

@endverbatim