RegistrationsController
Supabase::Rails::RegistrationsController — email + password sign-up with the two-branch happy path.
Supabase::Rails::RegistrationsController handles email + password sign-up. The install generator writes a top-level RegistrationsController that inherits from it.
Under the hood, create wraps the supabase-rb auth.sign_up call (via supabase_sign_up).
# app/controllers/registrations_controller.rb (written by `bin/rails generate supabase:install`)
class RegistrationsController < Supabase::Rails::RegistrationsController
endSource
# app/controllers/supabase/rails/registrations_controller.rb (in the gem)
class RegistrationsController < BaseController
allow_unauthenticated_access only: %i[new create]
def new; end
def create
result = supabase_sign_up(email: params[:email], password: params[:password])
if result.success?
if registered_session_present?(result.value)
redirect_to after_authentication_url,
notice: I18n.t("supabase.rails.registrations.created")
else
redirect_to new_session_path,
notice: I18n.t("supabase.rails.registrations.pending_confirmation")
end
else
flash.now[:alert] = result.error.message
render :new, status: :unprocessable_entity
end
end
private
def registered_session_present?(value)
value.respond_to?(:session) && !value.session.nil?
end
endRoutes
| Helper | Verb | URL | Action |
|---|---|---|---|
new_registration_path | GET | /registration/new | new |
registration_path | POST | /registration | create |
Mounted by resource :registration, only: %i[new create] inside supabase_authentication_routes. Singular registration keeps the URL /registration (not /registrations).
Actions
new
Renders the sign-up form at /registration/new. Action body is empty — Rails renders registrations/new.html.erb. Allowed without authentication.
The shipped view at app/views/supabase/rails/registrations/new.html.erb submits to registration_path with two fields (email, password).
create
Creates a new Supabase Auth user and dispatches on the upstream response shape. Allowed without authentication.
Params
| Param | Source | Notes |
|---|---|---|
email | POST body | Required. Forwarded to Supabase Auth verbatim. |
password | POST body | Required. Max 72 bytes (bcrypt limit). Supabase Auth enforces the project's password-strength policy. |
Outcome dispatch — two-branch happy path
The Supabase project's "Confirm email" toggle (Authentication → Sign In / Up in the dashboard) decides which branch fires:
| Branch | Trigger | Redirect / render | Status | Flash |
|---|---|---|---|---|
| Success — auto sign-in | Project has email confirmation off → upstream returns AuthResponse with .session non-nil | redirect_to after_authentication_url | 302 | notice: I18n.t("supabase.rails.registrations.created") |
| Success — confirmation pending | Project has email confirmation on → upstream returns AuthResponse with .session == nil | redirect_to new_session_path | 302 | notice: I18n.t("supabase.rails.registrations.pending_confirmation") |
| Failure — weak password | Upstream raises AuthWeakPassword → mapped code: WEAK_PASSWORD, status: 422 | render :new | 422 | flash.now[:alert] = result.error.message |
| Failure — other 4xx | Upstream 4xx (invalid email, already-registered conflict) → masked to code: INVALID_CREDENTIALS, status: 401 | render :new | 422 | flash.now[:alert] = "Invalid credentials" |
| Failure — 5xx | Upstream 5xx → mapped code: AUTH_UPSTREAM_ERROR, status: 503 | render :new | 422 | flash.now[:alert] = "..." (mapper message) |
The two success branches share the same upstream call (supabase_sign_up). The branch fires on result.value.session — when auto-sign-in is on, the upstream returns a session immediately and supabase_sign_up internally calls start_new_session_for so the cookie is already written by the time the redirect runs.
The 'pending_confirmation' branch will surprise you in dev
Fresh Supabase projects ship with "Confirm email" on. Following the default sign-up flow lands the user at new_session_path with the "check your inbox" notice — not the dashboard. If you want auto-sign-in in development, flip the toggle off in the Supabase dashboard before testing.
Error policy. The error matrix above is enforced by translate_sign_up_error in the concern. WEAK_PASSWORD, PKCE_ERROR, and SESSION_MISSING codes are preserved so the host can render a specific UI for each; every other 4xx is masked to INVALID_CREDENTIALS for parity with the sign-in helper. The status used for the re-render is always :unprocessable_entity (422) regardless of the mapped error's status — the form is re-rendered with the flash, and a real 401/422/503 only flows up if the host overrides the action.
allow_unauthenticated_access
allow_unauthenticated_access only: %i[new create]Sign-up is by definition unauthenticated, so both actions skip require_authentication.
Hookable callbacks
| Hook | Default | Where to override |
|---|---|---|
after_authentication_url | stored_location_for_redirect || root_url | app/controllers/concerns/authentication.rb (preferred) or the host's RegistrationsController |
registered_session_present? (private) | value.respond_to?(:session) && !value.session.nil? | The host's RegistrationsController (private method; override to change the branch trigger, e.g. if you want to always route through email confirmation even when auto-sign-in fires upstream) |
Override patterns
Passing user metadata. supabase_sign_up accepts data: for raw_user_meta_data and redirect_to: for the post-confirmation URL. Override create and call the helper directly:
class RegistrationsController < Supabase::Rails::RegistrationsController
def create
result = supabase_sign_up(
email: params[:email],
password: params[:password],
data: { full_name: params[:full_name], referrer: params[:referrer] },
redirect_to: confirmation_callback_url,
)
if result.success?
if result.value.respond_to?(:session) && result.value.session
redirect_to after_authentication_url, notice: t(".welcome")
else
redirect_to new_session_path, notice: t(".pending")
end
else
flash.now[:alert] = result.error.message
render :new, status: :unprocessable_entity
end
end
endThe metadata is then readable as Current.user.user_metadata["full_name"] after the user signs in.
Welcome email / onboarding. Use super and branch on authenticated?:
class RegistrationsController < Supabase::Rails::RegistrationsController
def create
super
OnboardingMailer.welcome(Current.user.email).deliver_later if authenticated?
end
endThe authenticated? check fires true only on the auto-sign-in branch — on the confirmation-pending branch Current.user is still nil at this point, so the welcome email correctly waits for confirmation.
Replacing with a from-scratch controller — point the host subclass at Supabase::Rails::BaseController directly and branch on the mapper's stable codes:
class RegistrationsController < Supabase::Rails::BaseController
allow_unauthenticated_access only: %i[new create]
def new; end
def create
result = supabase_sign_up(email: params[:email], password: params[:password])
if result.success?
handle_signup_success(result.value)
else
flash.now[:alert] = case result.error.code
when Supabase::Rails::AuthError::WEAK_PASSWORD then t(".weak_password")
when Supabase::Rails::AuthError::AUTH_UPSTREAM_ERROR then t(".try_again")
else t(".invalid")
end
render :new, status: :unprocessable_entity
end
end
private
def handle_signup_success(value)
if value.respond_to?(:session) && value.session
redirect_to after_authentication_url, notice: t(".created")
else
redirect_to new_session_path, notice: t(".pending")
end
end
endSee also
- Controllers overview — the full route table and the override pattern.
BaseController— the common parent class.Authenticationconcern —supabase_sign_up,start_new_session_for,authenticated?.AuthErrorMapper— the code → status table the failure branch reads.SessionsController— sign-in for confirmed users.- supabase-rb:
auth.sign_up— the underlying Ruby method this action delegates to.