AI agent skill · by @hotwired-laravel
Developing With Turbo Streams
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.
Archived 2026-10-23 · Source updated 2026-05-25 · License: MIT. Check the original repository for the latest version.
SKILL.md instructions
# 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