supabase-rb-rb
Configuration

Configuration

Every config.supabase.* Railtie option for supabase-rails, the environment-variable fallbacks, the CORS behaviour, and the logger.

supabase-rails exposes its full configuration surface on the Rails Railtie at config.supabase.*. Defaults are set when Supabase::Rails::Railtie boots; the install generator writes a starter config/initializers/supabase.rb that overrides the two values most apps need to change (mode and a commented-out session block).

The same Railtie also reads a small set of environment variables via Supabase::Rails::Env.resolve and inserts the Rack middleware that hangs the per-request context off request.env.

Where configuration lives

# config/initializers/supabase.rb (written by `bin/rails generate supabase:install`)
Rails.application.config.supabase.mode = :web

# Rails.application.config.supabase.allowed_redirect_origins = ["https://example.com"]
# Rails.application.config.supabase.expose_current_user = nil
# Rails.application.config.supabase.session = {
#   cookie_name: "sb-session",
#   same_site:   :lax,
#   secure:      nil,
#   domain:      nil,
#   path:        "/"
# }

Three pieces work together:

  • Supabase::Rails::Railtie declares config.supabase = ActiveSupport::OrderedOptions.new, sets defaults for every key listed below, and inserts Supabase::Rails::Middleware into the host's Rack stack (unless insert_middleware is false).
  • Supabase::Rails::Engine is internal — it is not mounted by the host (mount Supabase::Rails::Engine is not required). Its single job is to install the supabase_authentication_routes DSL helper onto ActionDispatch::Routing::Mapper before Rails draws config/routes.rb, and to add the gem's app/controllers / app/views / config/locales to the host autoload paths. The engine deliberately skips isolate_namespace so the gem's controllers inherit from the host's ::ApplicationController.
  • Supabase::Rails::Env parses SUPABASE_* environment variables at request time. Keys provided via config.supabase.env (a Hash override) take precedence over the corresponding environment variables.

Config keys

Every key has a default. You only need to set the ones that diverge from the defaults — the install generator sets mode = :web because the framework default is :api.

mode

TypeDefault
Symbol (:api or :web):api

Selects the authentication strategy. :api extracts credentials from the Authorization: Bearer and apikey request headers — appropriate for JSON APIs and SPAs that send a JWT on every request. :web reads and refreshes a Rails-encrypted session cookie (sb-session by default), so Devise-style server-rendered apps work without the client tracking tokens manually. Any other value raises Supabase::Rails::ConfigError(INVALID_MODE) when the middleware is constructed.

Rails.application.config.supabase.mode = :web

auth

TypeDefault
Symbol or Array<Symbol>:user

