Documentation · See the demo · Changelog · RubyGems · GitHub
Rails Consent
rails_consent is a Rails engine for cookie consent in Rails applications, built around locale-backed categories, a built-in signed cookie flow, small helper APIs, and a minimal JavaScript runtime.
Features
- Cookie consent banner and preferences modal
- Built-in signed cookie persistence with optional
:dbstorage hooks - Locale-backed category definitions and interface copy
- Helpers for views, controllers, and layouts
- Minimal browser runtime with lifecycle events and a small client-side API
- Overrideable templates without copying runtime assets
- Importmap and Turbo-friendly integration
Installation
Add the gem to your Rails app:
gem "rails_consent"Then install it:
bundle install
bin/rails generate rails_consent:installThe installer creates:
config/initializers/rails_consent.rbconfig/locales/rails_consent.en.yml
In the default :cookie mode, that generated setup is enough to render the UI and persist consent through a signed Rails cookie.
Define Categories and Copy
Rails Consent keeps banner copy, modal copy, and category definitions in one locale file:
en:
rails_consent:
banner:
eyebrow: Privacy settings
title: Review cookie choices
description: Necessary cookies stay enabled. Optional categories can be updated at any time.
accept_all: Allow all
reject_optional: Reject optional
manage_preferences: Privacy preferences
modal:
eyebrow: Cookie preferences
title: Review cookie categories
save_preferences: Save settings
categories:
necessary:
label: Necessary
required: true
description: Essential cookies required for site functionality
analytics:
label: Analytics
required: false
description: Used to understand visitor behavior
marketing:
label: Marketing
required: false
description: Used for advertising and remarketingDisplay order follows the locale file order.
Configure Rails Consent
The initializer defines where consent lives, how the banner behaves by default, and how category checks should resolve:
RailsConsent.configure do |config|
config.storage = :cookie
config.banner_position = :bottom
config.prompt_dismissible = true
config.cookie_name = "rails_consent_preferences"
config.cookie_expiration_days = 180
# config.cookie_same_site = :lax
# config.cookie_http_only = true
# config.cookie_secure = ->(request) { request.ssl? || Rails.env.production? }
config.consent_resolver = RailsConsent::DefaultConsentResolver.new
endconsent_resolver is required. Start with RailsConsent::DefaultConsentResolver.new when each optional category maps directly to a boolean consent decision, then replace it when your application needs additional rules.
Render the Consent UI
Load the gem assets in your layout and keep the banner mounted near the bottom of the page:
<head>
<%= rails_consent_assets %>
</head>
<body>
<%= yield %>
<%= rails_consent_banner %>
</body>rails_consent_assets loads only the gem's core runtime assets: rails_consent.js and the minimal rails_consent/application.css stylesheet used for runtime visibility utilities.
If one page needs different behavior, override it locally:
<%= rails_consent_banner position: :top, dismissible: false %>Reopen Preferences Later
Use the shared helper anywhere you want to reopen the preferences modal:
<%= rails_consent_preferences_button "Privacy preferences", class: "footer-button" %>That helper is a good fit for footers, privacy pages, and account settings screens.
Gate Optional Behavior
Use consent_given? anywhere you need to align server-rendered behavior with the current consent state:
<% if consent_given?(:analytics) %>
<%= render "analytics_script" %>
<% end %>The public helper surface is:
rails_consent_assets
rails_consent_banner
rails_consent_preferences_button
consent_given?(:analytics)
consent_preferences
consent_recorded?
reset_consent!Client-side API
Rails Consent also exposes a small browser API through window.RailsConsent:
window.RailsConsent.preferences()
window.RailsConsent.consentGiven("analytics")
window.RailsConsent.consentRecorded()
window.RailsConsent.sessionId()
window.RailsConsent.openPreferences()Lifecycle events are dispatched on document so host apps can react without modifying the gem runtime:
rails-consent:banner-shownrails-consent:preferences-openedrails-consent:preferences-savedrails-consent:preferences-updatedrails-consent:accepted-allrails-consent:rejected-optionalrails-consent:dismissed
Storage Modes
Signed Cookie Storage
config.storage = :cookie uses the built-in persistence route and a signed cookie payload keyed by category name. This is the default integration path.
If you want to wrap the built-in cookie flow without replacing it, provide cookie_reader, cookie_writer, or cookie_destroyer hooks around RailsConsent::CookieStore.
Database-backed Storage
When consent should live alongside a user or account record, switch only the storage hooks:
RailsConsent.configure do |config|
config.storage = :db
config.banner_position = :bottom
config.prompt_dismissible = true
config.consent_resolver = RailsConsent::DefaultConsentResolver.new
config.preferences_provider = ->(context:, **) do
context.current_user&.consent_preferences || {}
end
config.preferences_writer = ->(preferences:, context:, **) do
user = context.current_user
raise "current_user is required for db-backed consent" unless user
user.update!(consent_preferences: preferences)
user.consent_preferences
end
config.preferences_destroyer = ->(context:, **) do
context.current_user&.update!(consent_preferences: {})
end
endMost applications pair that initializer with a JSON or JSONB column such as users.consent_preferences.
Customize Presentation
Override any shipped partial from your host app by creating:
app/views/rails_consent/_banner.html.erb
app/views/rails_consent/_preferences_modal.html.erb
app/views/rails_consent/_category_toggle.html.erb
app/views/rails_consent/_cookie_table.html.erb
Keep wording changes in locale YAML when the markup stays the same. Reach for template overrides only when the structure itself needs to change.
Troubleshooting
- If the banner never appears, confirm
rails_consent_banneris mounted in your layout and consent has not already been recorded. - If the UI opens without styles or behavior, confirm
rails_consent_assetsis loaded in<head>. - If boot fails with a configuration error, compare your initializer to the generated file and restore any required lines that were removed.
- If a custom category never enables, review
consent_resolverafter changingconfig/locales/rails_consent.en.yml. - If
:dbmode does not persist updates, confirmpreferences_provider,preferences_writer, andpreferences_destroyerare all configured and return hashes keyed by category name. - If client-side integrations cannot read consent, keep
rails_consent_bannermounted on the page sowindow.RailsConsenthas a rendered boundary to read from.
Documentation
Full guides, live demos, and integration examples are available at:
Contributing
See CONTRIBUTING.md for development and contribution guidelines.
License
Released under the MIT license. See LICENSE.
