← NetlifyCONTENT HISTORY

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.

WHAT CHANGED · RULE-BASED ANALYSIS

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

Before

[{"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}]

After

[{"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 JSON
Full technical diff · 1 changed fields

changed /included_files

BEFORE
[
  {
    "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
  }
]
AFTER
[
  {
    "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