heartcombo/devise · error · ArgumentError

:route should be true, a Symbol or a Hash

Error message

:route should be true, a Symbol or a Hash

What it means

Devise.add_module registers authentication modules (both built-ins like :validatable and custom ones you declare in initializers). Its :route option tells Devise which route group the module's controller routes attach to, and only three shapes are legal: true (routes named after the module itself), a Symbol (routes under that scope, e.g. route: :session), or a Hash mapping a scope to actions (e.g. route: { session: [] }). Anything else (a String, Array, integer, etc.) raises ArgumentError at boot.

Source

Thrown at lib/devise.rb:423

    end

    if controller = options[:controller]
      controller = (controller == true ? module_name : controller)
      CONTROLLERS[module_name] = controller
    end

    NO_INPUT << strategy if options[:no_input]

    if route = options[:route]
      case route
      when TrueClass
        key, value = module_name, []
      when Symbol
        key, value = route, []
      when Hash
        key, value = route.keys.first, route.values.flatten
      else
        raise ArgumentError, ":route should be true, a Symbol or a Hash"
      end

      URL_HELPERS[key] ||= []
      URL_HELPERS[key].concat(value)
      URL_HELPERS[key].uniq!

      ROUTES[module_name] = key
    end

    if options[:model]
      path = (options[:model] == true ? "devise/models/#{module_name}" : options[:model])
      camelized = ActiveSupport::Inflector.camelize(module_name.to_s)
      Devise::Models.send(:autoload, camelized.to_sym, path)
    end

    Devise::Mapping.add_module module_name
  end

View on GitHub (pinned to 372b295fe6)

Solutions

  1. Use a Symbol naming the shared route group: Devise.add_module :my_module, route: :session — reuse an existing route key (check Devise::URL_HELPERS keys / devise.rb's own add_module calls) if the module should piggyback on existing routes.
  2. Use true if the module needs its own route group named after the module itself: Devise.add_module :my_module, route: true.
  3. Use a Hash when you must scope to an existing group: route: { session: [] } (keys = scope, values = extra URL helper actions array).
  4. If the module needs no routes at all (pure model concern, like :validatable), omit the :route option entirely.
  5. Restart the app after fixing; this raises during initializer load so a stale spring/bootsnap process can mask the fix (bin/spring stop).

Example fix

# before (config/initializers/devise.rb or custom gem initializer)
Devise.add_module(:two_factor, model: "devise/two_factor", route: "sessions") # String -> ArgumentError

# after — reuse the session route group via Symbol
Devise.add_module(:two_factor, model: "devise/two_factor", route: :session)

# after — own route group via true
Devise.add_module(:two_factor, model: "devise/two_factor", route: true)

# after — hash form, scope with no extra url helpers
Devise.add_module(:two_factor, model: "devise/two_factor", route: { session: [] })
Defensive patterns

Strategy: validation

Validate before calling

# Validate the :route option shape before registering a custom module:
route = :session # or true / { session: [] }
unless route == true || route.is_a?(Symbol) || route.is_a?(Hash)
  raise ArgumentError, "devise :route must be true, a Symbol, or a Hash (got #{route.class})"
end
Devise.add_module(:two_factor, model: "devise/two_factor", route: route)

Type guard

def valid_devise_route_option?(value)
  value == true || value.is_a?(Symbol) || value.is_a?(Hash)
end

Try / catch

# Wrap initializer-time registration so a bad option fails with file context:
begin
  Devise.add_module(:two_factor, route: route_option)
rescue ArgumentError => e
  raise ArgumentError, "#{e.message} (from config/initializers/devise.rb, module :two_factor)"
end

Prevention

When it happens

Trigger: Calling Devise.add_module :my_module, route: "sessions" (String) or route: [:a, :b] (Array) in config/initializers/devise.rb or a custom gem's engine initializer. Passing a truthy-but-wrong-typed value pulled from ENV/YAML/CLI options into add_module. Any custom Devise module registration whose :route value is not exactly true, a Symbol, or a Hash with scope => actions shape.

Common situations: Writing a custom Devise module (e.g. two-factor, impersonation) and guessing the add_module API instead of copying the shape used by Devise's own registrations (lib/devise.rb calls like add_module :registerable, route: :registration); copy-pasting module code from an old fork with a different API; feeding options through a config layer that stringifies values.

Related errors


AI-assisted analysis of heartcombo/devise@372b295fe6 (2026-08-21). Data as JSON: /api/errors/b53e94953aaf9232. Report an issue: GitHub.