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
- 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.
- Use true if the module needs its own route group named after the module itself: Devise.add_module :my_module, route: true.
- Use a Hash when you must scope to an existing group: route: { session: [] } (keys = scope, values = extra URL helper actions array).
- If the module needs no routes at all (pure model concern, like :validatable), omit the :route option entirely.
- 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
- Copy the exact add_module call shapes from devise.rb's own registrations (e.g. add_module :recoverable, route: :password) instead of guessing the API.
- Keep custom module registration in one initializer and boot the app (or run rails runner 'Devise.add_module' smoke checks) in CI to catch bad options.
- If options flow from config files or ENV, normalize them before they reach add_module (strings won't be coerced for you).
- Remember :route is optional — omit it for model-only modules like :validatable.
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
- #{name}
- Mapping omniauth_callbacks on a resource that is not omniaut
- Could not find devise mapping for path #{request.fullpath.in
- "Devise doesn't know how to sanitize parameters for '#{actio
- An ORM must be set to install Devise in your application. B
AI-assisted analysis of heartcombo/devise@372b295fe6 (2026-08-21).
Data as JSON: /api/errors/b53e94953aaf9232.
Report an issue: GitHub.