Introduction
Turbo Streams is the primary way Rails 8.1 applications deliver real-time DOM updates. When combined with Action Cable, Turbo Streams let you broadcast HTML fragments that automatically update the page without writing any custom JavaScript. This is the most Rails-idiomatic way to build real-time features.
Key Concepts
- Turbo Streams: An HTML-over-the-wire approach where the server sends small HTML fragments with instructions (append, prepend, replace, remove, update) that Turbo applies to the DOM.
turbo_stream_from: A view helper that creates a Turbo Streams subscription to an Action Cable channel, connecting DOM updates to a model's broadcasts.broadcast_append_to: A model method that broadcasts a Turbo Stream "append" action, adding HTML to a specified container.broadcasts_to: A model-level declaration that automatically broadcasts Turbo Stream updates on create, update, and destroy.
Real World Context
Instead of writing JavaScript to receive a JSON payload, parse it, and manually insert HTML into the DOM, Turbo Streams let the server send ready-to-render HTML. When a new comment is posted, the server broadcasts a Turbo Stream that appends the rendered comment partial to the comments list. Every connected user sees the new comment appear — zero client-side JavaScript required.
Deep Dive
First, set up the subscription in your view:
erb<%# app/views/rooms/show.html.erb %> <%= turbo_stream_from @room %> <div id="messages"> <%= render @room.messages %> </div> <%= form_with(model: [@room, Message.new]) do |f| %> <%= f.text_field :body %> <%= f.submit "Send" %> <% end %>
The turbo_stream_from @room helper generates a <turbo-cable-stream-source> element that subscribes to Action Cable and applies incoming Turbo Stream actions.
In the model, declare automatic broadcasts:
ruby# app/models/message.rb class Message < ApplicationRecord belongs_to :room belongs_to :user broadcasts_to :room end
The broadcasts_to :room declaration automatically broadcasts Turbo Stream updates when a message is created, updated, or destroyed. It renders the _message.html.erb partial and sends the HTML to all subscribers.
For more control, use explicit broadcast methods in callbacks:
rubyclass Message < ApplicationRecord belongs_to :room belongs_to :user after_create_commit -> { broadcast_append_to room, target: "messages", partial: "messages/message", locals: { message: self } } after_update_commit -> { broadcast_replace_to room, target: self, partial: "messages/message", locals: { message: self } } after_destroy_commit -> { broadcast_remove_to room, target: self } end
The available Turbo Stream actions are:
broadcast_append_to— Adds content at the end of a container.broadcast_prepend_to— Adds content at the beginning of a container.broadcast_replace_to— Replaces an existing element entirely.broadcast_update_to— Updates the content inside an existing element.broadcast_remove_to— Removes an element from the DOM.
Each method specifies a target (the DOM id) and a partial (the HTML to render).
Common Pitfalls
- Missing DOM target id — Turbo Streams target elements by their
idattribute. If the target id does not exist in the DOM, the stream action silently fails. Always ensure your container has the correctid. - Using
broadcasts_towithout a partial — The automatic broadcast renders_model_name.html.erbby convention. If this partial does not exist, broadcasts will fail with a missing template error. - Broadcasting in
after_createinstead ofafter_create_commit— Turbo Stream broadcasts must fire after the transaction commits. Use_commitcallbacks to ensure data consistency.
Best Practices
- Start with
broadcasts_tofor simple CRUD — It handles create, update, and destroy automatically with zero configuration. Use explicit callbacks only when you need custom targets or conditions. - Use
dom_idfor target ids — Rails'dom_idhelper generates consistent ids likemessage_42. Use it in both your partials and broadcast calls to avoid mismatches. - Keep partials self-contained — The partial rendered in a broadcast must work without the parent view's instance variables. Pass everything it needs via
locals.
Summary
- Turbo Streams deliver real-time DOM updates by broadcasting HTML fragments over Action Cable.
turbo_stream_from @modelin a view subscribes to broadcasts for that model.broadcasts_to :associationautomatically broadcasts on create, update, and destroy.- Explicit methods (
broadcast_append_to,broadcast_replace_to, etc.) give fine-grained control over what is sent and where it appears. - Always ensure target DOM ids exist and use
after_create_commitcallbacks for data consistency.
Code Examples
class Message < ApplicationRecord
belongs_to :room
belongs_to :user
# Automatically broadcast create/update/destroy as Turbo Streams
broadcasts_to :room
end