heartcombo/devise · error · AbstractController::ActionNotFound

Could not find devise mapping for path #{request.fullpath.in

Error message

Could not find devise mapping for path #{request.fullpath.inspect}.
This may happen for two reasons:

1) You forgot to wrap your route inside the scope block. For example:

  devise_scope :user do
    get "/some/route" => "some_devise_controller"
  end

2) You are testing a Devise controller bypassing the router.
   If so, you can explicitly tell Devise which mapping to use:

   @request.env["devise.mapping"] = Devise.mappings[:user]

What it means

Every Devise controller derives its resource scope (User, Admin, etc.) from request.env["devise.mapping"], which is set by the routing scope that devise_for/devise_scope installs. DeviseController runs assert_is_devise_resource! as a prepend_before_action on every request, and when the mapping env entry is nil it raises AbstractController::ActionNotFound with this message. In practice it means a Devise controller action was reached outside of any Devise-mapped route, or a controller test hit the controller directly without bypassing the router being compensated for.

Source

Thrown at app/controllers/devise_controller.rb:104

    get "/some/route" => "some_devise_controller"
  end

2) You are testing a Devise controller bypassing the router.
   If so, you can explicitly tell Devise which mapping to use:

   @request.env["devise.mapping"] = Devise.mappings[:user]

MESSAGE
  end

  # Returns real navigational formats which are supported by Rails
  def navigational_formats
    @navigational_formats ||= Devise.navigational_formats.select { |format| Mime::EXTENSION_LOOKUP[format.to_s] }
  end

  def unknown_action!(msg)
    logger.debug "[Devise] #{msg}" if logger
    raise AbstractController::ActionNotFound, msg
  end

  # Sets the resource creating an instance variable
  def resource=(new_resource)
    instance_variable_set(:"@#{resource_name}", new_resource)
  end

  # Helper for use in before_actions where no authentication is required.
  #
  # Example:
  #   before_action :require_no_authentication, only: :new
  def require_no_authentication
    assert_is_devise_resource!
    return unless is_navigational_format?
    no_input = devise_mapping.no_input_strategies

    authenticated = if no_input.present?
      args = no_input.dup.push scope: resource_name

View on GitHub (pinned to 372b295fe6)

Solutions

  1. Wrap the offending route in a devise_scope block: devise_scope :user do get "/users/invite" => "devise/registrations#invite" end (use the scope name from devise_for).
  2. If this happens in a controller/request spec, set the mapping explicitly in setup/before: @request.env["devise.mapping"] = Devise.mappings[:user].
  3. If the route is genuinely custom logic, point it at your own controller that inherits from the Devise controller only when it is also mounted inside devise_scope; otherwise inherit from ApplicationController.
  4. Prefer request specs (rails generate rspec:request or Rails' integration tests) that go through the real router, which sets the mapping automatically.

Example fix

# before (config/routes.rb)
get "/users/invite" => "devise/registrations#invite"

# after
scope "/users" do
  devise_scope :user do
    get "/invite" => "devise/registrations#invite", as: :invite
  end
end

# spec fix (controller specs bypassing the router)
# before
RSpec.describe Devise::RegistrationsController, type: :controller do
  describe "GET #new" do
    it "renders" do
      get :new
    end
  end
end

# after
RSpec.describe Devise::RegistrationsController, type: :controller do
  before do
    @request.env["devise.mapping"] = Devise.mappings[:user]
  end
  describe "GET #new" do
    it "renders" do
      get :new
      expect(response).to have_http_status(:ok)
    end
  end
end
Defensive patterns

Strategy: validation

Validate before calling

# In a custom Devise controller, verify the mapping before relying on it:
class Devise::RegistrationsController
  protected

  def devise_mapping_present?
    request.env["devise.mapping"].present?
  end
end

# In controller specs, always install the mapping in before/setup so the
# prepend_before_action assert_is_devise_resource! never fires:
# @request.env["devise.mapping"] = Devise.mappings[:user]

Type guard

# Predicate usable as a before_action guard for custom Devise routes:
def devise_scope_mapped?(request)
  !request.env["devise.mapping"].nil?
end
# usage: before_action -> { head :not_found unless devise_scope_mapped?(request) }

Prevention

When it happens

Trigger: 1) Routing a custom endpoint straight to a Devise controller, e.g. get "/users/invite" => "devise/registrations#invite", outside a devise_scope :user block, so the mapping middleware never runs. 2) Request/controller specs on a Devise controller that call get :new, params: {} directly (bypassing the router) without first setting @request.env["devise.mapping"] = Devise.mappings[:user]. 3) Reusing a Devise controller subclass for a non-Devise route that was never wrapped in devise_scope.

Common situations: Adding custom actions to Devise controllers (invite flows, profile pages) and mounting them with plain routes; writing controller tests for Devise or inherited Devise controllers instead of request/integration specs; refactoring routes.rb and accidentally moving a Devise route out of its scope; namespaced engines routing to Devise controllers without re-declaring the scope.

Related errors


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