hotwired-laravel / developing-with-turbo-streams
Install for your project team
Run this command in your project directory to install the skill for your entire team:
mkdir -p .claude/skills/developing-with-turbo-streams && curl -L -o skill.zip "https://fastmcp.me/Skills/Download/3413" && unzip -o skill.zip -d .claude/skills/developing-with-turbo-streams && rm skill.zip
Project Skills
This skill will be saved in .claude/skills/developing-with-turbo-streams/ and checked into git. All team members will have access to it automatically.
Important: Please verify the skill by reviewing its instructions before using it.
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