hotwired/turbo-rails
Use Turbo in your Ruby on Rails app
Healthy across the board
Permissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 2mo ago
- ✓35+ active contributors
- ✓Distributed ownership (top contributor 31% of recent commits)
Show 3 more →Show less
- ✓MIT licensed
- ✓CI configured
- ✓Tests present
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/hotwired/turbo-rails)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/hotwired/turbo-rails on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: hotwired/turbo-rails
Generated by RepoPilot · 2026-05-10 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/hotwired/turbo-rails shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across the board
- Last commit 2mo ago
- 35+ active contributors
- Distributed ownership (top contributor 31% of recent commits)
- MIT licensed
- CI configured
- Tests present
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live hotwired/turbo-rails
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/hotwired/turbo-rails.
What it runs against: a local clone of hotwired/turbo-rails — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in hotwired/turbo-rails | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch main exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 104 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of hotwired/turbo-rails. If you don't
# have one yet, run these first:
#
# git clone https://github.com/hotwired/turbo-rails.git
# cd turbo-rails
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of hotwired/turbo-rails and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "hotwired/turbo-rails(\\.git)?\\b" \\
&& ok "origin remote is hotwired/turbo-rails" \\
|| miss "origin remote is not hotwired/turbo-rails (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
&& ok "default branch main exists" \\
|| miss "default branch main no longer exists"
# 4. Critical files exist
test -f "lib/turbo-rails.rb" \\
&& ok "lib/turbo-rails.rb" \\
|| miss "missing critical file: lib/turbo-rails.rb"
test -f "lib/turbo/engine.rb" \\
&& ok "lib/turbo/engine.rb" \\
|| miss "missing critical file: lib/turbo/engine.rb"
test -f "app/javascript/turbo/index.js" \\
&& ok "app/javascript/turbo/index.js" \\
|| miss "missing critical file: app/javascript/turbo/index.js"
test -f "app/models/concerns/turbo/broadcastable.rb" \\
&& ok "app/models/concerns/turbo/broadcastable.rb" \\
|| miss "missing critical file: app/models/concerns/turbo/broadcastable.rb"
test -f "app/channels/turbo/streams_channel.rb" \\
&& ok "app/channels/turbo/streams_channel.rb" \\
|| miss "missing critical file: app/channels/turbo/streams_channel.rb"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 104 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~74d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/hotwired/turbo-rails"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
turbo-rails is a Ruby gem that integrates Turbo (a JavaScript framework for fast web navigation) into Rails applications, enabling single-page-app-like performance without custom JavaScript. It provides server-side helpers, ActiveCable integration, and Turbo Frames/Streams support to deliver partial HTML updates, lazy-loaded page segments, and real-time broadcasting—replacing the need for traditional full-page reloads or heavy client-side frameworks. Modular Rails mountable: core is split into app/channels (ActionCable WebSocket integration), app/controllers/turbo (Frame/Stream request handling), app/helpers/turbo (view layer DSL), app/jobs/turbo/streams (background broadcasting), and app/models/concerns/turbo/broadcastable.rb (model-level broadcast mixin). JavaScript bridge in app/javascript/turbo wraps the core @hotwired/turbo package with Rails-specific features (cable integration, fetch interception).
👥Who it's for
Rails developers building interactive web applications who want fast, responsive UIs with minimal JavaScript; specifically those targeting both web and hybrid native apps (iOS/Android) that can reuse the same HTML-based views through Turbo's navigation framework.
🌱Maturity & risk
Production-ready and actively maintained. The gem is at v8.0.23 with a comprehensive test suite (CI via GitHub Actions in .github/workflows/ci.yml), clear UPGRADING.md documentation, and regular dependency updates via Dependabot. This is a core Basecamp/Hotwire project with multi-year stability.
Standard open source risks apply.
Active areas of work
The project is in steady maintenance mode at v8.0.23. Visible from file structure: focus areas are Turbo Streams maturity (three job types for broadcasting), ActionCable integration (streams_channel, cable_stream_source_element.js), and native app support (turbo/native controllers). Dependabot is active (.github/dependabot.yml), indicating regular dependency updates.
🚀Get running
git clone https://github.com/hotwired/turbo-rails.git
cd turbo-rails
bundle install
bundle exec rake
Then explore Rakefile for test targets. The gem itself is a Rails engine—install into a Rails app via Gemfile to develop against it.
Daily commands:
For development: bundle exec rake test runs the test suite. To use in a Rails app: add gem 'turbo-rails' to Gemfile, then bundle install && rails turbo:install. The gem includes pre-built JS assets (turbo.js, turbo.min.js) and ES modules in app/javascript/turbo for importmap or bundler setups.
🗺️Map of the codebase
lib/turbo-rails.rb— Main gem entry point that loads the Rails engine and core turbo functionality; every contributor must understand how the gem initializes.lib/turbo/engine.rb— Rails engine configuration that registers routes, helpers, and asset pipelines; essential for understanding how Turbo integrates with Rails.app/javascript/turbo/index.js— Client-side JavaScript entry point that exports core Turbo APIs (Drive, Frames, Streams); defines the public JS interface.app/models/concerns/turbo/broadcastable.rb— Core mixin enabling real-time partial updates via ActionCable; foundational pattern for server-to-client streaming.app/channels/turbo/streams_channel.rb— ActionCable channel handling Turbo Stream subscriptions; critical for real-time broadcast functionality.app/helpers/turbo/frames_helper.rb— View helpers for rendering Turbo Frames; heavily used in templates and essential for frame-based navigation.app/helpers/turbo/streams_helper.rb— View helpers for rendering Turbo Streams (replace, append, prepend); core template API for partial updates.
🛠️How to make changes
Add a new Turbo Stream action
- Define the action method in TagBuilder class to generate the <turbo-stream> tag with custom action attribute (
app/models/turbo/streams/tag_builder.rb) - Add corresponding JavaScript handler in Turbo client-side code to process the new action (would be in compiled bundle from app/javascript/turbo/) (
app/javascript/turbo/index.js) - Create a view helper method in streams_helper.rb to make the action ergonomic in templates (
app/helpers/turbo/streams_helper.rb) - Write integration tests validating the action renders and broadcasts correctly (
test/broadcastable/test_helper_test.rb)
Add a new Turbo Frame helper or behavior
- Add the helper method to frames_helper.rb (e.g., conditional frame rendering, lazy-load attributes) (
app/helpers/turbo/frames_helper.rb) - Update the frame controller concern if logic requires server-side frame detection or response modification (
app/controllers/turbo/frames/frame_request.rb) - Create integration tests using the dummy app to verify frame behavior (system tests in test/dummy/) (
test/app_helper.rb)
Add support for broadcasting to a new model callback (e.g., on_destroy)
- Extend the Broadcastable concern with a new callback hook (e.g., broadcasts_on :destroy) (
app/models/concerns/turbo/broadcastable.rb) - If async job is needed, create or modify a broadcast job in app/jobs/turbo/streams/ to handle the callback (
app/jobs/turbo/streams/broadcast_job.rb) - Document the callback in test helpers and create test cases in broadcastable test file (
test/broadcastable/test_helper_test.rb)
Integrate Turbo with a custom build tool (webpack, esbuild, etc.)
- Create or modify an install class in lib/install/ to configure the build tool with Turbo entry points (
lib/install/turbo_with_importmap.rb) - Update the engine to conditionally load JavaScript based on the build tool configuration (
lib/turbo/engine.rb) - Add a corresponding rake task in lib/tasks/turbo_tasks.rake to guide installation (
lib/tasks/turbo_tasks.rake)
🔧Why these technologies
- Rails Engine (lib/turbo/engine.rb) — Allows Turbo to be packaged as a gem with automatic asset registration, view helpers, and route mounting without modifying host app code.
- ActionCable (app/channels/turbo/streams_channel.rb) — WebSocket infrastructure for real-time partial updates; avoids polling and enables low-latency server-to-browser communication.
- ES6 modules + Rollup (app/javascript/turbo/, rollup.config.js) — Modern JS bundling strategy allowing flexible consumption: importable as npm module OR vendored as prebuilt bundle for asset pipeline.
- ERB view helpers (app/helpers/turbo/) — Keeps Turbo syntax (turbo_frame_tag, turbo_stream.replace) in familiar Ruby context; maintains Rails idiom of 'convention
🪤Traps & gotchas
- Frame responses must omit layouts: If a controller responds to a Turbo Frame request with a full layout, the frame will render incorrectly. Use
render layout: falseor rely onFrameRequestdetection. 2. ActionCable must be configured: Streams/broadcasts require a working ActionCable setup (Redis or other adapter); in-memory adapters won't broadcast to multiple processes. 3. CSRF tokens in forms: Turbo intercepts form submissions—ensure Rails CSRF middleware andcsrf_meta_tagsare present in layouts. 4. Native app navigation: If targeting iOS/Android via Turbo Native, response codes and redirect behavior differ (seeapp/controllers/turbo/native/navigation_controller.rb); 404s may trigger native transitions instead of errors. 5. JavaScript bundling: If using importmap, ensure@hotwired/turboand@rails/actioncableare in your pin file; otherwise useyarn add @hotwired/turbo-rails.
🏗️Architecture
💡Concepts to learn
- Turbo Drive (History API + Fetch) — The foundation of turbo-rails—it intercepts link/form navigation, fetches HTML via fetch, and replaces the DOM without full page reloads, enabling SPA-like speed without client-side routing.
- Turbo Frames (DOM Islands) — Allows you to carve a Rails page into independent, replaceable segments that can lazy-load and respond independently; critical for modular UIs without building separate API endpoints.
- Turbo Streams (Server-Sent DOM Morphing) — A set of HTML tags (
<turbo-stream action="replace">, etc.) that describe atomic DOM changes; allows the server to push real-time updates without JavaScript, replacing traditional WebSocket message handlers. - ActionCable (Rails WebSocket Framework) — turbo-rails broadcasts use ActionCable channels to deliver Turbo Streams over WebSocket; understanding channel subscriptions and authentication is essential for real-time features.
- MORPHDOM (Morphing Algorithm) — The JavaScript algorithm used by Turbo to merge new HTML into the DOM (replacing vs. patching nodes intelligently); affects JavaScript state preservation and form input persistence.
- Hybrid Native Navigation (Redirect vs. Presentation) — iOS/Android Turbo apps interpret HTTP responses differently than web (302 redirects trigger native navigation instead of web history); turbo-rails' native controller layer handles this distinction.
- Rails Mountable Engines — turbo-rails is a Rails engine (not a standalone gem) with its own routes, controllers, and views; understanding Rails engine conventions is necessary to extend or debug the gem.
🔗Related repos
hotwired/turbo— The core JavaScript framework that turbo-rails wraps; understanding its fetch interception and DOM morphing is essential for debugging client-side behavior.rails/rails— turbo-rails is a Rails engine that depends on ActionCable, ActionView helpers, and ActiveModel concerns; Rails internals (especially request/response cycle) are fundamental.hotwired/stimulus— Companion framework for the remaining 10% of custom JavaScript; turbo-rails docs recommend Stimulus for interactive components that don't fit the Streams/Frames model.hotwired/turbo-android— Android half of hybrid native apps using Turbo—shares the same HTML views and navigation concepts as turbo-rails; helps understand native app integration.hotwired/turbo-ios— iOS half of hybrid native apps using Turbo; turbo-rails' native response handling (app/controllers/turbo/native/) is designed to work with this.
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive test suite for app/models/turbo/streams/tag_builder.rb
The TagBuilder class is critical for generating Turbo Streams HTML/markup, but there's no evidence of dedicated unit tests in the repo structure. This class handles multiple stream actions (replace, update, remove, etc.) and edge cases around DOM targeting that deserve thorough test coverage. New contributors can add tests for each action type, error handling, and HTML escaping scenarios.
- [ ] Create test/models/turbo/streams/tag_builder_test.rb
- [ ] Add tests for each stream action method (replace, update, remove, before, after, prepend, append)
- [ ] Add tests for DOM target validation and error cases
- [ ] Add tests for HTML escaping and XSS prevention in tag generation
- [ ] Verify tests pass with
bin/test
Add integration tests for app/controllers/turbo/streams/turbo_streams_tag_builder.rb
The TurboStreamsTagBuilder controller concern is responsible for rendering partial templates as Turbo Stream responses, but there are no visible integration tests. This is a high-risk area where malformed templates or missing partials could break user applications. Tests should cover rendering multiple actions, handling missing partials, and various content types.
- [ ] Create test/controllers/turbo/streams/turbo_streams_tag_builder_test.rb
- [ ] Add integration test for rendering single and multiple stream actions
- [ ] Add tests for error handling when partial templates don't exist
- [ ] Add tests for rendering with different content types and encodings
- [ ] Test interaction with the request_id_tracking concern
Add unit tests for app/javascript/turbo/cable_stream_source_element.js
This JavaScript file handles ActionCable stream subscriptions for Turbo Streams but appears to have no corresponding test file. This is critical for real-time features. A new contributor can add Jest/node tests covering subscription lifecycle, message handling, and error scenarios, improving reliability of the streaming functionality.
- [ ] Create test/javascript/turbo/cable_stream_source_element_test.js or equivalent
- [ ] Add tests for element mounting/unmounting and subscription lifecycle
- [ ] Add tests for message parsing and dispatch to DOM elements
- [ ] Add tests for error handling and reconnection scenarios
- [ ] Add tests for proper cleanup to prevent memory leaks
- [ ] Integrate into CI workflow (update .github/workflows/ci.yml if needed)
🌿Good first issues
- Add missing tests for edge cases in
app/controllers/turbo/frames/frame_request.rb—currently only basic frame detection is covered; add tests for nested frames, non-standard Turbo-Frame-Id headers, and missing DOM targets.: small: Frame detection is critical for correct partial rendering; robustness here prevents silent failures. - Document the full lifecycle of a broadcast in
UPGRADING.mdor add inline examples toapp/jobs/turbo/streams/broadcast_stream_job.rbandaction_broadcast_job.rbshowing the difference between broadcasting to a stream vs. broadcasting an action—many users confuse when to use each.: small: The two broadcast job classes solve different problems (all users vs. specific actions) but are poorly documented. - Add helper method to
app/helpers/turbo/streams_helper.rbfor conditional stream rendering (e.g.,turbo_stream_if) to avoid boilerplate<% if condition %>wrapping around stream tags in views.: medium: Common pattern in Stream responses; a helper would reduce view clutter and improve readability.
⭐Top contributors
Click to expand
Top contributors
- @jorgemanrubia — 31 commits
- @seanpdoyle — 14 commits
- @dhh — 8 commits
- @brunoprietog — 6 commits
- @packagethief — 4 commits
📝Recent commits
Click to expand
Recent commits
435135b— Bump version (packagethief)22701f1— @hotwired/turbo-rails v8.0.23 (packagethief)27030b9— Bump version (packagethief)52cde05— @hotwired/turbo-rails v8.0.22 (packagethief)e511fb2— Bump version (jorgemanrubia)99dc9c5— @hotwired/turbo-rails v8.0.20 (jorgemanrubia)c2cd99f— v8.0.21 (jorgemanrubia)31c78af— Respect broadcast suppressions on before/after actions (#770) (stowersjoshua)16f7613— Only return messages produced by block incapture_turbo_stream_broadcasts(Vivalldi)dda27a8— Restrict tests tominitest<6(seanpdoyle)
🔒Security observations
The turbo-rails codebase demonstrates generally good security practices as a Rails framework integration, with proper separation of concerns and use of Rails' built-in security features. However, there are several areas requiring attention: (1) outdated build dependencies that should be updated to address potential vulnerabilities, (2) XSS risks in Turbo Streams HTML rendering that require careful input sanitization, (3) WebSocket channel authorization gaps that could lead to information disclosure, and (4) missing explicit security header configuration. The main concerns are centered on proper input handling in the streaming mechanisms and authorization controls for real-time channels. The codebase would benefit from comprehensive security header configuration and explicit authorization auditing across all broadcast and channel functionality.
- Medium · Outdated Rollup Dependencies —
package.json - devDependencies. The package.json contains outdated Rollup build dependencies (@rollup/plugin-node-resolve@^11.0.1 and rollup@^2.35.1). These versions are from 2020-2021 and may contain known security vulnerabilities. Modern versions include important security patches. Fix: Update to latest stable versions: @rollup/plugin-node-resolve to ^15.x or latest, and rollup to ^4.x or latest. Run 'npm audit' to identify specific vulnerabilities and apply patches. - Medium · Potential XSS in Turbo Streams HTML Rendering —
app/helpers/turbo/streams/, app/models/turbo/streams/tag_builder.rb, app/controllers/turbo/streams/turbo_streams_tag_builder.rb. The codebase includes turbo/streams functionality that manipulates DOM and renders HTML content. Files like app/helpers/turbo/streams/action_helper.rb and app/models/turbo/streams/tag_builder.rb handle HTML generation. If user input is not properly sanitized before being rendered in Turbo Streams, it could lead to XSS attacks. Fix: Ensure all user-supplied input rendered in Turbo Streams is properly sanitized using Rails' built-in sanitization helpers (sanitize, h, etc.). Implement Content Security Policy (CSP) headers. Review stream action implementations for proper escaping. - Medium · Channel Subscription Authorization Gaps —
app/channels/turbo/streams_channel.rb. The app/channels/turbo/streams_channel.rb handles WebSocket subscriptions for Turbo Streams. Without proper authorization checks, users might subscribe to channels containing sensitive data they shouldn't access. Fix: Implement explicit authorization checks in the subscribe method. Verify that the user has permission to access the specific stream being subscribed to. Use ActionCable's built-in authorization mechanisms consistently across all stream channels. - Low · Missing Security Headers Configuration —
config/routes.rb, config/ (not fully visible). The codebase structure doesn't show explicit security header configuration files (no mention of security.txt, security headers middleware, or CORS configuration). Turbo modifies the DOM and handles navigation, which requires proper CORS and CSP headers. Fix: Configure security headers in the Rails application: Content-Security-Policy, X-Content-Type-Options, X-Frame-Options, X-XSS-Protection. Consider using gems like 'secure_headers' for consistent header management. - Low · Broadcast Job Information Disclosure —
app/jobs/turbo/streams/. The broadcast job implementation (app/jobs/turbo/streams/action_broadcast_job.rb, broadcast_job.rb) may inadvertently log or expose sensitive data during broadcasting. Job arguments are often logged by default in Rails. Fix: Ensure sensitive data is not passed as job arguments. Use job serialization filters if needed. Review logging configuration to prevent sensitive information from being logged with job details. Implement proper access controls on broadcast channels. - Low · No CSRF Protection Visible for Stream Actions —
app/channels/turbo/streams_channel.rb, app/models/concerns/turbo/broadcastable.rb. Turbo Streams can trigger state changes via WebSocket channels. If CSRF protection isn't properly enforced at the channel subscription level, unauthorized stream actions could be performed. Fix: Ensure ActionCable connections verify the CSRF token. Implement explicit authorization in channel subscriptions. Consider using request-level CSRF tokens for sensitive stream operations.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.