norman/friendly_id · warning

is reserved

Error message

is reserved

What it means

FriendlyId::Reserved (lib/friendly_id/reserved.rb:37) adds `validates_exclusion_of :friendly_id` against `friendly_id_config.reserved_words`, so a save fails with "is reserved" on the :friendly_id attribute whenever the resolved slug equals a reserved word. The Reserved module is active by default (`FriendlyId.defaults` does `config.use :reserved`), and the Rails generator's initializer reserves route-colliding words such as "new", "edit", "index", "admin", "login" (lib/friendly_id/initializer.rb:19). It is a validation failure, not an exception: `record.save` returns false and `record.valid?` is false.

Source

Thrown at lib/friendly_id/reserved.rb:37

  #     class Person < ActiveRecord::Base
  #       extend FriendlyId
  #       friendly_id :name, use: :slugged
  #
  #       after_validation :move_friendly_id_error_to_name
  #
  #       def move_friendly_id_error_to_name
  #         errors.add :name, *errors.delete(:friendly_id) if errors[:friendly_id].present?
  #       end
  #     end
  #
  # @guide end
  module Reserved
    # When included, this module adds configuration options to the model class's
    # friendly_id_config.
    def self.included(model_class)
      model_class.class_eval do
        friendly_id_config.class.send :include, Reserved::Configuration
        validates_exclusion_of :friendly_id, in: ->(_) {
          friendly_id_config.reserved_words || []
        }
      end
    end

    # This module adds the `:reserved_words` configuration option to
    # {FriendlyId::Configuration FriendlyId::Configuration}.
    module Configuration
      attr_accessor :reserved_words
      attr_accessor :treat_reserved_as_conflict
    end
  end
end

View on GitHub (pinned to 4699c9791d)

Solutions

  1. Set `config.treat_reserved_as_conflict = true` in the FriendlyId initializer so the slug generator treats reserved words as taken and appends a sequence ("new-2") instead of failing validation.
  2. Provide fallback candidates so validation can step past the reserved word: `friendly_id :name, use: [:slugged, :cached_candidates], candidates: [:name, [:name, :id]]`.
  3. Trim `reserved_words` to words that actually collide with your routes — e.g. drop "admin" if it is not a top-level slug in your app.
  4. When assigning friendly_id manually, check the value against `Model.friendly_id_config.reserved_words` (or suffix it) before assignment.
  5. Surface the error to users: add an `after_validation :move_friendly_id_error_to_name` callback that re-adds errors[:friendly_id] onto the name attribute, as shown in reserved.rb's guide.

Example fix

# before (config/initializers/friendly_id.rb)
FriendlyId.defaults do |config|
  config.reserved_words = %w[new edit index session login logout users admin]
end
# Post.create!(title: "New")
# => ActiveRecord::RecordInvalid: validation failed: Friendly ID is reserved

# after
FriendlyId.defaults do |config|
  config.reserved_words = %w[new edit index]
  config.treat_reserved_as_conflict = true
end
# Post.create!(title: "New").slug # => "new-2"
Defensive patterns

Strategy: validation

Validate before calling

slug = record.title.parameterize
reserved = Post.friendly_id_config.reserved_words || []
slug = "#{slug}-2" if reserved.include?(slug) # or pick another candidate
# or, after the fact:
# record.valid? && record.errors[:friendly_id].include?("is reserved")

Prevention

When it happens

Trigger: Creating or updating a record whose slug resolves to a reserved word — e.g. with the generated initializer `Post.create(title: "New")` produces slug "new", which is in the reserved list, so `save`/`valid?` adds "is reserved" to errors[:friendly_id]. Also triggered by assigning `friendly_id` directly to a reserved value, and typically happens when `treat_reserved_as_conflict` is not enabled, because the slugged generator then proposes the reserved slug instead of sequencing past it. Any model using FriendlyId inherits the Reserved validation via the default defaults block even if the author never asked for it.

Common situations: Scaffolded CRUD forms show nothing when this fires, because the error sits on :friendly_id which has no form field (the guide in reserved.rb shows moving it to :name with an after_validation hook); single-word titles like "admin" or "new"; teams editing the initializer's reserved list and forgetting dependent models; seed files and test fixtures that hardcode reserved slugs; keeping the default reserved list even though those words don't collide with the app's actual routes.


AI-assisted analysis of norman/friendly_id@4699c9791d (2026-08-21). Data as JSON: /api/errors/b7b2c6fbc7fcb673. Report an issue: GitHub.