G
GTM Vault
Browse
  • Dashboard
    • Automations
    • Skills
    • Prompts
    • Makers
  • Sign in
All Skills

login-page

Patterns for building login/signup pages with Netlify Identity — dual views, custom forms, GoTrue JS, controller class

skillInfrastructureClaude Code SkillsInternal Ops
Original
by Roheel Jain
Skill Content

Dual Sign In / Sign Up Pattern

Use tabs to toggle between two views:

  • Sign In view: Button that opens Netlify Identity widget modal
  • Sign Up view: Custom inline form with GoTrue JS for custom fields

Toggle with CSS classes (hidden/visible) controlled by a controller class.

Custom Signup Form with user_metadata

Identity widget does NOT support custom form fields. Use GoTrue JS directly:

const auth = new GoTrue({ APIUrl: `${window.location.origin}/.netlify/identity`, setCookie: true });
await auth.signup(email, password, {
    full_name: name,
    linkedin_url: url,
    company_name: company
});
  • Custom fields stored in user_metadata (not visible in Netlify UI, but stored on user)
  • After signup, user receives confirmation email
  • After confirming, user logs in via Identity widget (Sign In view)

Controller Class Structure (OOP)

class LoginController {
    constructor() {
        this.widget = window.netlifyIdentity;
        this.auth = new GoTrue({ APIUrl: '...', setCookie: true });
        // cache DOM elements
    }
    init() { this.bindEvents(); this.checkExistingSession(); }
    bindEvents() { /* DOM events first, then widget events */ }
    checkExistingSession() { /* widget.currentUser() → redirect */ }
    showLoginView() { /* toggle tabs + views */ }
    showSignupView() { /* toggle tabs + views */ }
    handleSignup(e) { /* validate, call auth.signup(), show confirmation */ }
    showConfirmation() { /* "check your email" message */ }
    openLogin() { this.widget.open('login'); }
    redirectToDashboard() { window.location.href = '/'; }
}

Key: Bind DOM event listeners (tabs, form submit) BEFORE widget event listeners. If widget throws, DOM events still work.

Common error: Binding widget events first → if widget throws, tab click handlers never get registered → UI appears broken.

Script Loading Order

<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
<script src="https://unpkg.com/gotrue-js@0.9.29/dist/gotrue.js"></script>
<script>
    // Initialize inline — both scripts are loaded synchronously above
    const controller = new LoginController();
    controller.init();
</script>

Common error: Wrapping init in DOMContentLoaded or load — inline execution after synchronous scripts is more reliable.

Tech Stack
JavaScript
JavaScript