This short post is intended as a how-to get up and running with StimulusReflex and the new ~importmap-rails~ gem to go all transpiler/bundler-less in your asset handling. Since not all generators in StimulusReflex have been updated to reflect this, I will keep this post updated ✌.

For reference, here is the link to the Github repository with the source code created in this walkthrough.

If you have any questions meanwhile, don’t hesitate to jump on the Discord server 💚.

Prepare Rails

Since Rails 7.0, importmaps, along with Hotwire aka Turbo and Stimulus, is the default frontend stack. Before starting out, we’ll make sure that we’re running the correct Rails version.

$ rails -v
Rails 7.0.0
$ rails new stimulus_reflex_importmaps
$ cd stimulus_reflex_importmaps

This will install and configure turbo-rails, stimulus-rails, and importmap-rails, which are the default starting with Rails 7.

Note: If you start from an existing app (e.g. Rails 6.1), you will need to add and install those yourself:

$ bundle add importmap-rails turbo-rails stimulus-rails
$ bin/rails importmap:install
$ bin/rails turbo:install
$ bin/rails stimulus:install

Set up StimulusReflex

Next, we’re going to add the StimulusReflex gem with using the most recent prerelease.

$ bundle add stimulus_reflex -v 3.5.0.pre8

Since the StimulusReflex installer hasn’t been updated to reflect the latest changes in Rails, we’ll have to do a couple of chores ourselves.

1. Enable Caching

StimulusReflex advises to use the cache store for session persistence, so we’ll configure that in development.rb:

# config/environments/development.rb
Rails.application.configure do
  # ...
  config.session_store :cache_store
  # ...

And enable it:

$ bin/rails dev:cache

2. Appease the StimulusReflex Sanity Checker

By default, StimulusReflex’s sanity checker will prevent your Rails process from starting if the Ruby and Javascript versions are not congruent. While the behavior of the checker is reworked at the moment, we will have to work around it for now.

To this end, set up a StimulusReflex initializer to bypass it:

$ bin/rails generate stimulus_reflex:initializer
# config/initializers/stimulus_reflex.rb

StimulusReflex.configure do |config|
  config.on_failed_sanity_checks = :warn

3. Pin the Required Packages

For the importmap approach, we need to use the bin/importmap tool to pin the StimulusReflex javascript package as follows:

$ bin/importmap pin stimulus_reflex@3.5.0-pre8

Pinning "stimulus_reflex" to
Pinning "@hotwired/stimulus" to
Pinning "@rails/actioncable" to
Pinning "cable_ready" to
Pinning "morphdom" to
Pinning "stimulus" to

Note that due to the namespace change that Hotwire has undergone, we have now pinned two separate Stimulus packages (@hotwired/stimulus and stimulus). StimulusReflex still references the legacy glue package stimulus, the work to transfer everything cleanly to @hotwired/stimulus is still in progress. Since we hand off the application to StimulusReflex, it shouldn’t be a problem though (see below).

4. Initialize the StimulusReflex Client

We use the ActionCable consumer obtained from Turbo’s cable interface to intialize StimulusReflex with a top level await:

// app/javascript/controllers/index.js

import StimulusReflex from "stimulus_reflex"; // <-- add this
import { application } from "./application";
import { cable } from "@hotwired/turbo-rails"; // <-- add this

// Eager load all controllers defined in the import map under controllers/**/*_controller
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading";
eagerLoadControllersFrom("controllers", application);

// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
// lazyLoadControllersFrom("controllers", application)

// initialize StimulusReflex w/top-level await
const consumer = await cable.getConsumer()
StimulusReflex.initialize(application, { consumer, debug: true });

Test Everything with a CounterReflex

For the rest of this walkthrough, I follow the official Quickstart Guide closely (for any in-depth explanation of what is going on here, please look there). Let’s first add a PagesController with only an index route:

$ bin/rails g controller Pages index
# config/routes.rb

Rails.application.routes.draw do
  resources :pages, only: :index
# app/controllers/pages_controller.rb

class PagesController < ApplicationController
  def index

Then, we’ll generate a CounterReflex with an increment action and use it to increase a @count index variable derived from a data-count attribute on the invoking element:

$ bin/rails g stimulus_reflex Counter increment
# app/reflexes/counter_reflex.rb

class CounterReflex < ApplicationReflex
  def increment
    @count = element.dataset[:count].to_i + element.dataset[:step].to_i

In the index view, we’ll furnish a simple <a> tag with a data-reflex attribute that invokes this increment action on click:

<!-- app/views/pages/index.html.erb -->
<a href="#"
   data-count="<%= @count.to_i %>">
  Increment <%= @count.to_i %>

Now it’s time to start Rails and kick the tires:

$ bin/rails s


Success! 🎉

Testing Client-Side Invocation

Now that we’ve established that the declarative way works, let’s double check that imperative client-side invocation functions, too. Again, nothing new here, refer to the relevant section in the Quick Start Guide.

Instead of a data-reflex, we are going to reference the Stimulus controller directly, and hook up a data-action:

<!-- app/views/pages/index.html.erb -->
<a href="#"
  Increment <%= @count %>

Remember that this is now dependent on our Stimulus counter_controller, so we have some work to do there:

// app/javascript/controllers/counter_controller.js

import ApplicationController from './application_controller'

export default class extends ApplicationController {
  connect () {

  increment() {
    this.stimulate('Counter#increment', 1)

To stay in sync with the Quick Start guide, let’s switch to session storage and passing the step via an argument:

# app/reflexes/counter_reflex.rb

class CounterReflex < ApplicationReflex
  def increment(step = 1)
    session[:count] = session[:count].to_i + step

We pick this up in the controller again, to render a new @count:

# app/controllers/pages_controller.rb

class PagesController < ApplicationController
  def index
    @count = session[:count].to_i

Voilà 🥂


We’ve established a way to get StimulusReflex up and running with importmaps. The tooling isn’t there yet, but this post essentially lays out the steps that have to be taken to patch it. I will keep updating it as the installers and generators evolve to become importmap-rails compliant.