In :api mode, the list of auth modes the middleware will try in order when verifying credentials. Recognised values: :user (verify a Bearer JWT against the project's JWKS), :publishable (match the apikey header against SUPABASE_PUBLISHABLE_KEY[S]), :secret (match against SUPABASE_SECRET_KEY[S]), :none (skip verification — useful for public routes). Append :name to restrict to a single key entry (e.g. [:publishable, "publishable:default"]) or :* to accept any. Ignored in :web mode, where credentials come from the encrypted cookie.

Rails.application.config.supabase.auth = %i[user publishable]

session

TypeDefault
Hash{ cookie_name: "sb-session", same_site: :lax, secure: nil, domain: nil, path: "/" }

Encrypted session cookie config for :web mode. The cookie is encrypted with the host's existing secret_key_basethere is no new secret to manage. Cookies are always HttpOnly; secure: nil auto-detects from Rails.env.production?.

KeyDefaultDescription
cookie_name"sb-session"Cookie name.
same_site:lax:lax, :strict, or :none. :none requires secure: true.
securenilnil → auto-detect (Rails.env.production?true). Set true/false to force.
domainnilCookie Domain attribute. nil → host-only cookie. Set for subdomain sharing.
path"/"Cookie Path attribute.
Rails.application.config.supabase.session = {
  cookie_name: "myapp-session",
  same_site:   :strict,
  secure:      true,
  domain:      ".myapp.com",
  path:        "/"
}

allowed_redirect_origins

TypeDefault
Array<String>[]

Origin allowlist consulted by the OAuth and password-reset helpers when validating a ?redirect_to= query param. Path-only targets (/dashboard) are always allowed; absolute URLs must match an entry exactly on scheme://host[:port]. When empty, the helpers fall back to [request.host] (same-origin only) at runtime, deriving the scheme from the request — so dev apps on http://localhost:3000 still work. Off-allowlist redirects raise Supabase::Rails::AuthError(INVALID_REDIRECT) with HTTP 400.

Rails.application.config.supabase.allowed_redirect_origins = [
  "https://app.example.com",
  "https://staging.example.com"
]

oauth_providers

TypeDefault
Array<Symbol>[]

Provider list rendered by the supabase/rails/oauth/_buttons partial. The default sign-in / sign-up views render this partial unconditionally, so an empty array means no OAuth buttons appear. Each entry becomes one "Sign in with <provider>" link wired to oauth_path(provider). The string must match a provider id Supabase recognises (google, github, azure, …) — the gem does not validate the list.

Rails.application.config.supabase.oauth_providers = %i[google github]

user_model

TypeDefault
String (class name) or nilnil

Opt into a shadow ActiveRecord User model that mirrors the Supabase user row (FR-W14). When set, Current.user becomes the AR record returned by <Model>.from_supabase(claims) (a by-PK lookup) instead of the immutable Supabase::Rails::User value object. The supabase:user_model generator emits this class + migration and appends this line to the initializer for you.

Rails.application.config.supabase.user_model = "User"

expose_current_user

TypeDefault
Boolean or nilnil (derives from mode)

When true, the Authentication concern exposes current_user as a Rails helper_method so views can call it directly. When nil (the default), it derives from modetrue in :web, false in :api (avoids clashing with API hosts that define their own current_user). Set explicitly when you need to override the inferred behaviour.

Rails.application.config.supabase.expose_current_user = true

insert_middleware

TypeDefault
Booleantrue

Whether the Railtie should insert Supabase::Rails::Middleware into the host's Rack stack on boot. Set to false only if you want to mount the middleware yourself in a non-standard position (for example, before a custom authentication middleware). Without the middleware, request.env[Supabase::Rails::CONTEXT_KEY] is never populated and every authenticated request returns 401.

Rails.application.config.supabase.insert_middleware = false

# Then mount it manually:
Rails.application.config.middleware.insert_before MyAuthMiddleware,
  Supabase::Rails::Middleware,
  mode: :api

env

TypeDefault
Hash or nilnil

Per-host overrides for the environment variables Supabase::Rails::Env.resolve reads. Recognised keys: :url, :publishable_keys, :secret_keys, :jwks. Each key, when present, replaces the corresponding SUPABASE_* env-var lookup entirely. Useful in tests (config.supabase.env = { url: "http://localhost:54321", publishable_keys: { "default" => "sb_publishable_test" } }) or in multi-tenant apps that source credentials from a database.

Rails.application.config.supabase.env = {
  url: "https://abcd1234.supabase.co",
  publishable_keys: { "default" => "sb_publishable_..." },
  secret_keys:      { "default" => "sb_secret_..." }
}

supabase_options

TypeDefault
Hash or nilnil

Raw options forwarded to Supabase::Client.new for every context client the middleware builds. Accepts the same shape as supabase-rb's Supabase::ClientOptions (or the legacy nested-hash form). Lets you set custom global headers, swap the HTTP adapter, or tune the PostgREST / Storage timeouts. The gem rewrites auth: to always include auto_refresh_token: false, persist_session: false, and detect_session_in_url: false (hard invariants — auto_refresh_token: true would leak a background timer per request).

Rails.application.config.supabase.supabase_options = {
  global:    { headers: { "X-App-Version" => MyApp::VERSION } },
  postgrest: { timeout: 30 }
}

cors

TypeDefault
Hash, nil, or falsenil

Controls CORS behaviour on the middleware. nil (default) sends a permissive set of headers tuned for the Supabase clients calling your API. false disables CORS entirely (use this when rack-cors is mounted upstream and should own the response). A Hash is used verbatim as the Access-Control-* headers added to every response and to the 204 preflight reply. See CORS behaviour below for the default header set.

Rails.application.config.supabase.cors = {
  "Access-Control-Allow-Origin"  => "https://app.example.com",
  "Access-Control-Allow-Headers" => "authorization, apikey, content-type",
  "Access-Control-Allow-Methods" => "GET, POST, OPTIONS"
}

Environment variables

Supabase::Rails::Env.resolve is invoked once per request by the middleware (and lazily by the auth helpers). It reads:

VariablePurposeRequired?
SUPABASE_URLProject URL, e.g. https://abcd1234.supabase.co.YesEnvError.missing_supabase_url raises 500 when absent.
SUPABASE_PUBLISHABLE_KEYDefault publishable (anon) key (sb_publishable_…).At least one of the two.
SUPABASE_PUBLISHABLE_KEYSJSON map {"default":"sb_publishable_…","tenant_a":"sb_publishable_…"} for multi-key apps.At least one of the two.
SUPABASE_SECRET_KEYDefault secret (service-role) key (sb_secret_…).Required for :secret auth mode or admin client access.
SUPABASE_SECRET_KEYSJSON map for multiple secret keys.Required for :secret auth mode or admin client access.
SUPABASE_JWKSInline JWKS JSON ({"keys":[…]} or a raw […] array).One of the two for JWT verification.
SUPABASE_JWKS_URLRemote JWKS endpoint. Must be https://http:// is only accepted for loopback hosts (localhost, 127.0.0.1, [::1]).One of the two for JWT verification.

The gem reads `SUPABASE_PUBLISHABLE_KEY`, not `SUPABASE_ANON_KEY`

The Supabase dashboard labels the same value as "anon public" on legacy projects and "publishable" on new projects. The gem only looks at SUPABASE_PUBLISHABLE_KEY / SUPABASE_PUBLISHABLE_KEYS — setting SUPABASE_ANON_KEY (or SUPABASE_SERVICE_ROLE_KEY for the secret) does nothing. Rename your env vars when copying from a Node project.

Multiple keys (plural form)

SUPABASE_PUBLISHABLE_KEYS (and SUPABASE_SECRET_KEYS) take precedence over the singular form when set. The value must be a JSON object mapping a key name to a key value:

SUPABASE_PUBLISHABLE_KEYS='{"default":"sb_publishable_aaa","tenant_a":"sb_publishable_bbb"}'

The singular form is shorthand for {"default":"<value>"}. Auth modes can target a specific key via auth: [:publishable, "publishable:tenant_a"]. Unparseable JSON silently falls back to {} — no key is registered, and request authentication will fail rather than crash at boot.

Interaction with config.supabase.env

When config.supabase.env is set (a Hash, not nil), each key present in the hash replaces the corresponding environment variable lookup wholesale. Keys not present in the hash still fall back to the environment:

# Reads SUPABASE_URL from ENV; uses the hash for keys.
Rails.application.config.supabase.env = {
  publishable_keys: { "default" => "sb_publishable_test" }
}

This is the recommended hook for tests (drive credentials from Rails.application.credentials.dig(:supabase, :test)) or for multi-tenant routing (build the hash per-request via a custom middleware that pre-populates request.env[Supabase::Rails::CONTEXT_KEY]).

CORS behaviour

Supabase::Rails::Middleware consults config.supabase.cors:

  • nil (default) — adds Supabase::Rails::CORS::DEFAULT_HEADERS to every response and replies to OPTIONS requests with 204 + the same headers.
  • false — skips CORS entirely. Preflights pass through to the app; no Access-Control-* headers are added.
  • Hash — used verbatim as the header set.

The default headers are intentionally permissive so the Supabase JS / Swift / Kotlin clients can talk to your API from any origin:

HeaderDefault
Access-Control-Allow-Origin*
Access-Control-Allow-Headersauthorization, x-client-info, apikey, content-type, x-retry-count
Access-Control-Allow-MethodsGET, POST, PUT, PATCH, DELETE, OPTIONS
# Lock origin to your dashboard SPA while keeping the gem's allowed headers/methods.
Rails.application.config.supabase.cors = Supabase::Rails::CORS::DEFAULT_HEADERS
  .merge("Access-Control-Allow-Origin" => "https://app.example.com")

Browser-only concern

CORS only governs browser fetches. Server-to-server calls and native mobile clients ignore these headers entirely — set cors: false if your API is consumed exclusively by trusted backends.

Logging

Supabase::Rails::Logging is a thread-safe accessor for the gem's logger. The gem writes structured one-line messages ([supabase.rails.sign_in_failure] code=… email=a***@example.com, [AUTH_…] warnings on credential failures) — set a logger to capture them.

APIDescription
Supabase::Rails.logger=Assign any object that responds to :debug, :info, :warn, :error. Mutex-guarded; safe to set at boot or swap at runtime.
Supabase::Rails.loggerReads the current logger. Returns nil when unset.
Supabase::Rails::Logging.log(level, message)Internal helper. No-op when no logger is set; swallows exceptions so a misbehaving logger never breaks an auth flow.
# config/initializers/supabase.rb
Supabase::Rails.logger = Rails.logger

Email addresses in log lines are automatically redacted via Supabase::Rails::Authentication.redact_email (alice@example.coma***@example.com), so you can ship sign-in failures to your log aggregator without leaking PII. Access tokens, refresh tokens, and JWT claims are never logged.

The Railtie initializer

The Railtie runs one initializer at boot:

initializer "supabase.middleware" do |app|
  cfg = app.config.supabase
  next unless cfg.insert_middleware

  app.middleware.use Supabase::Rails::Middleware,
                     mode: cfg.mode,
                     auth: cfg.auth,
                     env: cfg.env,
                     supabase_options: cfg.supabase_options,
                     cors: cfg.cors,
                     session: cfg.session,
                     user_model: cfg.user_model
end

Two things to note:

  • The middleware is appended at the end of the stack with app.middleware.use. If you need it earlier (for example, before a Rack::Attack throttler that should see the authenticated user), set insert_middleware = false and call Rails.application.config.middleware.insert_before yourself.
  • The middleware constructor reads its config from arguments — it does not re-read Rails.application.config.supabase per request. Changing a key after boot has no effect; restart the Rails process.

See also

  • Getting started — installing the gem and setting SUPABASE_URL / SUPABASE_PUBLISHABLE_KEY.
  • Generators — what supabase:install writes into the initializer.
  • Web mode — how the encrypted session cookie is read, refreshed, and rewritten.
  • Authentication — the concern that consumes expose_current_user, user_model, and allowed_redirect_origins.
  • supabase-rb: InitializingSupabase::Client.new and Supabase::ClientOptions, the underlying surface supabase_options is forwarded to.

On this page