Update to Netlify
Snapshot Sep 30, 2026 · 23:18 UTC · version 1.0.0
Collection source: not recorded for this historical snapshot. These snapshots do not have a confirmed matching collection source. Differences in file lists alone do not establish changes to the package.
Supporting file metadata differs
Newly listed paths: LICENSE.txt, agents/openai.yaml. This compares saved file lists, not package contents; a different collection source can change the list.
Observed in package metadata. These changes alone do not establish a new customer-facing feature.
Supporting files
[{"relative_path":"assets/netlify-small.svg","size_in_bytes":1291},{"relative_path":"assets/netlify.png","size_in_bytes":2686},{"relative_path":"references/advanced-patterns.md","size_in_bytes":4095}]
[{"relative_path":"LICENSE.txt","size_in_bytes":10776},{"relative_path":"agents/openai.yaml","size_in_bytes":335},{"relative_path":"assets/netlify-small.svg","size_in_bytes":1291},{"relative_path":"assets/netlify.png","size_in_bytes":268...
Compare saved observations
Download comparison JSONFull technical diff · 1 changed fields
changed /included_files
[
{
"relative_path": "assets/netlify-small.svg",
"size_in_bytes": 1291
},
{
"relative_path": "assets/netlify.png",
"size_in_bytes": 2686
},
{
"relative_path": "references/advanced-patterns.md",
"size_in_bytes": 4095
}
][
{
"relative_path": "LICENSE.txt",
"size_in_bytes": 10776
},
{
"relative_path": "agents/openai.yaml",
"size_in_bytes": 335
},
{
"relative_path": "assets/netlify-small.svg",
"size_in_bytes": 1291
},
{
"relative_path": "assets/netlify.png",
"size_in_bytes": 2686
},
{
"relative_path": "references/advanced-patterns.md",
"size_in_bytes": 4095
}
]Full snapshot data
{
"name": "netlify-identity",
"description": "Use when the task involves authentication, user signups, logins, password recovery, OAuth providers, role-based access control, or protecting routes and functions. Always use `@netlify/identity`. Never use `netlify-identity-widget` or `gotrue-js` — they are deprecated.",
"included_files": [
{
"relative_path": "LICENSE.txt",
"size_in_bytes": 10776
},
{
"relative_path": "agents/openai.yaml",
"size_in_bytes": 335
},
{
"relative_path": "assets/netlify-small.svg",
"size_in_bytes": 1291
},
{
"relative_path": "assets/netlify.png",
"size_in_bytes": 2686
},
{
"relative_path": "references/advanced-patterns.md",
"size_in_bytes": 4095
}
],
"skill_md_contents": "---\nname: netlify-identity\ndescription: Use when the task involves authentication, user signups, logins, password recovery, OAuth providers, role-based access control, or protecting routes and functions. Always use `@netlify/identity`. Never use `netlify-identity-widget` or `gotrue-js` — they are deprecated.\n---\n\n# Netlify Identity\n\nNetlify Identity is a user management service for signups, logins, password recovery, user metadata, and role-based access control. It is built on [GoTrue](https://github.com/netlify/gotrue) and issues JSON Web Tokens (JWTs).\n\n**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).\n\n## Setup\n\n```bash\nnpm install @netlify/identity\n```\n\nIdentity is automatically enabled when a deploy created by a Netlify Agent Runner session includes Identity code. Otherwise, it must be manually enabled in the UI. These are the default settings:\n\n- **Registration** — Open (anyone can sign up). Change to Invite only in **Project configuration > Identity** if needed.\n- **Autoconfirm** — Off (new signups require email confirmation). Enable in **Project configuration > Identity** to skip confirmation during development.\n\n### Local Development\n\nIdentity 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.\n\n## Quick Start\n\nLog in from the browser:\n\n```typescript\nimport { login, getUser } from '@netlify/identity'\n\nconst user = await login('user@example.com', '<password>')\nconsole.log(`Hello, ${user.name}`)\n\n// Later, check auth state\nconst currentUser = await getUser()\n```\n\nProtect a Netlify Function:\n\n```typescript\n// netlify/functions/protected.mts\nimport { getUser } from '@netlify/identity'\nimport type { Context } from '@netlify/functions'\n\nexport default async (req: Request, context: Context) => {\n const user = await getUser()\n if (!user) return new Response('Unauthorized', { status: 401 })\n return Response.json({ id: user.id, email: user.email })\n}\n```\n\n## Core API\n\nImport and use headless functions directly:\n\n```typescript\nimport {\n getUser,\n handleAuthCallback,\n login,\n logout,\n signup,\n oauthLogin,\n onAuthChange,\n getSettings,\n} from '@netlify/identity'\n```\n\n### Login\n\n```typescript\nimport { login, AuthError } from '@netlify/identity'\n\nasync function handleLogin(email: string, password: string) {\n try {\n const user = await login(email, password)\n showSuccess(`Welcome back, ${user.name ?? user.email}`)\n } catch (error) {\n if (error instanceof AuthError) {\n showError(error.status === 401 ? 'Invalid email or password.' : error.message)\n }\n }\n}\n```\n\n### Signup\n\nAfter signup, check `user.emailVerified` to determine if the user was auto-confirmed or needs to confirm their email.\n\n```typescript\nimport { signup, AuthError } from '@netlify/identity'\n\nasync function handleSignup(email: string, password: string, name: string) {\n try {\n const user = await signup(email, password, { full_name: name })\n if (user.emailVerified) {\n // Autoconfirm ON — user is logged in immediately\n showSuccess('Account created. You are now logged in.')\n } else {\n // Autoconfirm OFF — confirmation email sent\n showSuccess('Check your email to confirm your account.')\n }\n } catch (error) {\n if (error instanceof AuthError) {\n showError(error.status === 403 ? 'Signups are not allowed.' : error.message)\n }\n }\n}\n```\n\n### Logout\n\n```typescript\nimport { logout } from '@netlify/identity'\n\nawait logout()\n```\n\n### OAuth\n\nOAuth is a two-step flow: `oauthLogin(provider)` redirects away from the site, then `handleAuthCallback()` processes the redirect when the user returns.\n\n```typescript\nimport { oauthLogin } from '@netlify/identity'\n\n// Step 1: Redirect to provider (navigates away — never returns)\nfunction handleOAuthClick(provider: 'google' | 'github' | 'gitlab' | 'bitbucket') {\n oauthLogin(provider)\n}\n```\n\nEnable providers in **Project configuration > Identity > External providers** before using OAuth.\n\n### Handling Callbacks\n\nAlways 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.\n\n```typescript\nimport { handleAuthCallback, AuthError } from '@netlify/identity'\n\nasync function processCallback() {\n try {\n const result = await handleAuthCallback()\n if (!result) return // No callback hash — normal page load\n\n switch (result.type) {\n case 'oauth':\n showSuccess(`Logged in as ${result.user?.email}`)\n break\n case 'confirmation':\n showSuccess('Email confirmed. You are now logged in.')\n break\n case 'recovery':\n // User is authenticated but must set a new password\n showPasswordResetForm(result.user)\n break\n case 'invite':\n // User must set a password to accept the invite\n showInviteAcceptForm(result.token)\n break\n case 'email_change':\n showSuccess('Email address updated.')\n break\n }\n } catch (error) {\n if (error instanceof AuthError) showError(error.message)\n }\n}\n```\n\n### Auth State\n\n```typescript\nimport { getUser, onAuthChange, AUTH_EVENTS } from '@netlify/identity'\n\n// Check current user (never throws — returns null if not authenticated)\nconst user = await getUser()\n\n// Subscribe to auth state changes (returns unsubscribe function)\nconst unsubscribe = onAuthChange((event, user) => {\n switch (event) {\n case AUTH_EVENTS.LOGIN:\n console.log('Logged in:', user?.email)\n break\n case AUTH_EVENTS.LOGOUT:\n console.log('Logged out')\n break\n case AUTH_EVENTS.TOKEN_REFRESH:\n break\n case AUTH_EVENTS.USER_UPDATED:\n console.log('Profile updated:', user?.email)\n break\n case AUTH_EVENTS.RECOVERY:\n console.log('Password recovery initiated')\n break\n }\n})\n```\n\n### Settings-Driven UI\n\nFetch the project's Identity settings to conditionally render signup forms and OAuth buttons.\n\n```typescript\nimport { getSettings } from '@netlify/identity'\n\nconst settings = await getSettings()\n// settings.autoconfirm — boolean\n// settings.disableSignup — boolean\n// settings.providers — Record<AuthProvider, boolean>\n\nif (!settings.disableSignup) showSignupForm()\n\nfor (const [provider, enabled] of Object.entries(settings.providers)) {\n if (enabled) showOAuthButton(provider)\n}\n```\n\n## Minimal React Example\n\n```tsx\nimport { useEffect, useState } from 'react'\nimport {\n getUser,\n handleAuthCallback,\n login,\n logout,\n oauthLogin,\n onAuthChange,\n} from '@netlify/identity'\n\nfunction App() {\n const [user, setUser] = useState(null)\n const [loading, setLoading] = useState(true)\n\n useEffect(() => {\n ;(async () => {\n await handleAuthCallback()\n setUser(await getUser())\n setLoading(false)\n })()\n return onAuthChange((_event, currentUser) => setUser(currentUser))\n }, [])\n\n const handleLogin = async (email, password) => {\n const currentUser = await login(email, password)\n setUser(currentUser)\n }\n\n const handleGoogleLogin = () => oauthLogin('google')\n\n const handleSignOut = async () => {\n await logout()\n setUser(null)\n }\n\n if (loading) return <p>Loading...</p>\n // Render login form or user details based on `user` state\n}\n```\n\n## Error Handling\n\n`@netlify/identity` throws two error classes:\n\n- **`AuthError`** — Thrown by auth operations. Has `message`, optional `status` (HTTP status code), and optional `cause`.\n- **`MissingIdentityError`** — Thrown when Identity is not configured in the current environment.\n\n`getUser()` and `isAuthenticated()` never throw — they return `null` and `false` respectively on failure.\n\n| Status | Meaning |\n|--------|---------|\n| 401 | Invalid credentials or expired token |\n| 403 | Action not allowed (e.g., signups disabled) |\n| 422 | Validation error (e.g., weak password, malformed email) |\n| 404 | User or resource not found |\n\n## Identity Event Functions\n\nSpecial serverless functions that trigger on Identity lifecycle events. These use the **legacy named `handler` export** (not the modern default export).\n\n**Event names:** `identity-validate`, `identity-signup`, `identity-login`\n\n```typescript\n// netlify/functions/identity-signup.mts\nimport type { Handler, HandlerEvent, HandlerContext } from '@netlify/functions'\n\nconst handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {\n const { user } = JSON.parse(event.body || '{}')\n\n return {\n statusCode: 200,\n body: JSON.stringify({\n app_metadata: {\n ...user.app_metadata,\n roles: ['member'],\n },\n }),\n }\n}\n\nexport { handler }\n```\n\nThe response body replaces `app_metadata` and/or `user_metadata` on the user record — include all fields you want to keep.\n\n## Roles and Authorization\n\n- **`app_metadata.roles`** — Server-controlled. Only settable via the Netlify UI, admin API, or Identity event functions. Never let users set their own roles.\n- **`user_metadata`** — User-controlled. Users can update via `updateUser({ data: { ... } })`.\n\n### Role-Based Redirects\n\n```toml\n# netlify.toml\n[[redirects]]\n from = \"/admin/*\"\n to = \"/admin/:splat\"\n status = 200\n conditions = { Role = [\"admin\"] }\n\n[[redirects]]\n from = \"/admin/*\"\n to = \"/\"\n status = 302\n```\n\nRules are evaluated top-to-bottom. The `nf_jwt` cookie is read by the CDN to evaluate role conditions.\n\n## Bundled References (Load As Needed)\n\n- [Advanced patterns](references/advanced-patterns.md) — password recovery, invite acceptance, email change, session hydration, SSR integration\n"
}SHA-256: 11575981e3dbf325e79df95d3f049907e006f9d5f42bcd89aa4901be5a532abc