
Netlify Identity
- 1.4k installs
- 31 repo stars
- Updated August 4, 2026
- netlify/context-and-tools
netlify-identity is a backend integration skill that implements user authentication, signups, logins, password recovery, and role-based access control using the @netlify/identity package on Netlify-hosted applications.
About
netlify-identity is a Claude Code skill from netlify/context-and-tools for correctly implementing authentication on Netlify-hosted applications. Netlify Identity is a user management service built on GoTrue that handles signups, logins, password recovery, user metadata, and role-based access control while issuing JSON Web Tokens. The skill mandates @netlify/identity and explicitly forbids deprecated netlify-identity-widget and gotrue-js packages. Developers reach for netlify-identity when tasks involve authentication, OAuth providers, password recovery, protecting routes and Netlify Functions, or RBAC on Jamstack deployments where legacy identity widgets would introduce unsupported patterns.
- Always use @netlify/identity package
- Unified headless TypeScript API for browser and server contexts
- Built on GoTrue with JWT issuance for secure sessions
- Handles user metadata and role-based access control
- Prevents use of deprecated netlify-identity-widget or gotrue-js
Netlify Identity by the numbers
- 1,392 all-time installs (skills.sh)
- +126 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #337 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/netlify/context-and-tools --skill netlify-identityAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.4k |
|---|---|
| repo stars | ★ 31 |
| Last updated | August 4, 2026 |
| Repository | netlify/context-and-tools ↗ |
How do you add authentication to a Netlify app?
Correctly implement user authentication, signups, logins, password recovery, and role-based access control in any Netlify-hosted application.
Who is it for?
Full-stack developers deploying Jamstack apps on Netlify who need GoTrue-based auth without deprecated identity widgets.
Skip if: Applications hosted outside Netlify or projects already committed to Auth0, Clerk, or Supabase Auth instead of Netlify Identity.
When should I use this skill?
User needs Netlify authentication, signups, logins, password recovery, OAuth providers, RBAC, or protected Netlify Functions and routes.
What you get
@netlify/identity integration code with JWT auth, signup/login flows, password recovery, and role-based access control for routes and functions.
- Authentication integration code
- JWT-protected route and function configurations
Files
Netlify Identity
Netlify Identity is a user management service for signups, logins, password recovery, user metadata, and role-based access control. It is built on GoTrue and issues JSON Web Tokens (JWTs).
Always use `@netlify/identity`. Never use netlify-identity-widget or gotrue-js — they are deprecated. @netlify/identity provides a unified, headless TypeScript API that works in both browser and server contexts (Netlify Functions, Edge Functions, SSR frameworks).
Dashboard configuration (user handoff required)
All Identity instance configuration is dashboard-only — there is no public API. The agent owns the code, deploys, and the handoff checklist; the user owns flipping dashboard settings. Outside of a Netlify Agent Runner deploy, the Identity instance must be enabled in the dashboard before any auth flow will work. If you write Identity code first and only discover this when /.netlify/identity/signup 404s after a production deploy, that's wasted work — surface the dashboard handoff up front instead.
Dashboard URL pattern: https://app.netlify.com/projects/<project-slug>/configuration/identity (it's under project configuration — not under Integrations, and not a top-level sidebar item).
Dashboard-only operations
- Enable Identity — turns the Identity instance on for the site. Required before any auth flow works.
- Registration mode — Open (anyone can sign up, the default) or Invite only.
- Autoconfirm — ON skips the email-confirmation step on signup; OFF requires the new user to click a confirmation email before they can log in.
- External providers — Add Google / GitHub / GitLab / Bitbucket / Facebook. The "Use Netlify's app" option means no
client_id/secretneeded — good for prototypes. Adding an OAuth provider does NOT disable email/password — email/password is always available unless the front-end omits it. - Custom email templates / SMTP — advanced; out of scope for typical prototypes.
There is no CLI command and no public API for any of these. Do not curl https://api.netlify.com/... to flip toggles, do not read auth tokens out of ~/Library/Preferences/netlify/config.json, and do not probe for an undocumented endpoint. Give the user the dashboard URL and exact checklist instead.
Agent/user sequence
1. Agent asks any missing auth-shape questions before scaffolding. 2. Agent writes the Identity code and runs a draft deploy. 3. User enables Identity and any OAuth providers in the dashboard using the handoff checklist. 4. Agent verifies the draft URL and then runs the production deploy.
Recommended settings per use case
| Use case | Registration | Autoconfirm | External providers |
|---|---|---|---|
| Prototype / demo | Open | ON | as requested |
| Production with email signup | Open or Invite per product | OFF (real email confirmation) | configured with custom email templates / SMTP as needed |
Handoff checklist
When the dashboard work is needed, give the user a copy-pasteable checklist between the draft deploy and the production deploy — not after the prod deploy fails:
Before this works end-to-end, flip these in the Netlify dashboard at
https://app.netlify.com/projects/<your-slug>/configuration/identity:
- [ ] Identity → Enable
- [ ] Registration → Open (default) or Invite only
- [ ] Autoconfirm → ON for prototypes; OFF for prod with email confirmation
- [ ] External providers → Add Google (etc.) with "Use Netlify's app"
Tell me when these are flipped and I'll run the production deploy.Before you build
If the prompt didn't already specify, ask the user a few short questions before scaffolding any auth code — the answers shape both the dashboard config above and the auth UI you'll write:
- Which sign-in methods should this app expose: email/password, OAuth, or both?
- Which parts of the app need authenticated access: the whole app, specific routes, or only specific actions?
- Who can create accounts: public signup or invite-only?
- Should new email/password users be able to log in immediately for a prototype (Autoconfirm ON), or confirm by email first for production (Autoconfirm OFF)?
- Which OAuth providers should be enabled (Google, GitHub, GitLab, Bitbucket, Facebook)?
If you don't have preferences here, tell me what you want overall and I'll pick sensible defaults — typically email/password + Google OAuth, autoconfirm ON, registration Open for a prototype.
Asking these after coding causes rework — both the auth UI shape and the dashboard config fall out of these answers.
When something fails, surface and stop
If a deploy fails, an Identity callback 404s, an OAuth flow doesn't return, or /.netlify/identity/* is unreachable — report the failure to the user with the deploy log URL, the exact error, and the site URL, then stop. Do not curl the Netlify API to "fix" the Identity instance, do not invent recovery commands, do not bypass the dashboard. Identity instance state has no public API to repair; the recovery is to hand the user the dashboard URL, the setting to check, and the observed failure.
Setup
npm install @netlify/identityThe Identity instance must be enabled in the dashboard first (see Dashboard configuration above). The one exception: a deploy created by a Netlify Agent Runner session that includes Identity code auto-enables the instance.
Local Development
Identity does not currently work with netlify dev. You must deploy to Netlify to test Identity features. Use npx netlify deploy for preview deploys during development. This limitation may be resolved in a future release.
Quick Start
Log in from the browser:
import { login, getUser } from '@netlify/identity'
const user = await login('user@example.com', '<password>')
console.log(`Hello, ${user.name}`)
// Later, check auth state
const currentUser = await getUser()Protect a Netlify Function:
// netlify/functions/protected.mts
import { getUser } from '@netlify/identity'
import type { Context } from '@netlify/functions'
export default async (req: Request, context: Context) => {
const user = await getUser()
if (!user) return new Response('Unauthorized', { status: 401 })
return Response.json({ id: user.id, email: user.email })
}Core API
Import and use headless functions directly:
import {
getUser,
handleAuthCallback,
login,
logout,
signup,
oauthLogin,
onAuthChange,
getSettings,
} from '@netlify/identity'Login
import { login, AuthError } from '@netlify/identity'
async function handleLogin(email: string, password: string) {
try {
const user = await login(email, password)
showSuccess(`Welcome back, ${user.name ?? user.email}`)
} catch (error) {
if (error instanceof AuthError) {
showError(error.status === 401 ? 'Invalid email or password.' : error.message)
}
}
}Signup
After signup, check user.emailVerified to determine if the user was auto-confirmed or needs to confirm their email.
import { signup, AuthError } from '@netlify/identity'
async function handleSignup(email: string, password: string, name: string) {
try {
const user = await signup(email, password, { full_name: name })
if (user.emailVerified) {
// Autoconfirm ON — user is logged in immediately
showSuccess('Account created. You are now logged in.')
} else {
// Autoconfirm OFF — confirmation email sent
showSuccess('Check your email to confirm your account.')
}
} catch (error) {
if (error instanceof AuthError) {
showError(error.status === 403 ? 'Signups are not allowed.' : error.message)
}
}
}Logout
import { logout } from '@netlify/identity'
await logout()OAuth
OAuth is a two-step flow: oauthLogin(provider) redirects away from the site, then handleAuthCallback() processes the redirect when the user returns.
import { oauthLogin } from '@netlify/identity'
// Step 1: Redirect to provider (navigates away — never returns)
function handleOAuthClick(provider: 'google' | 'github' | 'gitlab' | 'bitbucket') {
oauthLogin(provider)
}Providers must be enabled in the dashboard before oauthLogin() works — see Dashboard configuration above. Registration is Open by default, so OAuth users can create accounts without any extra signup-related configuration; only the provider itself must be enabled.
Email/password is always available as a login method — there is no "Email provider" toggle in Identity settings, only External providers for OAuth. To restrict users to OAuth-only, omit the email/password form from your UI; the front-end is the gate.
Handling Callbacks
Always call handleAuthCallback() on page load in any app that uses OAuth, password recovery, invites, or email confirmation. It processes all callback types via the URL hash.
import { handleAuthCallback, AuthError } from '@netlify/identity'
async function processCallback() {
try {
const result = await handleAuthCallback()
if (!result) return // No callback hash — normal page load
switch (result.type) {
case 'oauth':
showSuccess(`Logged in as ${result.user?.email}`)
break
case 'confirmation':
showSuccess('Email confirmed. You are now logged in.')
break
case 'recovery':
// User is authenticated but must set a new password
showPasswordResetForm(result.user)
break
case 'invite':
// User must set a password to accept the invite
showInviteAcceptForm(result.token)
break
case 'email_change':
showSuccess('Email address updated.')
break
}
} catch (error) {
if (error instanceof AuthError) showError(error.message)
}
}Auth State
import { getUser, onAuthChange, AUTH_EVENTS } from '@netlify/identity'
// Check current user (never throws — returns null if not authenticated)
const user = await getUser()
// Subscribe to auth state changes (returns unsubscribe function)
const unsubscribe = onAuthChange((event, user) => {
switch (event) {
case AUTH_EVENTS.LOGIN:
console.log('Logged in:', user?.email)
break
case AUTH_EVENTS.LOGOUT:
console.log('Logged out')
break
case AUTH_EVENTS.TOKEN_REFRESH:
break
case AUTH_EVENTS.USER_UPDATED:
console.log('Profile updated:', user?.email)
break
case AUTH_EVENTS.RECOVERY:
console.log('Password recovery initiated')
break
}
})Settings-Driven UI
Fetch the project's Identity settings to conditionally render signup forms and OAuth buttons.
import { getSettings } from '@netlify/identity'
const settings = await getSettings()
// settings.autoconfirm — boolean
// settings.disableSignup — boolean
// settings.providers — Record<AuthProvider, boolean>
if (!settings.disableSignup) showSignupForm()
for (const [provider, enabled] of Object.entries(settings.providers)) {
if (enabled) showOAuthButton(provider)
}Minimal React Example
import { useEffect, useState } from 'react'
import {
getUser,
handleAuthCallback,
login,
logout,
oauthLogin,
onAuthChange,
} from '@netlify/identity'
function App() {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
;(async () => {
await handleAuthCallback()
setUser(await getUser())
setLoading(false)
})()
return onAuthChange((_event, currentUser) => setUser(currentUser))
}, [])
const handleLogin = async (email, password) => {
const currentUser = await login(email, password)
setUser(currentUser)
}
const handleGoogleLogin = () => oauthLogin('google')
const handleSignOut = async () => {
await logout()
setUser(null)
}
if (loading) return <p>Loading...</p>
// Render login form or user details based on `user` state
}Error Handling
@netlify/identity throws two error classes:
- `AuthError` — Thrown by auth operations. Has
message, optionalstatus(HTTP status code), and optionalcause. - `MissingIdentityError` — Thrown when Identity is not configured in the current environment.
getUser() and isAuthenticated() never throw — they return null and false respectively on failure.
| Status | Meaning |
|---|---|
| 401 | Invalid credentials or expired token |
| 403 | Action not allowed (e.g., signups disabled) |
| 422 | Validation error (e.g., weak password, malformed email) |
| 404 | User or resource not found |
Identity Event Functions
Special serverless functions that trigger on Identity lifecycle events. These use the legacy named `handler` export (not the modern default export).
Event names: identity-validate, identity-signup, identity-login
// netlify/functions/identity-signup.mts
import type { Handler, HandlerEvent, HandlerContext } from '@netlify/functions'
const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
const { user } = JSON.parse(event.body || '{}')
return {
statusCode: 200,
body: JSON.stringify({
app_metadata: {
...user.app_metadata,
roles: ['member'],
},
}),
}
}
export { handler }The response body replaces app_metadata and/or user_metadata on the user record — include all fields you want to keep.
Roles and Authorization
First Admin User
The first admin user cannot be created through code alone. You must direct the user to set it up through the Netlify UI:
1. Go to Project configuration > Identity in the Netlify dashboard (https://app.netlify.com/projects/<project-slug>/configuration/identity) 2. Click Invite users and enter the admin user's email address 3. After the user accepts the invite, click the user in the Identity list to open their detail page 4. In the Roles field, add the admin role and save
Once the first admin exists, subsequent users can be managed programmatically using Identity event functions (e.g., assigning roles in identity-signup) or role-based redirects.
- `app_metadata.roles` — Server-controlled. Only settable via the Netlify UI, admin API, or Identity event functions. Never let users set their own roles.
- `user_metadata` — User-controlled. Users can update via
updateUser({ data: { ... } }).
Role-Based Redirects
# netlify.toml
[[redirects]]
from = "/admin/*"
to = "/admin/:splat"
status = 200
conditions = { Role = ["admin"] }
[[redirects]]
from = "/admin/*"
to = "/"
status = 302Rules are evaluated top-to-bottom. The nf_jwt cookie is read by the CDN to evaluate role conditions.
References
- Advanced patterns — password recovery, invite acceptance, email change, session hydration, SSR integration
Advanced Identity Patterns
Password Recovery
Three-step flow: request recovery email, handle the callback, then set a new password.
import { requestPasswordRecovery, handleAuthCallback, updateUser, AuthError } from '@netlify/identity'
// Step 1: Send recovery email
async function handleForgotPassword(email: string) {
try {
await requestPasswordRecovery(email)
showSuccess('Check your email for a password reset link.')
} catch (error) {
if (error instanceof AuthError) showError(error.message)
}
}
// Step 2: handleAuthCallback() returns { type: 'recovery', user } — show password reset form
// (See the handleAuthCallback switch in SKILL.md)
// Step 3: Set new password
async function handlePasswordReset(newPassword: string) {
try {
await updateUser({ password: newPassword })
showSuccess('Password updated.')
} catch (error) {
if (error instanceof AuthError) showError(error.message)
}
}The recovery callback fires a 'recovery' auth event, not 'login'. The user is authenticated but should be prompted to set a new password before navigating away.
Invite Acceptance
When a user clicks an invite link, handleAuthCallback() returns { type: 'invite', user: null, token }. Use the token to accept the invite and set a password.
import { acceptInvite, AuthError } from '@netlify/identity'
async function handleAcceptInvite(token: string, password: string) {
try {
const user = await acceptInvite(token, password)
showSuccess(`Welcome, ${user.email}! Your account is ready.`)
} catch (error) {
if (error instanceof AuthError) showError(error.message)
}
}Email Change
When a user verifies an email change, handleAuthCallback() returns { type: 'email_change', user }. The user must be logged in when clicking the verification link.
import { verifyEmailChange, AuthError } from '@netlify/identity'
async function handleEmailChangeVerification(token: string) {
try {
const user = await verifyEmailChange(token)
showSuccess(`Email updated to ${user.email}`)
} catch (error) {
if (error instanceof AuthError) showError(error.message)
}
}Session Hydration
hydrateSession() bridges server-set cookies to the browser session. Call it on page load when using server-side login (e.g., login inside a Netlify Function followed by a redirect).
import { hydrateSession } from '@netlify/identity'
const user = await hydrateSession()
if (user) {
// Browser session is now in sync with server-set cookies
}getUser() auto-hydrates from the nf_jwt cookie if no browser session exists, so explicit hydrateSession() is only needed when you want to restore the full session (including token refresh timers) after a server-side login.
SSR Integration Patterns
For SSR frameworks, the recommended pattern is:
- Browser-side for auth mutations:
login(),signup(),logout(),oauthLogin() - Server-side for reading auth state:
getUser(),getSettings(),getIdentityConfig()
Browser-side auth mutations set the nf_jwt cookie and localStorage, and emit onAuthChange events. The server reads the cookie on the next request.
The library also supports server-side mutations (login(), signup(), logout() inside Netlify Functions), but these require the Netlify Functions runtime to set cookies. After a server-side mutation, use a full page navigation so the browser sends the new cookie.
Always use window.location.href (not framework router navigation) after server-side auth mutations in Next.js, TanStack Start, and SvelteKit. Remix redirect() is safe because Remix actions return real HTTP responses.
Full API Reference
For the complete API reference — all function signatures, type definitions, OAuth helpers, admin operations, session management, auth events, and framework-specific examples — read the package README:
node_modules/@netlify/identity/README.mdThe README is shipped with the npm package and is always in sync with the installed version.
Related skills
FAQ
Which Netlify Identity package should developers use?
netlify-identity requires @netlify/identity for all authentication tasks. The skill explicitly forbids netlify-identity-widget and gotrue-js because those packages are deprecated and lack current Netlify support.
What auth features does Netlify Identity provide?
Netlify Identity, built on GoTrue, supports user signups, logins, password recovery, user metadata, OAuth providers, and role-based access control. It issues JSON Web Tokens for protecting frontend routes and Netlify Functions.