supabase-rb-rb
Controllers

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
end

Source

# 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
end

Routes

HelperVerbURLAction
new_registration_pathGET/registration/newnew
registration_pathPOST/registrationcreate

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

ParamSourceNotes
emailPOST bodyRequired. Forwarded to Supabase Auth verbatim.
passwordPOST bodyRequired. 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:

BranchTriggerRedirect / renderStatusFlash
Success — auto sign-inProject has email confirmation off → upstream returns AuthResponse with .session non-nilredirect_to after_authentication_url302notice: I18n.t("supabase.rails.registrations.created")
Success — confirmation pendingProject has email confirmation on → upstream returns AuthResponse with .session == nilredirect_to new_session_path302notice: I18n.t("supabase.rails.registrations.pending_confirmation")
Failure — weak passwordUpstream raises AuthWeakPassword → mapped code: WEAK_PASSWORD, status: 422render :new422flash.now[:alert] = result.error.message
Failure — other 4xxUpstream 4xx (invalid email, already-registered conflict) → masked to code: INVALID_CREDENTIALS, status: 401render :new422flash.now[:alert] = "Invalid credentials"
Failure — 5xxUpstream 5xx → mapped code: AUTH_UPSTREAM_ERROR, status: 503render :new422flash.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

HookDefaultWhere to override
after_authentication_urlstored_location_for_redirect || root_urlapp/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
end

The 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
end

The 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
end

See also

On this page