supabase:views
Copies the gem's eight default auth view templates into app/views/supabase/rails/ so they can be customised — Rails' view-resolution order picks the host copies ahead of the gem's.
bin/rails generate supabase:views copies the eight default ERB templates the gem ships under app/views/supabase/rails/ into the host app at the same path. Standard Rails view resolution then picks the host copies ahead of the engine's — there is no prepend_view_path to manage, no opt-in flag to flip — so any edit you make to a copied file takes effect on the next request.
bin/rails generate supabase:viewsExpected output on a fresh app that has already run supabase:install:
create app/views/supabase/rails/oauth/_buttons.html.erb
create app/views/supabase/rails/otp/new.html.erb
create app/views/supabase/rails/otp/verify.html.erb
create app/views/supabase/rails/passwords/edit.html.erb
create app/views/supabase/rails/passwords/new.html.erb
create app/views/supabase/rails/registrations/new.html.erb
create app/views/supabase/rails/sessions/new.html.erb
create app/views/supabase/rails/shared/_flash.html.erbWhen to run this
Run it when you want to restyle the default forms, add fields, swap in your design system, or replace the bare ERB with Hotwire/View Components. Skip it if the gem's defaults are good enough — the templates are functional out of the box and you can ship to production without ever touching them.
Files created
Eight files, all copied byte-verbatim from the gem's app/views/supabase/rails/ directory. Files have .html.erb extensions (not .tt), so there is no ERB pre-processing pass — what's in the gem is what lands in your repo.
| Path | What it renders | Mounted at |
|---|---|---|
app/views/supabase/rails/sessions/new.html.erb | Sign-in form: email + password, plus links to "Forgot password?" / "Create an account" and the oauth/_buttons partial. | GET /session/new |
app/views/supabase/rails/registrations/new.html.erb | Sign-up form: email + password, plus a link back to the sign-in page. | GET /registration/new |
app/views/supabase/rails/passwords/new.html.erb | "Forgot password" request form: email only, posts to POST /passwords to trigger the reset email. | GET /passwords/new |
app/views/supabase/rails/passwords/edit.html.erb | "Reset password" form: password + confirmation. Posts to PUT /passwords/:token. Rendered when the user clicks the reset link in their email. | GET /passwords/:token/edit |
app/views/supabase/rails/otp/new.html.erb | One-time-code request form: email only, posts to POST /otp to send the code. | GET /otp/new |
app/views/supabase/rails/otp/verify.html.erb | One-time-code verify form: hidden email/phone/type fields + the visible 6-digit token field. Posts to POST /otp/verify. | GET /otp/verify |
app/views/supabase/rails/oauth/_buttons.html.erb | Partial. Iterates Rails.application.config.supabase.oauth_providers (defaults to []) and renders one "Sign in with <Provider>" link per entry. Each link kicks off the PKCE flow via OauthController#authorize. Rendered by sessions/new.html.erb unconditionally. | partial |
app/views/supabase/rails/shared/_flash.html.erb | Partial. Renders flash[:alert] in red and flash[:notice] in green. Rendered by every gem-shipped form so sign-in errors and "check your email" success messages surface consistently. | partial |
oauth/_buttons.html.erb reads Rails.application.config.supabase.oauth_providers — the default is an empty array, so the sign-in form renders no OAuth buttons until you populate that list in config/initializers/supabase.rb. See Configuration → oauth_providers for the list of supported providers and what each link's href resolves to.
Override precedence
Rails resolves view templates in the order their paths appear on ActionController::Base.view_paths. The host app's app/views/ is always first; engine-added paths come after. The gem's Supabase::Rails::Engine adds its own app/views/ to the path, but after the host's, so any file the host ships at the same relative path wins.
The resolution order for render "supabase/rails/sessions/new" is:
<your-app>/app/views/supabase/rails/sessions/new.html.erb— if present, this wins.<gem>/app/views/supabase/rails/sessions/new.html.erb— the fallback the gem ships.
The generator's only job is to drop the gem's copy into bucket 1 so you have something to edit. There is no flag on the controller, no prepend_view_path call to make, no initializer toggle. Copy → edit → reload is the entire override loop.
This applies file-by-file:
- Copy only
sessions/new.html.erband the other seven still resolve from the gem. - Delete a host copy and Rails silently falls back to the gem version on the next request.
- The
oauth/_buttonsandshared/_flashpartials follow the same precedence rule — overriding them changes how every gem-shipped form renders, because each top-level template renders the shared partials by relative path (render "supabase/rails/shared/flash",render "supabase/rails/oauth/buttons").
No initializer change needed
Unlike supabase:user_model, this generator does not touch config/initializers/supabase.rb. It only writes the eight ERB files. Once they exist on disk, Rails' standard view-path resolution does the rest.
Worked example: customising the sign-in form
The default sessions/new.html.erb is intentionally minimal — bare <h1>, no styling, no field grouping. Most apps will want to wrap it in their layout's form components. Here's the full edit loop end to end.
Step 1 — Run the generator
bin/rails generate supabase:viewsThis drops the eight templates into app/views/supabase/rails/. Confirm with:
ls app/views/supabase/rails/sessions/
# new.html.erbStep 2 — Inspect the default template
app/views/supabase/rails/sessions/new.html.erb (the file the generator just copied):
<h1>Sign in</h1>
<%= render "supabase/rails/shared/flash" %>
<%= form_with url: session_path do |form| %>
<%= form.email_field :email, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email] %><br>
<%= form.password_field :password, required: true, autocomplete: "current-password", placeholder: "Enter your password", maxlength: 72 %><br>
<%= form.submit "Sign in" %>
<% end %>
<br>
<%= link_to "Forgot password?", new_password_path %><br>
<%= link_to "Create an account", new_registration_path %>
<%= render "supabase/rails/oauth/buttons" %>The contract worth preserving across any edit:
form_with url: session_path— posts toPOST /session, whichSupabase::Rails::SessionsController#createhandles. Change the URL helper and you break sign-in.emailandpasswordare the field names the controller reads fromparams— rename them and the controller will seenil.maxlength: 72matches Supabase Auth's bcrypt password limit. Lowering it is fine; raising it lets users enter passwords Supabase will reject.render "supabase/rails/shared/flash"surfaces the controller's sign-in-error messages. Drop it and failed sign-ins will silently re-render the form.
Step 3 — Replace the markup with your design system
Wrap the form in your app's standard layout helpers — here a fictional card / field_group / primary_button set:
<%# app/views/supabase/rails/sessions/new.html.erb %>
<%= render layout: "shared/card", locals: { title: "Welcome back" } do %>
<%= render "supabase/rails/shared/flash" %>
<%= form_with url: session_path, class: "auth-form" do |form| %>
<%= render "shared/field_group", label: "Email", field: form.email_field(:email,
required: true,
autofocus: true,
autocomplete: "username",
placeholder: "you@example.com",
value: params[:email],
class: "input input--full"
) %>
<%= render "shared/field_group", label: "Password", field: form.password_field(:password,
required: true,
autocomplete: "current-password",
placeholder: "••••••••",
maxlength: 72,
class: "input input--full"
) %>
<%= form.submit "Sign in", class: "btn btn--primary btn--full" %>
<% end %>
<div class="auth-form__links">
<%= link_to "Forgot your password?", new_password_path, class: "link" %>
<%= link_to "Create an account", new_registration_path, class: "link" %>
</div>
<%= render "supabase/rails/oauth/buttons" %>
<% end %>The structural changes only — the four contract pieces above (URL helper, field names, maxlength: 72, flash partial render) are preserved verbatim, so the controller round-trip still works.
Step 4 — Reload and verify
No restart needed — Rails picks up view-template changes on the next request in development:
bin/rails serverOpen http://localhost:3000/session/new. The host's restyled form should render. To prove the override is doing the work (not the gem still serving its own copy), temporarily rename app/views/supabase/rails/sessions/new.html.erb to .bak — the page should fall back to the gem's bare default on the next reload.
Step 5 — Style the shared partials too
If you styled the sign-in form, the same look almost certainly applies to the other seven templates. The two highest-leverage edits:
shared/_flash.html.erb— every form renders it, so a single change updates sign-in error styling, password-reset success messages, OTP "code sent" notices, and registration errors all at once.oauth/_buttons.html.erb— the loop body controls how every OAuth button looks. Replacelink_to "Sign in with #{provider.to_s.capitalize}"with abutton_to+ provider-specific icon and you have a polished social sign-in row.
The other five top-level forms (registrations/new, passwords/new+edit, otp/new+verify) follow the same shape as sessions/new: an <h1>, the flash partial, a form_with with a session_path-style URL helper, the field whitelist the controller reads from params, and a back-link. Apply the same restyling pattern to each.
Re-running and rolling back
The generator uses Thor's directory action, which compares each source file against its destination:
- Files that don't exist in the host print
createand are written. - Files that are byte-identical to the gem source print
identicaland are not rewritten. - Files that differ trigger Thor's standard
[Ynaqdh]overwrite prompt —Yoverwrites,nkeeps the host copy,dshows a diff,aoverwrites all remaining conflicts,qaborts. (Same prompt assupabase:install— see that page for the full key reference.)
The flags worth knowing — all inherited from Thor, the generator declares none of its own:
| Flag | Effect |
|---|---|
--force, -f | Overwrite every host file that has diverged, without prompting. Use when you want to reset your customisations and start from the current gem defaults. |
--skip, -s | Keep host copies for every file that has diverged. Useful when re-running after a gem upgrade to pick up new view files without losing your edits to existing ones. |
--pretend, -p | Dry-run. Prints the create/identical/conflict log lines without writing anything. |
--quiet, -q | Suppress the log lines. |
After a gem upgrade that adds a new view template, the typical workflow is:
bin/rails generate supabase:views --skip--skip keeps every file you've customised, but new files (which don't exist in the host) are still created. This is the lowest-friction way to stay in sync with the gem's view surface without an interactive prompt for each existing file.
To roll back manually — there is no supabase:views uninstall:
- Delete the file you no longer want to customise (e.g.
rm app/views/supabase/rails/sessions/new.html.erb). - Rails immediately falls back to the gem's bundled version on the next request — no restart, no config change.
Delete the entire app/views/supabase/ tree to revert every template at once.
See also
supabase:install— the prerequisite generator that wires up the controllers these templates render under.- Configuration →
oauth_providers— populate this to make theoauth/_buttonspartial render anything. - Authentication — the
Authenticationconcern and the helpers (authenticated?,current_user) available inside customised templates. - Controllers — the five base controllers that render these templates, including the
paramswhitelist each one reads from the forms. - Generators — the index page for all three generators.
supabase:user_model
Opt-in shadow ActiveRecord User model + UUID-keyed Rails migration that mirrors Supabase auth, so belongs_to :user resolves like any other AR association.
Authentication
The Supabase::Rails::Authentication concern, the generated app/controllers/concerns/authentication.rb, the Current model, and the supabase_context request object.