#
tokens: 49190/50000 23/1099 files (page 12/69)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 12 of 69. Use http://codebase.md/better-auth/better-auth?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .gitattributes
├── .github
│   ├── CODEOWNERS
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   └── feature_request.yml
│   ├── renovate.json5
│   └── workflows
│       ├── ci.yml
│       ├── e2e.yml
│       ├── preview.yml
│       └── release.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .vscode
│   └── settings.json
├── banner-dark.png
├── banner.png
├── biome.json
├── bump.config.ts
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── demo
│   ├── expo-example
│   │   ├── .env.example
│   │   ├── .gitignore
│   │   ├── app.config.ts
│   │   ├── assets
│   │   │   ├── bg-image.jpeg
│   │   │   ├── fonts
│   │   │   │   └── SpaceMono-Regular.ttf
│   │   │   ├── icon.png
│   │   │   └── images
│   │   │       ├── adaptive-icon.png
│   │   │       ├── favicon.png
│   │   │       ├── logo.png
│   │   │       ├── partial-react-logo.png
│   │   │       ├── react-logo.png
│   │   │       ├── [email protected]
│   │   │       ├── [email protected]
│   │   │       └── splash.png
│   │   ├── babel.config.js
│   │   ├── components.json
│   │   ├── expo-env.d.ts
│   │   ├── index.ts
│   │   ├── metro.config.js
│   │   ├── nativewind-env.d.ts
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── app
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── api
│   │   │   │   │   └── auth
│   │   │   │   │       └── [...route]+api.ts
│   │   │   │   ├── dashboard.tsx
│   │   │   │   ├── forget-password.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   └── sign-up.tsx
│   │   │   ├── components
│   │   │   │   ├── icons
│   │   │   │   │   └── google.tsx
│   │   │   │   └── ui
│   │   │   │       ├── avatar.tsx
│   │   │   │       ├── button.tsx
│   │   │   │       ├── card.tsx
│   │   │   │       ├── dialog.tsx
│   │   │   │       ├── input.tsx
│   │   │   │       ├── separator.tsx
│   │   │   │       └── text.tsx
│   │   │   ├── global.css
│   │   │   └── lib
│   │   │       ├── auth-client.ts
│   │   │       ├── auth.ts
│   │   │       ├── icons
│   │   │       │   ├── iconWithClassName.ts
│   │   │       │   └── X.tsx
│   │   │       └── utils.ts
│   │   ├── tailwind.config.js
│   │   └── tsconfig.json
│   └── nextjs
│       ├── .env.example
│       ├── .gitignore
│       ├── app
│       │   ├── (auth)
│       │   │   ├── forget-password
│       │   │   │   └── page.tsx
│       │   │   ├── reset-password
│       │   │   │   └── page.tsx
│       │   │   ├── sign-in
│       │   │   │   ├── loading.tsx
│       │   │   │   └── page.tsx
│       │   │   └── two-factor
│       │   │       ├── otp
│       │   │       │   └── page.tsx
│       │   │       └── page.tsx
│       │   ├── accept-invitation
│       │   │   └── [id]
│       │   │       ├── invitation-error.tsx
│       │   │       └── page.tsx
│       │   ├── admin
│       │   │   └── page.tsx
│       │   ├── api
│       │   │   └── auth
│       │   │       └── [...all]
│       │   │           └── route.ts
│       │   ├── apps
│       │   │   └── register
│       │   │       └── page.tsx
│       │   ├── client-test
│       │   │   └── page.tsx
│       │   ├── dashboard
│       │   │   ├── change-plan.tsx
│       │   │   ├── client.tsx
│       │   │   ├── organization-card.tsx
│       │   │   ├── page.tsx
│       │   │   ├── upgrade-button.tsx
│       │   │   └── user-card.tsx
│       │   ├── device
│       │   │   ├── approve
│       │   │   │   └── page.tsx
│       │   │   ├── denied
│       │   │   │   └── page.tsx
│       │   │   ├── layout.tsx
│       │   │   ├── page.tsx
│       │   │   └── success
│       │   │       └── page.tsx
│       │   ├── favicon.ico
│       │   ├── features.tsx
│       │   ├── fonts
│       │   │   ├── GeistMonoVF.woff
│       │   │   └── GeistVF.woff
│       │   ├── globals.css
│       │   ├── layout.tsx
│       │   ├── oauth
│       │   │   └── authorize
│       │   │       ├── concet-buttons.tsx
│       │   │       └── page.tsx
│       │   ├── page.tsx
│       │   └── pricing
│       │       └── page.tsx
│       ├── components
│       │   ├── account-switch.tsx
│       │   ├── blocks
│       │   │   └── pricing.tsx
│       │   ├── logo.tsx
│       │   ├── one-tap.tsx
│       │   ├── sign-in-btn.tsx
│       │   ├── sign-in.tsx
│       │   ├── sign-up.tsx
│       │   ├── theme-provider.tsx
│       │   ├── theme-toggle.tsx
│       │   ├── tier-labels.tsx
│       │   ├── ui
│       │   │   ├── accordion.tsx
│       │   │   ├── alert-dialog.tsx
│       │   │   ├── alert.tsx
│       │   │   ├── aspect-ratio.tsx
│       │   │   ├── avatar.tsx
│       │   │   ├── badge.tsx
│       │   │   ├── breadcrumb.tsx
│       │   │   ├── button.tsx
│       │   │   ├── calendar.tsx
│       │   │   ├── card.tsx
│       │   │   ├── carousel.tsx
│       │   │   ├── chart.tsx
│       │   │   ├── checkbox.tsx
│       │   │   ├── collapsible.tsx
│       │   │   ├── command.tsx
│       │   │   ├── context-menu.tsx
│       │   │   ├── copy-button.tsx
│       │   │   ├── dialog.tsx
│       │   │   ├── drawer.tsx
│       │   │   ├── dropdown-menu.tsx
│       │   │   ├── form.tsx
│       │   │   ├── hover-card.tsx
│       │   │   ├── input-otp.tsx
│       │   │   ├── input.tsx
│       │   │   ├── label.tsx
│       │   │   ├── menubar.tsx
│       │   │   ├── navigation-menu.tsx
│       │   │   ├── pagination.tsx
│       │   │   ├── password-input.tsx
│       │   │   ├── popover.tsx
│       │   │   ├── progress.tsx
│       │   │   ├── radio-group.tsx
│       │   │   ├── resizable.tsx
│       │   │   ├── scroll-area.tsx
│       │   │   ├── select.tsx
│       │   │   ├── separator.tsx
│       │   │   ├── sheet.tsx
│       │   │   ├── skeleton.tsx
│       │   │   ├── slider.tsx
│       │   │   ├── sonner.tsx
│       │   │   ├── switch.tsx
│       │   │   ├── table.tsx
│       │   │   ├── tabs.tsx
│       │   │   ├── tabs2.tsx
│       │   │   ├── textarea.tsx
│       │   │   ├── toast.tsx
│       │   │   ├── toaster.tsx
│       │   │   ├── toggle-group.tsx
│       │   │   ├── toggle.tsx
│       │   │   └── tooltip.tsx
│       │   └── wrapper.tsx
│       ├── components.json
│       ├── hooks
│       │   └── use-toast.ts
│       ├── lib
│       │   ├── auth-client.ts
│       │   ├── auth-types.ts
│       │   ├── auth.ts
│       │   ├── email
│       │   │   ├── invitation.tsx
│       │   │   ├── resend.ts
│       │   │   └── reset-password.tsx
│       │   ├── metadata.ts
│       │   ├── shared.ts
│       │   └── utils.ts
│       ├── next.config.ts
│       ├── package.json
│       ├── postcss.config.mjs
│       ├── proxy.ts
│       ├── public
│       │   ├── __og.png
│       │   ├── _og.png
│       │   ├── favicon
│       │   │   ├── android-chrome-192x192.png
│       │   │   ├── android-chrome-512x512.png
│       │   │   ├── apple-touch-icon.png
│       │   │   ├── favicon-16x16.png
│       │   │   ├── favicon-32x32.png
│       │   │   ├── favicon.ico
│       │   │   ├── light
│       │   │   │   ├── android-chrome-192x192.png
│       │   │   │   ├── android-chrome-512x512.png
│       │   │   │   ├── apple-touch-icon.png
│       │   │   │   ├── favicon-16x16.png
│       │   │   │   ├── favicon-32x32.png
│       │   │   │   ├── favicon.ico
│       │   │   │   └── site.webmanifest
│       │   │   └── site.webmanifest
│       │   ├── logo.svg
│       │   └── og.png
│       ├── README.md
│       ├── tailwind.config.ts
│       ├── tsconfig.json
│       └── turbo.json
├── docker-compose.yml
├── docs
│   ├── .env.example
│   ├── .gitignore
│   ├── app
│   │   ├── api
│   │   │   ├── ai-chat
│   │   │   │   └── route.ts
│   │   │   ├── analytics
│   │   │   │   ├── conversation
│   │   │   │   │   └── route.ts
│   │   │   │   ├── event
│   │   │   │   │   └── route.ts
│   │   │   │   └── feedback
│   │   │   │       └── route.ts
│   │   │   ├── chat
│   │   │   │   └── route.ts
│   │   │   ├── og
│   │   │   │   └── route.tsx
│   │   │   ├── og-release
│   │   │   │   └── route.tsx
│   │   │   ├── search
│   │   │   │   └── route.ts
│   │   │   └── support
│   │   │       └── route.ts
│   │   ├── blog
│   │   │   ├── _components
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── blog-list.tsx
│   │   │   │   ├── changelog-layout.tsx
│   │   │   │   ├── default-changelog.tsx
│   │   │   │   ├── fmt-dates.tsx
│   │   │   │   ├── icons.tsx
│   │   │   │   ├── stat-field.tsx
│   │   │   │   └── support.tsx
│   │   │   ├── [[...slug]]
│   │   │   │   └── page.tsx
│   │   │   └── layout.tsx
│   │   ├── changelogs
│   │   │   ├── _components
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── changelog-layout.tsx
│   │   │   │   ├── default-changelog.tsx
│   │   │   │   ├── fmt-dates.tsx
│   │   │   │   ├── grid-pattern.tsx
│   │   │   │   ├── icons.tsx
│   │   │   │   └── stat-field.tsx
│   │   │   ├── [[...slug]]
│   │   │   │   └── page.tsx
│   │   │   └── layout.tsx
│   │   ├── community
│   │   │   ├── _components
│   │   │   │   ├── header.tsx
│   │   │   │   └── stats.tsx
│   │   │   └── page.tsx
│   │   ├── docs
│   │   │   ├── [[...slug]]
│   │   │   │   ├── page.client.tsx
│   │   │   │   └── page.tsx
│   │   │   ├── layout.tsx
│   │   │   └── lib
│   │   │       └── get-llm-text.ts
│   │   ├── global.css
│   │   ├── layout.config.tsx
│   │   ├── layout.tsx
│   │   ├── llms.txt
│   │   │   ├── [...slug]
│   │   │   │   └── route.ts
│   │   │   └── route.ts
│   │   ├── not-found.tsx
│   │   ├── page.tsx
│   │   ├── reference
│   │   │   └── route.ts
│   │   ├── sitemap.xml
│   │   ├── static.json
│   │   │   └── route.ts
│   │   └── v1
│   │       ├── _components
│   │       │   └── v1-text.tsx
│   │       ├── bg-line.tsx
│   │       └── page.tsx
│   ├── assets
│   │   ├── Geist.ttf
│   │   └── GeistMono.ttf
│   ├── components
│   │   ├── ai-chat-modal.tsx
│   │   ├── anchor-scroll-fix.tsx
│   │   ├── api-method-tabs.tsx
│   │   ├── api-method.tsx
│   │   ├── banner.tsx
│   │   ├── blocks
│   │   │   └── features.tsx
│   │   ├── builder
│   │   │   ├── beam.tsx
│   │   │   ├── code-tabs
│   │   │   │   ├── code-editor.tsx
│   │   │   │   ├── code-tabs.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   ├── tab-bar.tsx
│   │   │   │   └── theme.ts
│   │   │   ├── index.tsx
│   │   │   ├── sign-in.tsx
│   │   │   ├── sign-up.tsx
│   │   │   ├── social-provider.tsx
│   │   │   ├── store.ts
│   │   │   └── tabs.tsx
│   │   ├── display-techstack.tsx
│   │   ├── divider-text.tsx
│   │   ├── docs
│   │   │   ├── docs.client.tsx
│   │   │   ├── docs.tsx
│   │   │   ├── layout
│   │   │   │   ├── nav.tsx
│   │   │   │   ├── theme-toggle.tsx
│   │   │   │   ├── toc-thumb.tsx
│   │   │   │   └── toc.tsx
│   │   │   ├── page.client.tsx
│   │   │   ├── page.tsx
│   │   │   ├── shared.tsx
│   │   │   └── ui
│   │   │       ├── button.tsx
│   │   │       ├── collapsible.tsx
│   │   │       ├── popover.tsx
│   │   │       └── scroll-area.tsx
│   │   ├── endpoint.tsx
│   │   ├── features.tsx
│   │   ├── floating-ai-search.tsx
│   │   ├── fork-button.tsx
│   │   ├── generate-apple-jwt.tsx
│   │   ├── generate-secret.tsx
│   │   ├── github-stat.tsx
│   │   ├── icons.tsx
│   │   ├── landing
│   │   │   ├── gradient-bg.tsx
│   │   │   ├── grid-pattern.tsx
│   │   │   ├── hero.tsx
│   │   │   ├── section-svg.tsx
│   │   │   ├── section.tsx
│   │   │   ├── spotlight.tsx
│   │   │   └── testimonials.tsx
│   │   ├── logo-context-menu.tsx
│   │   ├── logo.tsx
│   │   ├── markdown-renderer.tsx
│   │   ├── markdown.tsx
│   │   ├── mdx
│   │   │   ├── add-to-cursor.tsx
│   │   │   └── database-tables.tsx
│   │   ├── message-feedback.tsx
│   │   ├── mobile-search-icon.tsx
│   │   ├── nav-bar.tsx
│   │   ├── nav-link.tsx
│   │   ├── nav-mobile.tsx
│   │   ├── promo-card.tsx
│   │   ├── resource-card.tsx
│   │   ├── resource-grid.tsx
│   │   ├── resource-section.tsx
│   │   ├── ripple.tsx
│   │   ├── search-dialog.tsx
│   │   ├── side-bar.tsx
│   │   ├── sidebar-content.tsx
│   │   ├── techstack-icons.tsx
│   │   ├── theme-provider.tsx
│   │   ├── theme-toggler.tsx
│   │   └── ui
│   │       ├── accordion.tsx
│   │       ├── alert-dialog.tsx
│   │       ├── alert.tsx
│   │       ├── aside-link.tsx
│   │       ├── aspect-ratio.tsx
│   │       ├── avatar.tsx
│   │       ├── background-beams.tsx
│   │       ├── background-boxes.tsx
│   │       ├── badge.tsx
│   │       ├── breadcrumb.tsx
│   │       ├── button.tsx
│   │       ├── calendar.tsx
│   │       ├── callout.tsx
│   │       ├── card.tsx
│   │       ├── carousel.tsx
│   │       ├── chart.tsx
│   │       ├── checkbox.tsx
│   │       ├── code-block.tsx
│   │       ├── collapsible.tsx
│   │       ├── command.tsx
│   │       ├── context-menu.tsx
│   │       ├── dialog.tsx
│   │       ├── drawer.tsx
│   │       ├── dropdown-menu.tsx
│   │       ├── dynamic-code-block.tsx
│   │       ├── fade-in.tsx
│   │       ├── form.tsx
│   │       ├── hover-card.tsx
│   │       ├── input-otp.tsx
│   │       ├── input.tsx
│   │       ├── label.tsx
│   │       ├── menubar.tsx
│   │       ├── navigation-menu.tsx
│   │       ├── pagination.tsx
│   │       ├── popover.tsx
│   │       ├── progress.tsx
│   │       ├── radio-group.tsx
│   │       ├── resizable.tsx
│   │       ├── scroll-area.tsx
│   │       ├── select.tsx
│   │       ├── separator.tsx
│   │       ├── sheet.tsx
│   │       ├── sidebar.tsx
│   │       ├── skeleton.tsx
│   │       ├── slider.tsx
│   │       ├── sonner.tsx
│   │       ├── sparkles.tsx
│   │       ├── switch.tsx
│   │       ├── table.tsx
│   │       ├── tabs.tsx
│   │       ├── textarea.tsx
│   │       ├── toggle-group.tsx
│   │       ├── toggle.tsx
│   │       ├── tooltip-docs.tsx
│   │       ├── tooltip.tsx
│   │       └── use-copy-button.tsx
│   ├── components.json
│   ├── content
│   │   ├── blogs
│   │   │   ├── 0-supabase-auth-to-planetscale-migration.mdx
│   │   │   ├── 1-3.mdx
│   │   │   ├── authjs-joins-better-auth.mdx
│   │   │   └── seed-round.mdx
│   │   ├── changelogs
│   │   │   ├── 1-2.mdx
│   │   │   └── 1.0.mdx
│   │   └── docs
│   │       ├── adapters
│   │       │   ├── community-adapters.mdx
│   │       │   ├── drizzle.mdx
│   │       │   ├── mongo.mdx
│   │       │   ├── mssql.mdx
│   │       │   ├── mysql.mdx
│   │       │   ├── other-relational-databases.mdx
│   │       │   ├── postgresql.mdx
│   │       │   ├── prisma.mdx
│   │       │   └── sqlite.mdx
│   │       ├── authentication
│   │       │   ├── apple.mdx
│   │       │   ├── atlassian.mdx
│   │       │   ├── cognito.mdx
│   │       │   ├── discord.mdx
│   │       │   ├── dropbox.mdx
│   │       │   ├── email-password.mdx
│   │       │   ├── facebook.mdx
│   │       │   ├── figma.mdx
│   │       │   ├── github.mdx
│   │       │   ├── gitlab.mdx
│   │       │   ├── google.mdx
│   │       │   ├── huggingface.mdx
│   │       │   ├── kakao.mdx
│   │       │   ├── kick.mdx
│   │       │   ├── line.mdx
│   │       │   ├── linear.mdx
│   │       │   ├── linkedin.mdx
│   │       │   ├── microsoft.mdx
│   │       │   ├── naver.mdx
│   │       │   ├── notion.mdx
│   │       │   ├── other-social-providers.mdx
│   │       │   ├── paypal.mdx
│   │       │   ├── reddit.mdx
│   │       │   ├── roblox.mdx
│   │       │   ├── salesforce.mdx
│   │       │   ├── slack.mdx
│   │       │   ├── spotify.mdx
│   │       │   ├── tiktok.mdx
│   │       │   ├── twitch.mdx
│   │       │   ├── twitter.mdx
│   │       │   ├── vk.mdx
│   │       │   └── zoom.mdx
│   │       ├── basic-usage.mdx
│   │       ├── comparison.mdx
│   │       ├── concepts
│   │       │   ├── api.mdx
│   │       │   ├── cli.mdx
│   │       │   ├── client.mdx
│   │       │   ├── cookies.mdx
│   │       │   ├── database.mdx
│   │       │   ├── email.mdx
│   │       │   ├── hooks.mdx
│   │       │   ├── oauth.mdx
│   │       │   ├── plugins.mdx
│   │       │   ├── rate-limit.mdx
│   │       │   ├── session-management.mdx
│   │       │   ├── typescript.mdx
│   │       │   └── users-accounts.mdx
│   │       ├── examples
│   │       │   ├── astro.mdx
│   │       │   ├── next-js.mdx
│   │       │   ├── nuxt.mdx
│   │       │   ├── remix.mdx
│   │       │   └── svelte-kit.mdx
│   │       ├── guides
│   │       │   ├── auth0-migration-guide.mdx
│   │       │   ├── browser-extension-guide.mdx
│   │       │   ├── clerk-migration-guide.mdx
│   │       │   ├── create-a-db-adapter.mdx
│   │       │   ├── next-auth-migration-guide.mdx
│   │       │   ├── optimizing-for-performance.mdx
│   │       │   ├── saml-sso-with-okta.mdx
│   │       │   ├── supabase-migration-guide.mdx
│   │       │   └── your-first-plugin.mdx
│   │       ├── installation.mdx
│   │       ├── integrations
│   │       │   ├── astro.mdx
│   │       │   ├── convex.mdx
│   │       │   ├── elysia.mdx
│   │       │   ├── expo.mdx
│   │       │   ├── express.mdx
│   │       │   ├── fastify.mdx
│   │       │   ├── hono.mdx
│   │       │   ├── lynx.mdx
│   │       │   ├── nestjs.mdx
│   │       │   ├── next.mdx
│   │       │   ├── nitro.mdx
│   │       │   ├── nuxt.mdx
│   │       │   ├── remix.mdx
│   │       │   ├── solid-start.mdx
│   │       │   ├── svelte-kit.mdx
│   │       │   ├── tanstack.mdx
│   │       │   └── waku.mdx
│   │       ├── introduction.mdx
│   │       ├── meta.json
│   │       ├── plugins
│   │       │   ├── 2fa.mdx
│   │       │   ├── admin.mdx
│   │       │   ├── anonymous.mdx
│   │       │   ├── api-key.mdx
│   │       │   ├── autumn.mdx
│   │       │   ├── bearer.mdx
│   │       │   ├── captcha.mdx
│   │       │   ├── community-plugins.mdx
│   │       │   ├── device-authorization.mdx
│   │       │   ├── dodopayments.mdx
│   │       │   ├── dub.mdx
│   │       │   ├── email-otp.mdx
│   │       │   ├── generic-oauth.mdx
│   │       │   ├── have-i-been-pwned.mdx
│   │       │   ├── jwt.mdx
│   │       │   ├── last-login-method.mdx
│   │       │   ├── magic-link.mdx
│   │       │   ├── mcp.mdx
│   │       │   ├── multi-session.mdx
│   │       │   ├── oauth-proxy.mdx
│   │       │   ├── oidc-provider.mdx
│   │       │   ├── one-tap.mdx
│   │       │   ├── one-time-token.mdx
│   │       │   ├── open-api.mdx
│   │       │   ├── organization.mdx
│   │       │   ├── passkey.mdx
│   │       │   ├── phone-number.mdx
│   │       │   ├── polar.mdx
│   │       │   ├── siwe.mdx
│   │       │   ├── sso.mdx
│   │       │   ├── stripe.mdx
│   │       │   └── username.mdx
│   │       └── reference
│   │           ├── contributing.mdx
│   │           ├── faq.mdx
│   │           ├── options.mdx
│   │           ├── resources.mdx
│   │           ├── security.mdx
│   │           └── telemetry.mdx
│   ├── hooks
│   │   └── use-mobile.ts
│   ├── ignore-build.sh
│   ├── lib
│   │   ├── blog.ts
│   │   ├── chat
│   │   │   └── inkeep-qa-schema.ts
│   │   ├── constants.ts
│   │   ├── export-search-indexes.ts
│   │   ├── inkeep-analytics.ts
│   │   ├── is-active.ts
│   │   ├── metadata.ts
│   │   ├── source.ts
│   │   └── utils.ts
│   ├── next.config.js
│   ├── package.json
│   ├── postcss.config.js
│   ├── proxy.ts
│   ├── public
│   │   ├── avatars
│   │   │   └── beka.jpg
│   │   ├── blogs
│   │   │   ├── authjs-joins.png
│   │   │   ├── seed-round.png
│   │   │   └── supabase-ps.png
│   │   ├── branding
│   │   │   ├── better-auth-brand-assets.zip
│   │   │   ├── better-auth-logo-dark.png
│   │   │   ├── better-auth-logo-dark.svg
│   │   │   ├── better-auth-logo-light.png
│   │   │   ├── better-auth-logo-light.svg
│   │   │   ├── better-auth-logo-wordmark-dark.png
│   │   │   ├── better-auth-logo-wordmark-dark.svg
│   │   │   ├── better-auth-logo-wordmark-light.png
│   │   │   └── better-auth-logo-wordmark-light.svg
│   │   ├── extension-id.png
│   │   ├── favicon
│   │   │   ├── android-chrome-192x192.png
│   │   │   ├── android-chrome-512x512.png
│   │   │   ├── apple-touch-icon.png
│   │   │   ├── favicon-16x16.png
│   │   │   ├── favicon-32x32.png
│   │   │   ├── favicon.ico
│   │   │   ├── light
│   │   │   │   ├── android-chrome-192x192.png
│   │   │   │   ├── android-chrome-512x512.png
│   │   │   │   ├── apple-touch-icon.png
│   │   │   │   ├── favicon-16x16.png
│   │   │   │   ├── favicon-32x32.png
│   │   │   │   ├── favicon.ico
│   │   │   │   └── site.webmanifest
│   │   │   └── site.webmanifest
│   │   ├── images
│   │   │   └── blogs
│   │   │       └── better auth (1).png
│   │   ├── logo.png
│   │   ├── logo.svg
│   │   ├── LogoDark.webp
│   │   ├── LogoLight.webp
│   │   ├── og.png
│   │   ├── open-api-reference.png
│   │   ├── people-say
│   │   │   ├── code-with-antonio.jpg
│   │   │   ├── dagmawi-babi.png
│   │   │   ├── dax.png
│   │   │   ├── dev-ed.png
│   │   │   ├── egoist.png
│   │   │   ├── guillermo-rauch.png
│   │   │   ├── jonathan-wilke.png
│   │   │   ├── josh-tried-coding.jpg
│   │   │   ├── kitze.jpg
│   │   │   ├── lazar-nikolov.png
│   │   │   ├── nizzy.png
│   │   │   ├── omar-mcadam.png
│   │   │   ├── ryan-vogel.jpg
│   │   │   ├── saltyatom.jpg
│   │   │   ├── sebastien-chopin.png
│   │   │   ├── shreyas-mididoddi.png
│   │   │   ├── tech-nerd.png
│   │   │   ├── theo.png
│   │   │   ├── vybhav-bhargav.png
│   │   │   └── xavier-pladevall.jpg
│   │   ├── plus.svg
│   │   ├── release-og
│   │   │   ├── 1-2.png
│   │   │   ├── 1-3.png
│   │   │   └── changelog-og.png
│   │   └── v1-og.png
│   ├── README.md
│   ├── scripts
│   │   ├── endpoint-to-doc
│   │   │   ├── index.ts
│   │   │   ├── input.ts
│   │   │   ├── output.mdx
│   │   │   └── readme.md
│   │   └── sync-orama.ts
│   ├── source.config.ts
│   ├── tailwind.config.js
│   ├── tsconfig.json
│   └── turbo.json
├── e2e
│   ├── integration
│   │   ├── package.json
│   │   ├── playwright.config.ts
│   │   ├── solid-vinxi
│   │   │   ├── .gitignore
│   │   │   ├── app.config.ts
│   │   │   ├── e2e
│   │   │   │   ├── test.spec.ts
│   │   │   │   └── utils.ts
│   │   │   ├── package.json
│   │   │   ├── public
│   │   │   │   └── favicon.ico
│   │   │   ├── src
│   │   │   │   ├── app.tsx
│   │   │   │   ├── entry-client.tsx
│   │   │   │   ├── entry-server.tsx
│   │   │   │   ├── global.d.ts
│   │   │   │   ├── lib
│   │   │   │   │   ├── auth-client.ts
│   │   │   │   │   └── auth.ts
│   │   │   │   └── routes
│   │   │   │       ├── [...404].tsx
│   │   │   │       ├── api
│   │   │   │       │   └── auth
│   │   │   │       │       └── [...all].ts
│   │   │   │       └── index.tsx
│   │   │   └── tsconfig.json
│   │   ├── test-utils
│   │   │   ├── package.json
│   │   │   └── src
│   │   │       └── playwright.ts
│   │   └── vanilla-node
│   │       ├── e2e
│   │       │   ├── app.ts
│   │       │   ├── domain.spec.ts
│   │       │   ├── postgres-js.spec.ts
│   │       │   ├── test.spec.ts
│   │       │   └── utils.ts
│   │       ├── index.html
│   │       ├── package.json
│   │       ├── src
│   │       │   ├── main.ts
│   │       │   └── vite-env.d.ts
│   │       ├── tsconfig.json
│   │       └── vite.config.ts
│   └── smoke
│       ├── package.json
│       ├── test
│       │   ├── bun.spec.ts
│       │   ├── cloudflare.spec.ts
│       │   ├── deno.spec.ts
│       │   ├── fixtures
│       │   │   ├── bun-simple.ts
│       │   │   ├── cloudflare
│       │   │   │   ├── .gitignore
│       │   │   │   ├── drizzle
│       │   │   │   │   ├── 0000_clean_vector.sql
│       │   │   │   │   └── meta
│       │   │   │   │       ├── _journal.json
│       │   │   │   │       └── 0000_snapshot.json
│       │   │   │   ├── drizzle.config.ts
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── auth-schema.ts
│       │   │   │   │   ├── db.ts
│       │   │   │   │   └── index.ts
│       │   │   │   ├── test
│       │   │   │   │   ├── apply-migrations.ts
│       │   │   │   │   ├── env.d.ts
│       │   │   │   │   └── index.test.ts
│       │   │   │   ├── tsconfig.json
│       │   │   │   ├── vitest.config.ts
│       │   │   │   ├── worker-configuration.d.ts
│       │   │   │   └── wrangler.json
│       │   │   ├── deno-simple.ts
│       │   │   ├── tsconfig-decelration
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── demo.ts
│       │   │   │   │   └── index.ts
│       │   │   │   └── tsconfig.json
│       │   │   ├── tsconfig-exact-optional-property-types
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── index.ts
│       │   │   │   │   ├── organization.ts
│       │   │   │   │   ├── user-additional-fields.ts
│       │   │   │   │   └── username.ts
│       │   │   │   └── tsconfig.json
│       │   │   ├── tsconfig-isolated-module-bundler
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   └── index.ts
│       │   │   │   └── tsconfig.json
│       │   │   ├── tsconfig-verbatim-module-syntax-node10
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   └── index.ts
│       │   │   │   └── tsconfig.json
│       │   │   └── vite
│       │   │       ├── package.json
│       │   │       ├── src
│       │   │       │   ├── client.ts
│       │   │       │   └── server.ts
│       │   │       ├── tsconfig.json
│       │   │       └── vite.config.ts
│       │   ├── ssr.ts
│       │   ├── typecheck.spec.ts
│       │   └── vite.spec.ts
│       └── tsconfig.json
├── LICENSE.md
├── package.json
├── packages
│   ├── better-auth
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── __snapshots__
│   │   │   │   └── init.test.ts.snap
│   │   │   ├── adapters
│   │   │   │   ├── adapter-factory
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── test
│   │   │   │   │   │   ├── __snapshots__
│   │   │   │   │   │   │   └── adapter-factory.test.ts.snap
│   │   │   │   │   │   └── adapter-factory.test.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── create-test-suite.ts
│   │   │   │   ├── drizzle-adapter
│   │   │   │   │   ├── drizzle-adapter.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── test
│   │   │   │   │       ├── .gitignore
│   │   │   │   │       ├── adapter.drizzle.mysql.test.ts
│   │   │   │   │       ├── adapter.drizzle.pg.test.ts
│   │   │   │   │       ├── adapter.drizzle.sqlite.test.ts
│   │   │   │   │       └── generate-schema.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── kysely-adapter
│   │   │   │   │   ├── bun-sqlite-dialect.ts
│   │   │   │   │   ├── dialect.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── kysely-adapter.ts
│   │   │   │   │   ├── node-sqlite-dialect.ts
│   │   │   │   │   ├── test
│   │   │   │   │   │   ├── adapter.kysely.mssql.test.ts
│   │   │   │   │   │   ├── adapter.kysely.mysql.test.ts
│   │   │   │   │   │   ├── adapter.kysely.pg.test.ts
│   │   │   │   │   │   ├── adapter.kysely.sqlite.test.ts
│   │   │   │   │   │   └── node-sqlite-dialect.test.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── memory-adapter
│   │   │   │   │   ├── adapter.memory.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── memory-adapter.ts
│   │   │   │   ├── mongodb-adapter
│   │   │   │   │   ├── adapter.mongo-db.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── mongodb-adapter.ts
│   │   │   │   ├── prisma-adapter
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── prisma-adapter.ts
│   │   │   │   │   └── test
│   │   │   │   │       ├── .gitignore
│   │   │   │   │       ├── base.prisma
│   │   │   │   │       ├── generate-auth-config.ts
│   │   │   │   │       ├── generate-prisma-schema.ts
│   │   │   │   │       ├── get-prisma-client.ts
│   │   │   │   │       ├── prisma.mysql.test.ts
│   │   │   │   │       ├── prisma.pg.test.ts
│   │   │   │   │       ├── prisma.sqlite.test.ts
│   │   │   │   │       └── push-prisma-schema.ts
│   │   │   │   ├── test-adapter.ts
│   │   │   │   ├── test.ts
│   │   │   │   ├── tests
│   │   │   │   │   ├── auth-flow.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── normal.ts
│   │   │   │   │   ├── number-id.ts
│   │   │   │   │   ├── performance.ts
│   │   │   │   │   └── transactions.ts
│   │   │   │   └── utils.ts
│   │   │   ├── api
│   │   │   │   ├── check-endpoint-conflicts.test.ts
│   │   │   │   ├── index.test.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── middlewares
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── origin-check.test.ts
│   │   │   │   │   └── origin-check.ts
│   │   │   │   ├── rate-limiter
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── rate-limiter.test.ts
│   │   │   │   ├── routes
│   │   │   │   │   ├── account.test.ts
│   │   │   │   │   ├── account.ts
│   │   │   │   │   ├── callback.ts
│   │   │   │   │   ├── email-verification.test.ts
│   │   │   │   │   ├── email-verification.ts
│   │   │   │   │   ├── error.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── ok.ts
│   │   │   │   │   ├── reset-password.test.ts
│   │   │   │   │   ├── reset-password.ts
│   │   │   │   │   ├── session-api.test.ts
│   │   │   │   │   ├── session.ts
│   │   │   │   │   ├── sign-in.test.ts
│   │   │   │   │   ├── sign-in.ts
│   │   │   │   │   ├── sign-out.test.ts
│   │   │   │   │   ├── sign-out.ts
│   │   │   │   │   ├── sign-up.test.ts
│   │   │   │   │   ├── sign-up.ts
│   │   │   │   │   ├── update-user.test.ts
│   │   │   │   │   └── update-user.ts
│   │   │   │   ├── to-auth-endpoints.test.ts
│   │   │   │   └── to-auth-endpoints.ts
│   │   │   ├── auth.test.ts
│   │   │   ├── auth.ts
│   │   │   ├── call.test.ts
│   │   │   ├── client
│   │   │   │   ├── client-ssr.test.ts
│   │   │   │   ├── client.test.ts
│   │   │   │   ├── config.ts
│   │   │   │   ├── fetch-plugins.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── lynx
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── lynx-store.ts
│   │   │   │   ├── parser.ts
│   │   │   │   ├── path-to-object.ts
│   │   │   │   ├── plugins
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── infer-plugin.ts
│   │   │   │   ├── proxy.ts
│   │   │   │   ├── query.ts
│   │   │   │   ├── react
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── react-store.ts
│   │   │   │   ├── session-atom.ts
│   │   │   │   ├── solid
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── solid-store.ts
│   │   │   │   ├── svelte
│   │   │   │   │   └── index.ts
│   │   │   │   ├── test-plugin.ts
│   │   │   │   ├── types.ts
│   │   │   │   ├── url.test.ts
│   │   │   │   ├── vanilla.ts
│   │   │   │   └── vue
│   │   │   │       ├── index.ts
│   │   │   │       └── vue-store.ts
│   │   │   ├── cookies
│   │   │   │   ├── check-cookies.ts
│   │   │   │   ├── cookie-utils.ts
│   │   │   │   ├── cookies.test.ts
│   │   │   │   └── index.ts
│   │   │   ├── crypto
│   │   │   │   ├── buffer.ts
│   │   │   │   ├── hash.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── jwt.ts
│   │   │   │   ├── password.test.ts
│   │   │   │   ├── password.ts
│   │   │   │   └── random.ts
│   │   │   ├── db
│   │   │   │   ├── db.test.ts
│   │   │   │   ├── field.ts
│   │   │   │   ├── get-migration.ts
│   │   │   │   ├── get-schema.ts
│   │   │   │   ├── get-tables.test.ts
│   │   │   │   ├── get-tables.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── internal-adapter.test.ts
│   │   │   │   ├── internal-adapter.ts
│   │   │   │   ├── schema.ts
│   │   │   │   ├── secondary-storage.test.ts
│   │   │   │   ├── to-zod.ts
│   │   │   │   ├── utils.ts
│   │   │   │   └── with-hooks.ts
│   │   │   ├── index.ts
│   │   │   ├── init.test.ts
│   │   │   ├── init.ts
│   │   │   ├── integrations
│   │   │   │   ├── next-js.ts
│   │   │   │   ├── node.ts
│   │   │   │   ├── react-start.ts
│   │   │   │   ├── solid-start.ts
│   │   │   │   └── svelte-kit.ts
│   │   │   ├── oauth2
│   │   │   │   ├── index.ts
│   │   │   │   ├── link-account.test.ts
│   │   │   │   ├── link-account.ts
│   │   │   │   ├── state.ts
│   │   │   │   └── utils.ts
│   │   │   ├── plugins
│   │   │   │   ├── access
│   │   │   │   │   ├── access.test.ts
│   │   │   │   │   ├── access.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── additional-fields
│   │   │   │   │   ├── additional-fields.test.ts
│   │   │   │   │   └── client.ts
│   │   │   │   ├── admin
│   │   │   │   │   ├── access
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   └── statement.ts
│   │   │   │   │   ├── admin.test.ts
│   │   │   │   │   ├── admin.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── error-codes.ts
│   │   │   │   │   ├── has-permission.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── anonymous
│   │   │   │   │   ├── anon.test.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── api-key
│   │   │   │   │   ├── api-key.test.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── rate-limit.ts
│   │   │   │   │   ├── routes
│   │   │   │   │   │   ├── create-api-key.ts
│   │   │   │   │   │   ├── delete-all-expired-api-keys.ts
│   │   │   │   │   │   ├── delete-api-key.ts
│   │   │   │   │   │   ├── get-api-key.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   ├── list-api-keys.ts
│   │   │   │   │   │   ├── update-api-key.ts
│   │   │   │   │   │   └── verify-api-key.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── bearer
│   │   │   │   │   ├── bearer.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── captcha
│   │   │   │   │   ├── captcha.test.ts
│   │   │   │   │   ├── constants.ts
│   │   │   │   │   ├── error-codes.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   ├── utils.ts
│   │   │   │   │   └── verify-handlers
│   │   │   │   │       ├── captchafox.ts
│   │   │   │   │       ├── cloudflare-turnstile.ts
│   │   │   │   │       ├── google-recaptcha.ts
│   │   │   │   │       ├── h-captcha.ts
│   │   │   │   │       └── index.ts
│   │   │   │   ├── custom-session
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── custom-session.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── device-authorization
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── device-authorization.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── schema.ts
│   │   │   │   ├── email-otp
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── email-otp.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── generic-oauth
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── generic-oauth.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── haveibeenpwned
│   │   │   │   │   ├── haveibeenpwned.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── jwt
│   │   │   │   │   ├── adapter.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── jwt.test.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── sign.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── last-login-method
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── custom-prefix.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── last-login-method.test.ts
│   │   │   │   ├── magic-link
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── magic-link.test.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── mcp
│   │   │   │   │   ├── authorize.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── mcp.test.ts
│   │   │   │   ├── multi-session
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── multi-session.test.ts
│   │   │   │   ├── oauth-proxy
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── oauth-proxy.test.ts
│   │   │   │   ├── oidc-provider
│   │   │   │   │   ├── authorize.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── oidc.test.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   ├── ui.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── one-tap
│   │   │   │   │   ├── client.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── one-time-token
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── one-time-token.test.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── open-api
│   │   │   │   │   ├── generator.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── logo.ts
│   │   │   │   │   └── open-api.test.ts
│   │   │   │   ├── organization
│   │   │   │   │   ├── access
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   └── statement.ts
│   │   │   │   │   ├── adapter.ts
│   │   │   │   │   ├── call.ts
│   │   │   │   │   ├── client.test.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── error-codes.ts
│   │   │   │   │   ├── has-permission.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── organization-hook.test.ts
│   │   │   │   │   ├── organization.test.ts
│   │   │   │   │   ├── organization.ts
│   │   │   │   │   ├── permission.ts
│   │   │   │   │   ├── routes
│   │   │   │   │   │   ├── crud-access-control.test.ts
│   │   │   │   │   │   ├── crud-access-control.ts
│   │   │   │   │   │   ├── crud-invites.ts
│   │   │   │   │   │   ├── crud-members.test.ts
│   │   │   │   │   │   ├── crud-members.ts
│   │   │   │   │   │   ├── crud-org.test.ts
│   │   │   │   │   │   ├── crud-org.ts
│   │   │   │   │   │   └── crud-team.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── team.test.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── passkey
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── passkey.test.ts
│   │   │   │   ├── phone-number
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── phone-number-error.ts
│   │   │   │   │   └── phone-number.test.ts
│   │   │   │   ├── siwe
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── siwe.test.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── two-factor
│   │   │   │   │   ├── backup-codes
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── constant.ts
│   │   │   │   │   ├── error-code.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── otp
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── totp
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── two-factor.test.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   ├── utils.ts
│   │   │   │   │   └── verify-two-factor.ts
│   │   │   │   └── username
│   │   │   │       ├── client.ts
│   │   │   │       ├── error-codes.ts
│   │   │   │       ├── index.ts
│   │   │   │       ├── schema.ts
│   │   │   │       └── username.test.ts
│   │   │   ├── social-providers
│   │   │   │   └── index.ts
│   │   │   ├── social.test.ts
│   │   │   ├── test-utils
│   │   │   │   ├── headers.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── state.ts
│   │   │   │   └── test-instance.ts
│   │   │   ├── types
│   │   │   │   ├── adapter.ts
│   │   │   │   ├── api.ts
│   │   │   │   ├── helper.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── models.ts
│   │   │   │   ├── plugins.ts
│   │   │   │   └── types.test.ts
│   │   │   └── utils
│   │   │       ├── await-object.ts
│   │   │       ├── boolean.ts
│   │   │       ├── clone.ts
│   │   │       ├── constants.ts
│   │   │       ├── date.ts
│   │   │       ├── ensure-utc.ts
│   │   │       ├── get-request-ip.ts
│   │   │       ├── hashing.ts
│   │   │       ├── hide-metadata.ts
│   │   │       ├── id.ts
│   │   │       ├── import-util.ts
│   │   │       ├── index.ts
│   │   │       ├── is-atom.ts
│   │   │       ├── is-promise.ts
│   │   │       ├── json.ts
│   │   │       ├── merger.ts
│   │   │       ├── middleware-response.ts
│   │   │       ├── misc.ts
│   │   │       ├── password.ts
│   │   │       ├── plugin-helper.ts
│   │   │       ├── shim.ts
│   │   │       ├── time.ts
│   │   │       ├── url.ts
│   │   │       └── wildcard.ts
│   │   ├── tsconfig.json
│   │   ├── tsdown.config.ts
│   │   ├── vitest.config.ts
│   │   └── vitest.setup.ts
│   ├── cli
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── commands
│   │   │   │   ├── generate.ts
│   │   │   │   ├── info.ts
│   │   │   │   ├── init.ts
│   │   │   │   ├── login.ts
│   │   │   │   ├── mcp.ts
│   │   │   │   ├── migrate.ts
│   │   │   │   └── secret.ts
│   │   │   ├── generators
│   │   │   │   ├── auth-config.ts
│   │   │   │   ├── drizzle.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── kysely.ts
│   │   │   │   ├── prisma.ts
│   │   │   │   └── types.ts
│   │   │   ├── index.ts
│   │   │   └── utils
│   │   │       ├── add-svelte-kit-env-modules.ts
│   │   │       ├── check-package-managers.ts
│   │   │       ├── format-ms.ts
│   │   │       ├── get-config.ts
│   │   │       ├── get-package-info.ts
│   │   │       ├── get-tsconfig-info.ts
│   │   │       └── install-dependencies.ts
│   │   ├── test
│   │   │   ├── __snapshots__
│   │   │   │   ├── auth-schema-mysql-enum.txt
│   │   │   │   ├── auth-schema-mysql-number-id.txt
│   │   │   │   ├── auth-schema-mysql-passkey-number-id.txt
│   │   │   │   ├── auth-schema-mysql-passkey.txt
│   │   │   │   ├── auth-schema-mysql.txt
│   │   │   │   ├── auth-schema-number-id.txt
│   │   │   │   ├── auth-schema-pg-enum.txt
│   │   │   │   ├── auth-schema-pg-passkey.txt
│   │   │   │   ├── auth-schema-sqlite-enum.txt
│   │   │   │   ├── auth-schema-sqlite-number-id.txt
│   │   │   │   ├── auth-schema-sqlite-passkey-number-id.txt
│   │   │   │   ├── auth-schema-sqlite-passkey.txt
│   │   │   │   ├── auth-schema-sqlite.txt
│   │   │   │   ├── auth-schema.txt
│   │   │   │   ├── migrations.sql
│   │   │   │   ├── schema-mongodb.prisma
│   │   │   │   ├── schema-mysql-custom.prisma
│   │   │   │   ├── schema-mysql.prisma
│   │   │   │   ├── schema-numberid.prisma
│   │   │   │   └── schema.prisma
│   │   │   ├── generate-all-db.test.ts
│   │   │   ├── generate.test.ts
│   │   │   ├── get-config.test.ts
│   │   │   ├── info.test.ts
│   │   │   └── migrate.test.ts
│   │   ├── tsconfig.json
│   │   ├── tsconfig.test.json
│   │   └── tsdown.config.ts
│   ├── core
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── api
│   │   │   │   └── index.ts
│   │   │   ├── async_hooks
│   │   │   │   └── index.ts
│   │   │   ├── context
│   │   │   │   ├── endpoint-context.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── transaction.ts
│   │   │   ├── db
│   │   │   │   ├── adapter
│   │   │   │   │   └── index.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── plugin.ts
│   │   │   │   ├── schema
│   │   │   │   │   ├── account.ts
│   │   │   │   │   ├── rate-limit.ts
│   │   │   │   │   ├── session.ts
│   │   │   │   │   ├── shared.ts
│   │   │   │   │   ├── user.ts
│   │   │   │   │   └── verification.ts
│   │   │   │   └── type.ts
│   │   │   ├── env
│   │   │   │   ├── color-depth.ts
│   │   │   │   ├── env-impl.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── logger.test.ts
│   │   │   │   └── logger.ts
│   │   │   ├── error
│   │   │   │   ├── codes.ts
│   │   │   │   └── index.ts
│   │   │   ├── index.ts
│   │   │   ├── oauth2
│   │   │   │   ├── client-credentials-token.ts
│   │   │   │   ├── create-authorization-url.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── oauth-provider.ts
│   │   │   │   ├── refresh-access-token.ts
│   │   │   │   ├── utils.ts
│   │   │   │   └── validate-authorization-code.ts
│   │   │   ├── social-providers
│   │   │   │   ├── apple.ts
│   │   │   │   ├── atlassian.ts
│   │   │   │   ├── cognito.ts
│   │   │   │   ├── discord.ts
│   │   │   │   ├── dropbox.ts
│   │   │   │   ├── facebook.ts
│   │   │   │   ├── figma.ts
│   │   │   │   ├── github.ts
│   │   │   │   ├── gitlab.ts
│   │   │   │   ├── google.ts
│   │   │   │   ├── huggingface.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── kakao.ts
│   │   │   │   ├── kick.ts
│   │   │   │   ├── line.ts
│   │   │   │   ├── linear.ts
│   │   │   │   ├── linkedin.ts
│   │   │   │   ├── microsoft-entra-id.ts
│   │   │   │   ├── naver.ts
│   │   │   │   ├── notion.ts
│   │   │   │   ├── paypal.ts
│   │   │   │   ├── reddit.ts
│   │   │   │   ├── roblox.ts
│   │   │   │   ├── salesforce.ts
│   │   │   │   ├── slack.ts
│   │   │   │   ├── spotify.ts
│   │   │   │   ├── tiktok.ts
│   │   │   │   ├── twitch.ts
│   │   │   │   ├── twitter.ts
│   │   │   │   ├── vk.ts
│   │   │   │   └── zoom.ts
│   │   │   ├── types
│   │   │   │   ├── context.ts
│   │   │   │   ├── cookie.ts
│   │   │   │   ├── helper.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── init-options.ts
│   │   │   │   ├── plugin-client.ts
│   │   │   │   └── plugin.ts
│   │   │   └── utils
│   │   │       ├── error-codes.ts
│   │   │       └── index.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── expo
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── client.ts
│   │   │   ├── expo.test.ts
│   │   │   └── index.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── sso
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── client.ts
│   │   │   ├── index.ts
│   │   │   ├── oidc.test.ts
│   │   │   └── saml.test.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── stripe
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── client.ts
│   │   │   ├── hooks.ts
│   │   │   ├── index.ts
│   │   │   ├── schema.ts
│   │   │   ├── stripe.test.ts
│   │   │   ├── types.ts
│   │   │   └── utils.ts
│   │   ├── tsconfig.json
│   │   ├── tsdown.config.ts
│   │   └── vitest.config.ts
│   └── telemetry
│       ├── package.json
│       ├── src
│       │   ├── detectors
│       │   │   ├── detect-auth-config.ts
│       │   │   ├── detect-database.ts
│       │   │   ├── detect-framework.ts
│       │   │   ├── detect-project-info.ts
│       │   │   ├── detect-runtime.ts
│       │   │   └── detect-system-info.ts
│       │   ├── index.ts
│       │   ├── project-id.ts
│       │   ├── telemetry.test.ts
│       │   ├── types.ts
│       │   └── utils
│       │       ├── hash.ts
│       │       ├── id.ts
│       │       ├── import-util.ts
│       │       └── package-json.ts
│       ├── tsconfig.json
│       └── tsdown.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── README.md
├── SECURITY.md
├── tsconfig.json
└── turbo.json
```

# Files

--------------------------------------------------------------------------------
/demo/nextjs/components/account-switch.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import { useState } from "react";
  4 | import { Button } from "@/components/ui/button";
  5 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
  6 | import {
  7 | 	Popover,
  8 | 	PopoverContent,
  9 | 	PopoverTrigger,
 10 | } from "@/components/ui/popover";
 11 | import {
 12 | 	Command,
 13 | 	CommandGroup,
 14 | 	CommandItem,
 15 | 	CommandList,
 16 | 	CommandSeparator,
 17 | } from "@/components/ui/command";
 18 | import { ChevronDown, PlusCircle } from "lucide-react";
 19 | import { Session } from "@/lib/auth-types";
 20 | import { client, useSession } from "@/lib/auth-client";
 21 | import { useRouter } from "next/navigation";
 22 | 
 23 | export default function AccountSwitcher({ sessions }: { sessions: Session[] }) {
 24 | 	const { data: currentUser } = useSession();
 25 | 	const [open, setOpen] = useState(false);
 26 | 	const router = useRouter();
 27 | 	return (
 28 | 		<Popover open={open} onOpenChange={setOpen}>
 29 | 			<PopoverTrigger asChild>
 30 | 				<Button
 31 | 					variant="outline"
 32 | 					role="combobox"
 33 | 					aria-expanded={open}
 34 | 					aria-label="Select a user"
 35 | 					className="w-[250px] justify-between"
 36 | 				>
 37 | 					<Avatar className="mr-2 h-6 w-6">
 38 | 						<AvatarImage
 39 | 							src={currentUser?.user.image || undefined}
 40 | 							alt={currentUser?.user.name}
 41 | 						/>
 42 | 						<AvatarFallback>{currentUser?.user.name.charAt(0)}</AvatarFallback>
 43 | 					</Avatar>
 44 | 					{currentUser?.user.name}
 45 | 					<ChevronDown className="ml-auto h-4 w-4 shrink-0 opacity-50" />
 46 | 				</Button>
 47 | 			</PopoverTrigger>
 48 | 			<PopoverContent className="w-[250px] p-0">
 49 | 				<Command>
 50 | 					<CommandList>
 51 | 						<CommandGroup heading="Current Account">
 52 | 							<CommandItem
 53 | 								onSelect={() => {}}
 54 | 								className="text-sm w-full justify-between"
 55 | 								key={currentUser?.user.id}
 56 | 							>
 57 | 								<div className="flex items-center">
 58 | 									<Avatar className="mr-2 h-5 w-5">
 59 | 										<AvatarImage
 60 | 											src={currentUser?.user.image || undefined}
 61 | 											alt={currentUser?.user.name}
 62 | 										/>
 63 | 										<AvatarFallback>
 64 | 											{currentUser?.user.name.charAt(0)}
 65 | 										</AvatarFallback>
 66 | 									</Avatar>
 67 | 									{currentUser?.user.name}
 68 | 								</div>
 69 | 							</CommandItem>
 70 | 						</CommandGroup>
 71 | 						<CommandSeparator />
 72 | 						<CommandGroup heading="Switch Account">
 73 | 							{sessions
 74 | 								.filter((s) => s.user.id !== currentUser?.user.id)
 75 | 								.map((u, i) => (
 76 | 									<CommandItem
 77 | 										key={i}
 78 | 										onSelect={async () => {
 79 | 											await client.multiSession.setActive({
 80 | 												sessionToken: u.session.token,
 81 | 											});
 82 | 											setOpen(false);
 83 | 										}}
 84 | 										className="text-sm"
 85 | 									>
 86 | 										<Avatar className="mr-2 h-5 w-5">
 87 | 											<AvatarImage
 88 | 												src={u.user.image || undefined}
 89 | 												alt={u.user.name}
 90 | 											/>
 91 | 											<AvatarFallback>{u.user.name.charAt(0)}</AvatarFallback>
 92 | 										</Avatar>
 93 | 										<div className="flex items-center justify-between w-full">
 94 | 											<div>
 95 | 												<p>{u.user.name}</p>
 96 | 												<p className="text-xs">({u.user.email})</p>
 97 | 											</div>
 98 | 										</div>
 99 | 									</CommandItem>
100 | 								))}
101 | 						</CommandGroup>
102 | 					</CommandList>
103 | 					<CommandSeparator />
104 | 					<CommandList>
105 | 						<CommandGroup>
106 | 							<CommandItem
107 | 								onSelect={() => {
108 | 									router.push("/sign-in");
109 | 									setOpen(false);
110 | 								}}
111 | 								className="cursor-pointer text-sm"
112 | 							>
113 | 								<PlusCircle className="mr-2 h-5 w-5" />
114 | 								Add Account
115 | 							</CommandItem>
116 | 						</CommandGroup>
117 | 					</CommandList>
118 | 				</Command>
119 | 			</PopoverContent>
120 | 		</Popover>
121 | 	);
122 | }
123 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/app/oauth/authorize/page.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import { Metadata } from "next";
  2 | import { auth } from "@/lib/auth";
  3 | import { headers } from "next/headers";
  4 | import { ArrowLeftRight, ArrowUpRight, Mail, Users } from "lucide-react";
  5 | import { Card, CardContent } from "@/components/ui/card";
  6 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
  7 | import { Logo } from "@/components/logo";
  8 | import Image from "next/image";
  9 | import { ConsentBtns } from "./concet-buttons";
 10 | 
 11 | export const metadata: Metadata = {
 12 | 	title: "Authorize Application",
 13 | 	description: "Grant access to your account",
 14 | };
 15 | 
 16 | interface AuthorizePageProps {
 17 | 	searchParams: Promise<{
 18 | 		redirect_uri: string;
 19 | 		scope: string;
 20 | 		cancel_uri: string;
 21 | 		client_id: string;
 22 | 	}>;
 23 | }
 24 | 
 25 | export default async function AuthorizePage({
 26 | 	searchParams,
 27 | }: AuthorizePageProps) {
 28 | 	const { redirect_uri, scope, client_id, cancel_uri } = await searchParams;
 29 | 	const session = await auth.api.getSession({
 30 | 		headers: await headers(),
 31 | 	});
 32 | 	// @ts-expect-error
 33 | 	const clientDetails = await auth.api.getOAuthClient({
 34 | 		params: {
 35 | 			id: client_id,
 36 | 		},
 37 | 		headers: await headers(),
 38 | 	});
 39 | 
 40 | 	return (
 41 | 		<div className="container mx-auto py-10">
 42 | 			<h1 className="text-2xl font-bold mb-6 text-center">
 43 | 				Authorize Application
 44 | 			</h1>
 45 | 			<div className="min-h-screen bg-black text-white flex flex-col">
 46 | 				<div className="flex flex-col items-center justify-center max-w-2xl mx-auto px-4">
 47 | 					<div className="flex items-center gap-8 mb-8">
 48 | 						<div className="w-16 h-16 border rounded-full flex items-center justify-center">
 49 | 							{clientDetails.icon ? (
 50 | 								<Image
 51 | 									src={clientDetails.icon}
 52 | 									alt="App Logo"
 53 | 									className="object-cover"
 54 | 									width={64}
 55 | 									height={64}
 56 | 								/>
 57 | 							) : (
 58 | 								<Logo />
 59 | 							)}
 60 | 						</div>
 61 | 						<ArrowLeftRight className="h-6 w-6" />
 62 | 						<div className="w-16 h-16 rounded-full overflow-hidden">
 63 | 							<Avatar className="hidden h-16 w-16 sm:flex ">
 64 | 								<AvatarImage
 65 | 									src={session?.user.image || "#"}
 66 | 									alt="Avatar"
 67 | 									className="object-cover"
 68 | 								/>
 69 | 								<AvatarFallback>{session?.user.name.charAt(0)}</AvatarFallback>
 70 | 							</Avatar>
 71 | 						</div>
 72 | 					</div>
 73 | 
 74 | 					<h1 className="text-3xl font-semibold text-center mb-8">
 75 | 						{clientDetails.name} is requesting access to your Better Auth
 76 | 						account
 77 | 					</h1>
 78 | 
 79 | 					<Card className="w-full bg-zinc-900 border-zinc-800 rounded-none">
 80 | 						<CardContent className="p-6">
 81 | 							<div className="flex items-center justify-between p-4 bg-zinc-800 rounded-lg mb-6">
 82 | 								<div>
 83 | 									<div className="font-medium">{session?.user.name}</div>
 84 | 									<div className="text-zinc-400">{session?.user.email}</div>
 85 | 								</div>
 86 | 								<ArrowUpRight className="h-5 w-5 text-zinc-400" />
 87 | 							</div>
 88 | 							<div className="flex flex-col gap-1">
 89 | 								<div className="text-lg mb-4">
 90 | 									Continuing will allow Sign in with {clientDetails.name} to:
 91 | 								</div>
 92 | 								{scope.includes("profile") && (
 93 | 									<div className="flex items-center gap-3 text-zinc-300">
 94 | 										<Users className="h-5 w-5" />
 95 | 										<span>Read your Better Auth user data.</span>
 96 | 									</div>
 97 | 								)}
 98 | 
 99 | 								{scope.includes("email") && (
100 | 									<div className="flex items-center gap-3 text-zinc-300">
101 | 										<Mail className="h-5 w-5" />
102 | 										<span>Read your email address.</span>
103 | 									</div>
104 | 								)}
105 | 							</div>
106 | 						</CardContent>
107 | 						<ConsentBtns />
108 | 					</Card>
109 | 				</div>
110 | 			</div>
111 | 		</div>
112 | 	);
113 | }
114 | 
```

--------------------------------------------------------------------------------
/demo/expo-example/src/app/index.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import Ionicons from "@expo/vector-icons/AntDesign";
  2 | import { Button } from "@/components/ui/button";
  3 | import { Card, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
  4 | import { Text } from "@/components/ui/text";
  5 | import { authClient } from "@/lib/auth-client";
  6 | import { Image, View } from "react-native";
  7 | import { Separator } from "@/components/ui/separator";
  8 | import { Input } from "@/components/ui/input";
  9 | import { useEffect, useState } from "react";
 10 | import { router, useNavigationContainerRef } from "expo-router";
 11 | import { useStore } from "@nanostores/react";
 12 | 
 13 | export default function Index() {
 14 | 	const { data: isAuthenticated } = useStore(authClient.useSession);
 15 | 	const navContainerRef = useNavigationContainerRef();
 16 | 	const [email, setEmail] = useState("");
 17 | 	const [password, setPassword] = useState("");
 18 | 
 19 | 	useEffect(() => {
 20 | 		if (isAuthenticated) {
 21 | 			if (navContainerRef.isReady()) {
 22 | 				router.push("/dashboard");
 23 | 			}
 24 | 		}
 25 | 	}, [isAuthenticated, navContainerRef.isReady()]);
 26 | 	return (
 27 | 		<Card className="z-50 mx-6 backdrop-blur-lg bg-gray-200/70">
 28 | 			<CardHeader className="flex items-center justify-center gap-8">
 29 | 				<Image
 30 | 					source={require("../../assets/images/logo.png")}
 31 | 					style={{
 32 | 						width: 40,
 33 | 						height: 40,
 34 | 					}}
 35 | 				/>
 36 | 				<CardTitle>Sign In to your account</CardTitle>
 37 | 			</CardHeader>
 38 | 			<View className="px-6 flex gap-2">
 39 | 				<Button
 40 | 					onPress={() => {
 41 | 						authClient.signIn.social({
 42 | 							provider: "google",
 43 | 							callbackURL: "/dashboard",
 44 | 						});
 45 | 					}}
 46 | 					variant="secondary"
 47 | 					className="flex flex-row gap-2 items-center bg-white/50"
 48 | 				>
 49 | 					<Ionicons name="google" size={16} />
 50 | 					<Text>Sign In with Google</Text>
 51 | 				</Button>
 52 | 				<Button
 53 | 					variant="secondary"
 54 | 					className="flex flex-row gap-2 items-center bg-white/50"
 55 | 					onPress={() => {
 56 | 						authClient.signIn.social({
 57 | 							provider: "github",
 58 | 							callbackURL: "/dashboard",
 59 | 						});
 60 | 					}}
 61 | 				>
 62 | 					<Ionicons name="github" size={16} />
 63 | 					<Text>Sign In with GitHub</Text>
 64 | 				</Button>
 65 | 			</View>
 66 | 			<View className="flex-row gap-2 w-full items-center px-6 my-4">
 67 | 				<Separator className="flex-grow w-3/12" />
 68 | 				<Text>or continue with</Text>
 69 | 				<Separator className="flex-grow w-3/12" />
 70 | 			</View>
 71 | 			<View className="px-6">
 72 | 				<Input
 73 | 					placeholder="Email Address"
 74 | 					className="rounded-b-none border-b-0"
 75 | 					value={email}
 76 | 					onChangeText={(text) => {
 77 | 						setEmail(text);
 78 | 					}}
 79 | 				/>
 80 | 				<Input
 81 | 					placeholder="Password"
 82 | 					className="rounded-t-none"
 83 | 					secureTextEntry
 84 | 					value={password}
 85 | 					onChangeText={(text) => {
 86 | 						setPassword(text);
 87 | 					}}
 88 | 				/>
 89 | 			</View>
 90 | 			<CardFooter>
 91 | 				<View className="w-full">
 92 | 					<Button
 93 | 						variant="link"
 94 | 						className="w-full"
 95 | 						onPress={() => {
 96 | 							router.push("/forget-password");
 97 | 						}}
 98 | 					>
 99 | 						<Text className="underline text-center">Forget Password?</Text>
100 | 					</Button>
101 | 					<Button
102 | 						onPress={() => {
103 | 							authClient.signIn.email(
104 | 								{
105 | 									email,
106 | 									password,
107 | 								},
108 | 								{
109 | 									onError: (ctx) => {
110 | 										alert(ctx.error.message);
111 | 									},
112 | 								},
113 | 							);
114 | 						}}
115 | 					>
116 | 						<Text>Continue</Text>
117 | 					</Button>
118 | 					<Text className="text-center mt-2">
119 | 						Don't have an account?{" "}
120 | 						<Text
121 | 							className="underline"
122 | 							onPress={() => {
123 | 								router.push("/sign-up");
124 | 							}}
125 | 						>
126 | 							Create Account
127 | 						</Text>
128 | 					</Text>
129 | 				</View>
130 | 			</CardFooter>
131 | 		</Card>
132 | 	);
133 | }
134 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/client/types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { BetterAuthPlugin } from "@better-auth/core";
  2 | import type { StripEmptyObjects, UnionToIntersection } from "../types/helper";
  3 | import type { Auth } from "../auth";
  4 | import type { InferRoutes } from "./path-to-object";
  5 | import type { Session, User } from "../types";
  6 | import type { InferFieldsInputClient, InferFieldsOutput } from "../db";
  7 | import type {
  8 | 	ClientStore,
  9 | 	ClientAtomListener,
 10 | 	BetterAuthClientOptions,
 11 | 	BetterAuthClientPlugin,
 12 | } from "@better-auth/core";
 13 | export type {
 14 | 	ClientStore,
 15 | 	ClientAtomListener,
 16 | 	BetterAuthClientOptions,
 17 | 	BetterAuthClientPlugin,
 18 | };
 19 | 
 20 | /**
 21 |  * @deprecated use type `BetterAuthClientOptions` instead.
 22 |  */
 23 | export type Store = ClientStore;
 24 | /**
 25 |  * @deprecated use type `ClientAtomListener` instead.
 26 |  */
 27 | export type AtomListener = ClientAtomListener;
 28 | /**
 29 |  * @deprecated use type `BetterAuthClientPlugin` instead.
 30 |  */
 31 | export type ClientOptions = BetterAuthClientOptions;
 32 | 
 33 | export type InferClientAPI<O extends BetterAuthClientOptions> = InferRoutes<
 34 | 	O["plugins"] extends Array<any>
 35 | 		? Auth["api"] &
 36 | 				(O["plugins"] extends Array<infer Pl>
 37 | 					? UnionToIntersection<
 38 | 							Pl extends {
 39 | 								$InferServerPlugin: infer Plug;
 40 | 							}
 41 | 								? Plug extends {
 42 | 										endpoints: infer Endpoints;
 43 | 									}
 44 | 									? Endpoints
 45 | 									: {}
 46 | 								: {}
 47 | 						>
 48 | 					: {})
 49 | 		: Auth["api"],
 50 | 	O
 51 | >;
 52 | 
 53 | export type InferActions<O extends BetterAuthClientOptions> =
 54 | 	(O["plugins"] extends Array<infer Plugin>
 55 | 		? UnionToIntersection<
 56 | 				Plugin extends BetterAuthClientPlugin
 57 | 					? Plugin["getActions"] extends (...args: any) => infer Actions
 58 | 						? Actions
 59 | 						: {}
 60 | 					: {}
 61 | 			>
 62 | 		: {}) &
 63 | 		//infer routes from auth config
 64 | 		InferRoutes<
 65 | 			O["$InferAuth"] extends {
 66 | 				plugins: infer Plugins;
 67 | 			}
 68 | 				? Plugins extends Array<infer Plugin>
 69 | 					? Plugin extends {
 70 | 							endpoints: infer Endpoints;
 71 | 						}
 72 | 						? Endpoints
 73 | 						: {}
 74 | 					: {}
 75 | 				: {},
 76 | 			O
 77 | 		>;
 78 | 
 79 | export type InferErrorCodes<O extends BetterAuthClientOptions> =
 80 | 	O["plugins"] extends Array<infer Plugin>
 81 | 		? UnionToIntersection<
 82 | 				Plugin extends BetterAuthClientPlugin
 83 | 					? Plugin["$InferServerPlugin"] extends BetterAuthPlugin
 84 | 						? Plugin["$InferServerPlugin"]["$ERROR_CODES"]
 85 | 						: {}
 86 | 					: {}
 87 | 			>
 88 | 		: {};
 89 | /**
 90 |  * signals are just used to recall a computed value.
 91 |  * as a convention they start with "$"
 92 |  */
 93 | export type IsSignal<T> = T extends `$${infer _}` ? true : false;
 94 | 
 95 | export type InferPluginsFromClient<O extends BetterAuthClientOptions> =
 96 | 	O["plugins"] extends Array<BetterAuthClientPlugin>
 97 | 		? Array<O["plugins"][number]["$InferServerPlugin"]>
 98 | 		: undefined;
 99 | 
100 | export type InferSessionFromClient<O extends BetterAuthClientOptions> =
101 | 	StripEmptyObjects<
102 | 		Session &
103 | 			UnionToIntersection<InferAdditionalFromClient<O, "session", "output">>
104 | 	>;
105 | export type InferUserFromClient<O extends BetterAuthClientOptions> =
106 | 	StripEmptyObjects<
107 | 		User & UnionToIntersection<InferAdditionalFromClient<O, "user", "output">>
108 | 	>;
109 | 
110 | export type InferAdditionalFromClient<
111 | 	Options extends BetterAuthClientOptions,
112 | 	Key extends string,
113 | 	Format extends "input" | "output" = "output",
114 | > = Options["plugins"] extends Array<infer T>
115 | 	? T extends BetterAuthClientPlugin
116 | 		? T["$InferServerPlugin"] extends {
117 | 				schema: {
118 | 					[key in Key]: {
119 | 						fields: infer Field;
120 | 					};
121 | 				};
122 | 			}
123 | 			? Format extends "input"
124 | 				? InferFieldsInputClient<Field>
125 | 				: InferFieldsOutput<Field>
126 | 			: {}
127 | 		: {}
128 | 	: {};
129 | 
130 | export type SessionQueryParams = {
131 | 	disableCookieCache?: boolean;
132 | 	disableRefresh?: boolean;
133 | };
134 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/api/routes/sign-up.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { afterEach, describe, expect, vi } from "vitest";
  2 | import { getTestInstance } from "../../test-utils/test-instance";
  3 | 
  4 | describe("sign-up with custom fields", async (it) => {
  5 | 	const mockFn = vi.fn();
  6 | 	const { auth, db } = await getTestInstance(
  7 | 		{
  8 | 			account: {
  9 | 				fields: {
 10 | 					providerId: "provider_id",
 11 | 					accountId: "account_id",
 12 | 				},
 13 | 			},
 14 | 			user: {
 15 | 				additionalFields: {
 16 | 					newField: {
 17 | 						type: "string",
 18 | 						required: false,
 19 | 					},
 20 | 					newField2: {
 21 | 						type: "string",
 22 | 						required: false,
 23 | 					},
 24 | 					isAdmin: {
 25 | 						type: "boolean",
 26 | 						defaultValue: true,
 27 | 						input: false,
 28 | 					},
 29 | 					role: {
 30 | 						input: false,
 31 | 						type: "string",
 32 | 						required: false,
 33 | 					},
 34 | 				},
 35 | 			},
 36 | 			emailVerification: {
 37 | 				sendOnSignUp: true,
 38 | 				sendVerificationEmail: mockFn,
 39 | 			},
 40 | 		},
 41 | 		{
 42 | 			disableTestUser: true,
 43 | 		},
 44 | 	);
 45 | 
 46 | 	afterEach(() => {
 47 | 		mockFn.mockReset();
 48 | 	});
 49 | 
 50 | 	it("should work with custom fields on account table", async () => {
 51 | 		const res = await auth.api.signUpEmail({
 52 | 			body: {
 53 | 				email: "[email protected]",
 54 | 				password: "password",
 55 | 				name: "Test Name",
 56 | 				image: "https://picsum.photos/200",
 57 | 			},
 58 | 		});
 59 | 		expect(res.token).toBeDefined();
 60 | 		const users = await db.findMany({
 61 | 			model: "user",
 62 | 		});
 63 | 		const accounts = await db.findMany({
 64 | 			model: "account",
 65 | 		});
 66 | 		expect(accounts).toHaveLength(1);
 67 | 
 68 | 		expect("isAdmin" in (users[0] as any)).toBe(true);
 69 | 		expect((users[0] as any).isAdmin).toBe(true);
 70 | 
 71 | 		expect(mockFn).toHaveBeenCalledTimes(1);
 72 | 		expect(mockFn).toHaveBeenCalledWith(
 73 | 			expect.objectContaining({
 74 | 				token: expect.any(String),
 75 | 				url: expect.any(String),
 76 | 				user: expect.any(Object),
 77 | 			}),
 78 | 		);
 79 | 	});
 80 | 
 81 | 	it("should get the ipAddress and userAgent from headers", async () => {
 82 | 		const res = await auth.api.signUpEmail({
 83 | 			body: {
 84 | 				email: "[email protected]",
 85 | 				password: "password",
 86 | 				name: "Test Name",
 87 | 			},
 88 | 			headers: new Headers({
 89 | 				"x-forwarded-for": "127.0.0.1",
 90 | 				"user-agent": "test-user-agent",
 91 | 			}),
 92 | 		});
 93 | 		const session = await auth.api.getSession({
 94 | 			headers: new Headers({
 95 | 				authorization: `Bearer ${res.token}`,
 96 | 			}),
 97 | 		});
 98 | 		expect(session).toBeDefined();
 99 | 		expect(session!.session).toMatchObject({
100 | 			userAgent: "test-user-agent",
101 | 			ipAddress: "127.0.0.1",
102 | 		});
103 | 	});
104 | 
105 | 	it("should rollback when session creation fails", async ({ skip }) => {
106 | 		const ctx = await auth.$context;
107 | 		if (!ctx.adapter.options?.adapterConfig.transaction) {
108 | 			skip();
109 | 		}
110 | 		const originalCreateSession = ctx.internalAdapter.createSession;
111 | 		ctx.internalAdapter.createSession = vi
112 | 			.fn()
113 | 			.mockRejectedValue(new Error("Session creation failed"));
114 | 
115 | 		await expect(
116 | 			auth.api.signUpEmail({
117 | 				body: {
118 | 					email: "[email protected]",
119 | 					password: "password",
120 | 					name: "Rollback Test",
121 | 				},
122 | 			}),
123 | 		).rejects.toThrow();
124 | 
125 | 		const users = await db.findMany({ model: "user" });
126 | 		const rollbackUser = users.find(
127 | 			(u: any) => u.email === "[email protected]",
128 | 		);
129 | 		expect(rollbackUser).toBeUndefined();
130 | 
131 | 		ctx.internalAdapter.createSession = originalCreateSession;
132 | 	});
133 | 
134 | 	it("should not allow user to set the field that is set to input: false", async () => {
135 | 		const res = await auth.api.signUpEmail({
136 | 			body: {
137 | 				email: "[email protected]",
138 | 				password: "password",
139 | 				name: "Input False Test",
140 | 				//@ts-expect-error
141 | 				role: "admin",
142 | 			},
143 | 		});
144 | 		const session = await auth.api.getSession({
145 | 			headers: new Headers({
146 | 				authorization: `Bearer ${res.token}`,
147 | 			}),
148 | 		});
149 | 		expect(session?.user.role).toBeNull();
150 | 	});
151 | });
152 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/api/routes/error.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { HIDE_METADATA } from "../../utils/hide-metadata";
  2 | import { createAuthEndpoint } from "@better-auth/core/api";
  3 | 
  4 | function sanitize(input: string): string {
  5 | 	return input
  6 | 		.replace(/&/g, "&amp;")
  7 | 		.replace(/</g, "&lt;")
  8 | 		.replace(/>/g, "&gt;")
  9 | 		.replace(/"/g, "&quot;")
 10 | 		.replace(/'/g, "&#39;");
 11 | }
 12 | 
 13 | const html = (errorCode: string = "Unknown") => `<!DOCTYPE html>
 14 | <html lang="en">
 15 | <head>
 16 |     <meta charset="UTF-8">
 17 |     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 18 |     <title>Authentication Error</title>
 19 |     <style>
 20 |         :root {
 21 |             --bg-color: #f8f9fa;
 22 |             --text-color: #212529;
 23 |             --accent-color: #000000;
 24 |             --error-color: #dc3545;
 25 |             --border-color: #e9ecef;
 26 |         }
 27 |         body {
 28 |             font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
 29 |             background-color: var(--bg-color);
 30 |             color: var(--text-color);
 31 |             display: flex;
 32 |             justify-content: center;
 33 |             align-items: center;
 34 |             height: 100vh;
 35 |             margin: 0;
 36 |             line-height: 1.5;
 37 |         }
 38 |         .error-container {
 39 |             background-color: #ffffff;
 40 |             border-radius: 12px;
 41 |             box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
 42 |             padding: 2.5rem;
 43 |             text-align: center;
 44 |             max-width: 90%;
 45 |             width: 400px;
 46 |         }
 47 |         h1 {
 48 |             color: var(--error-color);
 49 |             font-size: 1.75rem;
 50 |             margin-bottom: 1rem;
 51 |             font-weight: 600;
 52 |         }
 53 |         p {
 54 |             margin-bottom: 1.5rem;
 55 |             color: #495057;
 56 |         }
 57 |         .btn {
 58 |             background-color: var(--accent-color);
 59 |             color: #ffffff;
 60 |             text-decoration: none;
 61 |             padding: 0.75rem 1.5rem;
 62 |             border-radius: 6px;
 63 |             transition: all 0.3s ease;
 64 |             display: inline-block;
 65 |             font-weight: 500;
 66 |             border: 2px solid var(--accent-color);
 67 |         }
 68 |         .btn:hover {
 69 |             background-color: #131721;
 70 |         }
 71 |         .error-code {
 72 |             font-size: 0.875rem;
 73 |             color: #6c757d;
 74 |             margin-top: 1.5rem;
 75 |             padding-top: 1.5rem;
 76 |             border-top: 1px solid var(--border-color);
 77 |         }
 78 |         .icon {
 79 |             font-size: 3rem;
 80 |             margin-bottom: 1rem;
 81 |         }
 82 |     </style>
 83 | </head>
 84 | <body>
 85 |     <div class="error-container">
 86 |         <div class="icon">⚠️</div>
 87 |         <h1>Better Auth Error</h1>
 88 |         <p>We encountered an issue while processing your request. Please try again or contact the application owner if the problem persists.</p>
 89 |         <a href="/" id="returnLink" class="btn">Return to Application</a>
 90 |         <div class="error-code">Error Code: <span id="errorCode">${sanitize(
 91 | 					errorCode,
 92 | 				)}</span></div>
 93 |     </div>
 94 | </body>
 95 | </html>`;
 96 | export const error = createAuthEndpoint(
 97 | 	"/error",
 98 | 	{
 99 | 		method: "GET",
100 | 		metadata: {
101 | 			...HIDE_METADATA,
102 | 			openapi: {
103 | 				description: "Displays an error page",
104 | 				responses: {
105 | 					"200": {
106 | 						description: "Success",
107 | 						content: {
108 | 							"text/html": {
109 | 								schema: {
110 | 									type: "string",
111 | 									description: "The HTML content of the error page",
112 | 								},
113 | 							},
114 | 						},
115 | 					},
116 | 				},
117 | 			},
118 | 		},
119 | 	},
120 | 	async (c) => {
121 | 		const query =
122 | 			new URL(c.request?.url || "").searchParams.get("error") || "Unknown";
123 | 		return new Response(html(query), {
124 | 			headers: {
125 | 				"Content-Type": "text/html",
126 | 			},
127 | 		});
128 | 	},
129 | );
130 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/client/config.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { createFetch } from "@better-fetch/fetch";
  2 | import { getBaseURL } from "../utils/url";
  3 | import { type WritableAtom } from "nanostores";
  4 | import type {
  5 | 	BetterAuthClientOptions,
  6 | 	ClientAtomListener,
  7 | } from "@better-auth/core";
  8 | import { redirectPlugin } from "./fetch-plugins";
  9 | import { getSessionAtom } from "./session-atom";
 10 | import { parseJSON } from "./parser";
 11 | 
 12 | export const getClientConfig = (
 13 | 	options?: BetterAuthClientOptions,
 14 | 	loadEnv?: boolean,
 15 | ) => {
 16 | 	/* check if the credentials property is supported. Useful for cf workers */
 17 | 	const isCredentialsSupported = "credentials" in Request.prototype;
 18 | 	const baseURL =
 19 | 		getBaseURL(options?.baseURL, options?.basePath, undefined, loadEnv) ??
 20 | 		"/api/auth";
 21 | 	const pluginsFetchPlugins =
 22 | 		options?.plugins
 23 | 			?.flatMap((plugin) => plugin.fetchPlugins)
 24 | 			.filter((pl) => pl !== undefined) || [];
 25 | 	const lifeCyclePlugin = {
 26 | 		id: "lifecycle-hooks",
 27 | 		name: "lifecycle-hooks",
 28 | 		hooks: {
 29 | 			onSuccess: options?.fetchOptions?.onSuccess,
 30 | 			onError: options?.fetchOptions?.onError,
 31 | 			onRequest: options?.fetchOptions?.onRequest,
 32 | 			onResponse: options?.fetchOptions?.onResponse,
 33 | 		},
 34 | 	};
 35 | 	const { onSuccess, onError, onRequest, onResponse, ...restOfFetchOptions } =
 36 | 		options?.fetchOptions || {};
 37 | 	const $fetch = createFetch({
 38 | 		baseURL,
 39 | 		...(isCredentialsSupported ? { credentials: "include" } : {}),
 40 | 		method: "GET",
 41 | 		jsonParser(text) {
 42 | 			if (!text) {
 43 | 				return null as any;
 44 | 			}
 45 | 			return parseJSON(text, {
 46 | 				strict: false,
 47 | 			});
 48 | 		},
 49 | 		customFetchImpl: fetch,
 50 | 		...restOfFetchOptions,
 51 | 		plugins: [
 52 | 			lifeCyclePlugin,
 53 | 			...(restOfFetchOptions.plugins || []),
 54 | 			...(options?.disableDefaultFetchPlugins ? [] : [redirectPlugin]),
 55 | 			...pluginsFetchPlugins,
 56 | 		],
 57 | 	});
 58 | 	const { $sessionSignal, session } = getSessionAtom($fetch);
 59 | 	const plugins = options?.plugins || [];
 60 | 	let pluginsActions = {} as Record<string, any>;
 61 | 	let pluginsAtoms = {
 62 | 		$sessionSignal,
 63 | 		session,
 64 | 	} as Record<string, WritableAtom<any>>;
 65 | 	let pluginPathMethods: Record<string, "POST" | "GET"> = {
 66 | 		"/sign-out": "POST",
 67 | 		"/revoke-sessions": "POST",
 68 | 		"/revoke-other-sessions": "POST",
 69 | 		"/delete-user": "POST",
 70 | 	};
 71 | 	const atomListeners: ClientAtomListener[] = [
 72 | 		{
 73 | 			signal: "$sessionSignal",
 74 | 			matcher(path) {
 75 | 				return (
 76 | 					path === "/sign-out" ||
 77 | 					path === "/update-user" ||
 78 | 					path.startsWith("/sign-in") ||
 79 | 					path.startsWith("/sign-up") ||
 80 | 					path === "/delete-user" ||
 81 | 					path === "/verify-email"
 82 | 				);
 83 | 			},
 84 | 		},
 85 | 	];
 86 | 
 87 | 	for (const plugin of plugins) {
 88 | 		if (plugin.getAtoms) {
 89 | 			Object.assign(pluginsAtoms, plugin.getAtoms?.($fetch));
 90 | 		}
 91 | 		if (plugin.pathMethods) {
 92 | 			Object.assign(pluginPathMethods, plugin.pathMethods);
 93 | 		}
 94 | 		if (plugin.atomListeners) {
 95 | 			atomListeners.push(...plugin.atomListeners);
 96 | 		}
 97 | 	}
 98 | 
 99 | 	const $store = {
100 | 		notify: (signal?: Omit<string, "$sessionSignal"> | "$sessionSignal") => {
101 | 			pluginsAtoms[signal as keyof typeof pluginsAtoms]!.set(
102 | 				!pluginsAtoms[signal as keyof typeof pluginsAtoms]!.get(),
103 | 			);
104 | 		},
105 | 		listen: (
106 | 			signal: Omit<string, "$sessionSignal"> | "$sessionSignal",
107 | 			listener: (value: boolean, oldValue?: boolean | undefined) => void,
108 | 		) => {
109 | 			pluginsAtoms[signal as keyof typeof pluginsAtoms]!.subscribe(listener);
110 | 		},
111 | 		atoms: pluginsAtoms,
112 | 	};
113 | 
114 | 	for (const plugin of plugins) {
115 | 		if (plugin.getActions) {
116 | 			Object.assign(
117 | 				pluginsActions,
118 | 				plugin.getActions?.($fetch, $store, options),
119 | 			);
120 | 		}
121 | 	}
122 | 	return {
123 | 		get baseURL() {
124 | 			return baseURL;
125 | 		},
126 | 		pluginsActions,
127 | 		pluginsAtoms,
128 | 		pluginPathMethods,
129 | 		atomListeners,
130 | 		$fetch,
131 | 		$store,
132 | 	};
133 | };
134 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/authentication/slack.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: Slack
  3 | description: Slack provider setup and usage.
  4 | ---
  5 | 
  6 | <Steps>
  7 |     <Step> 
  8 |         ### Get your Slack credentials
  9 |         To use Slack as a social provider, you need to create a Slack app and get your credentials.
 10 | 
 11 |         1. Go to [Your Apps on Slack API](https://api.slack.com/apps) and click "Create New App"
 12 |         2. Choose "From scratch" and give your app a name and select a development workspace
 13 |         3. In your app settings, navigate to "OAuth & Permissions"
 14 |         4. Under "Redirect URLs", add your redirect URL:
 15 |            - For local development: `http://localhost:3000/api/auth/callback/slack`
 16 |            - For production: `https://yourdomain.com/api/auth/callback/slack`
 17 |         5. Copy your Client ID and Client Secret from the "Basic Information" page
 18 | 
 19 |         <Callout>
 20 |             Slack requires HTTPS for redirect URLs in production. For local development, you can use tools like [ngrok](https://ngrok.com/) to create a secure tunnel.
 21 |         </Callout>
 22 |     </Step>
 23 | 
 24 |     <Step>
 25 |         ### Configure the provider
 26 |         To configure the provider, you need to pass the `clientId` and `clientSecret` to `socialProviders.slack` in your auth configuration.
 27 | 
 28 |         ```ts title="auth.ts"
 29 |         import { betterAuth } from "better-auth"
 30 | 
 31 |         export const auth = betterAuth({
 32 |             socialProviders: {
 33 |                 slack: { // [!code highlight]
 34 |                     clientId: process.env.SLACK_CLIENT_ID as string, // [!code highlight]
 35 |                     clientSecret: process.env.SLACK_CLIENT_SECRET as string, // [!code highlight]
 36 |                 }, // [!code highlight]
 37 |             },
 38 |         })
 39 |         ```
 40 |     </Step>
 41 | 
 42 | </Steps>
 43 | 
 44 | ## Usage
 45 | 
 46 | ### Sign In with Slack
 47 | 
 48 | To sign in with Slack, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
 49 | 
 50 | - `provider`: The provider to use. It should be set to `slack`.
 51 | 
 52 | ```ts title="auth-client.ts"
 53 | import { createAuthClient } from "better-auth/client";
 54 | const authClient = createAuthClient();
 55 | 
 56 | const signIn = async () => {
 57 |   const data = await authClient.signIn.social({ provider: "slack" });
 58 | };
 59 | ```
 60 | 
 61 | ### Requesting Additional Scopes
 62 | 
 63 | By default, Slack uses OpenID Connect scopes: `openid`, `profile`, and `email`. You can request additional Slack scopes during sign-in:
 64 | 
 65 | ```ts title="auth-client.ts"
 66 | const signInWithSlack = async () => {
 67 |   await authClient.signIn.social({
 68 |     provider: "slack",
 69 |     scopes: ["channels:read", "chat:write"], // Additional Slack API scopes
 70 |   });
 71 | };
 72 | ```
 73 | 
 74 | ### Workspace-Specific Sign In
 75 | 
 76 | If you want to restrict sign-in to a specific Slack workspace, you can pass the `team` parameter:
 77 | 
 78 | ```ts title="auth.ts"
 79 | socialProviders: {
 80 |     slack: {
 81 |         clientId: process.env.SLACK_CLIENT_ID as string,
 82 |         clientSecret: process.env.SLACK_CLIENT_SECRET as string,
 83 |         team: "T1234567890", // Your Slack workspace ID
 84 |     },
 85 | }
 86 | ```
 87 | 
 88 | ### Using Slack API After Sign In
 89 | 
 90 | After successful authentication, you can access the user's Slack information through the session. The access token can be used to make requests to the Slack API:
 91 | 
 92 | ```ts
 93 | const session = await authClient.getSession();
 94 | if (session?.user) {
 95 |   // Access Slack-specific data
 96 |   const slackUserId = session.user.id; // This is the Slack user ID
 97 |   // The access token is stored securely on the server
 98 | }
 99 | ```
100 | 
101 | <Callout>
102 |   The Slack provider uses OpenID Connect by default, which provides basic user
103 |   information. If you need to access other Slack APIs, make sure to request the
104 |   appropriate scopes during sign-in.
105 | </Callout>
106 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/app/(auth)/forget-password/page.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import { Alert, AlertDescription } from "@/components/ui/alert";
  4 | import { Button } from "@/components/ui/button";
  5 | import {
  6 | 	Card,
  7 | 	CardContent,
  8 | 	CardDescription,
  9 | 	CardFooter,
 10 | 	CardHeader,
 11 | 	CardTitle,
 12 | } from "@/components/ui/card";
 13 | import { Input } from "@/components/ui/input";
 14 | import { Label } from "@/components/ui/label";
 15 | import { client } from "@/lib/auth-client";
 16 | import { AlertCircle, ArrowLeft, CheckCircle2 } from "lucide-react";
 17 | import Link from "next/link";
 18 | import { useState } from "react";
 19 | 
 20 | export default function Component() {
 21 | 	const [email, setEmail] = useState("");
 22 | 	const [isSubmitting, setIsSubmitting] = useState(false);
 23 | 	const [isSubmitted, setIsSubmitted] = useState(false);
 24 | 	const [error, setError] = useState("");
 25 | 
 26 | 	const handleSubmit = async (e: React.FormEvent) => {
 27 | 		e.preventDefault();
 28 | 		setIsSubmitting(true);
 29 | 		setError("");
 30 | 
 31 | 		try {
 32 | 			await client.requestPasswordReset({
 33 | 				email,
 34 | 				redirectTo: "/reset-password",
 35 | 			});
 36 | 			setIsSubmitted(true);
 37 | 		} catch (err) {
 38 | 			setError("An error occurred. Please try again.");
 39 | 		} finally {
 40 | 			setIsSubmitting(false);
 41 | 		}
 42 | 	};
 43 | 
 44 | 	if (isSubmitted) {
 45 | 		return (
 46 | 			<main className="flex flex-col items-center justify-center min-h-[calc(100vh-10rem)]">
 47 | 				<Card className="w-[350px]">
 48 | 					<CardHeader>
 49 | 						<CardTitle>Check your email</CardTitle>
 50 | 						<CardDescription>
 51 | 							We've sent a password reset link to your email.
 52 | 						</CardDescription>
 53 | 					</CardHeader>
 54 | 					<CardContent>
 55 | 						<Alert variant="default">
 56 | 							<CheckCircle2 className="h-4 w-4" />
 57 | 							<AlertDescription>
 58 | 								If you don't see the email, check your spam folder.
 59 | 							</AlertDescription>
 60 | 						</Alert>
 61 | 					</CardContent>
 62 | 					<CardFooter>
 63 | 						<Button
 64 | 							variant="outline"
 65 | 							className="w-full"
 66 | 							onClick={() => setIsSubmitted(false)}
 67 | 						>
 68 | 							<ArrowLeft className="mr-2 h-4 w-4" /> Back to reset password
 69 | 						</Button>
 70 | 					</CardFooter>
 71 | 				</Card>
 72 | 			</main>
 73 | 		);
 74 | 	}
 75 | 
 76 | 	return (
 77 | 		<main className="flex flex-col items-center justify-center min-h-[calc(100vh-10rem)]">
 78 | 			{/* Radial gradient for the container to give a faded look */}
 79 | 			<div className="absolute pointer-events-none inset-0 flex items-center justify-center dark:bg-black bg-white mask-[radial-gradient(ellipse_at_center,transparent_20%,black)]"></div>
 80 | 			<Card className="w-[350px]">
 81 | 				<CardHeader>
 82 | 					<CardTitle>Forgot password</CardTitle>
 83 | 					<CardDescription>
 84 | 						Enter your email to reset your password
 85 | 					</CardDescription>
 86 | 				</CardHeader>
 87 | 				<CardContent>
 88 | 					<form onSubmit={handleSubmit}>
 89 | 						<div className="grid w-full items-center gap-4">
 90 | 							<div className="flex flex-col space-y-1.5">
 91 | 								<Label htmlFor="email">Email</Label>
 92 | 								<Input
 93 | 									id="email"
 94 | 									type="email"
 95 | 									placeholder="Enter your email"
 96 | 									value={email}
 97 | 									onChange={(e) => setEmail(e.target.value)}
 98 | 									required
 99 | 								/>
100 | 							</div>
101 | 						</div>
102 | 						{error && (
103 | 							<Alert variant="destructive" className="mt-4">
104 | 								<AlertCircle className="h-4 w-4" />
105 | 								<AlertDescription>{error}</AlertDescription>
106 | 							</Alert>
107 | 						)}
108 | 						<Button
109 | 							className="w-full mt-4"
110 | 							type="submit"
111 | 							disabled={isSubmitting}
112 | 						>
113 | 							{isSubmitting ? "Sending..." : "Send reset link"}
114 | 						</Button>
115 | 					</form>
116 | 				</CardContent>
117 | 				<CardFooter className="flex justify-center">
118 | 					<Link href="/sign-in">
119 | 						<Button variant="link" className="px-0">
120 | 							Back to sign in
121 | 						</Button>
122 | 					</Link>
123 | 				</CardFooter>
124 | 			</Card>
125 | 		</main>
126 | 	);
127 | }
128 | 
```

--------------------------------------------------------------------------------
/docs/components/builder/code-tabs/index.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import { useState } from "react";
  2 | import { TabBar } from "./tab-bar";
  3 | import { CodeEditor } from "./code-editor";
  4 | import { useAtom } from "jotai";
  5 | import { optionsAtom } from "../store";
  6 | import { js_beautify } from "js-beautify";
  7 | import { signUpString } from "../sign-up";
  8 | import { signInString } from "../sign-in";
  9 | 
 10 | export default function CodeTabs() {
 11 | 	const [options] = useAtom(optionsAtom);
 12 | 
 13 | 	const initialFiles = [
 14 | 		{
 15 | 			id: "1",
 16 | 			name: "auth.ts",
 17 | 			content: `import { betterAuth } from 'better-auth';
 18 | 
 19 | 	export const auth = betterAuth({
 20 | 		${
 21 | 			options.email
 22 | 				? `emailAndPassword: {
 23 | 		enabled: true,
 24 | ${
 25 | 	options.requestPasswordReset
 26 | 		? `async sendResetPassword(data, request) {
 27 | 			// Send an email to the user with a link to reset their password
 28 | 		},`
 29 | 		: ``
 30 | }
 31 | 		},`
 32 | 				: ""
 33 | 		}${
 34 | 			options.socialProviders.length
 35 | 				? `socialProviders: ${JSON.stringify(
 36 | 						options.socialProviders.reduce((acc, provider) => {
 37 | 							return {
 38 | 								...acc,
 39 | 								[provider]: {
 40 | 									clientId: `process.env.${provider.toUpperCase()}_CLIENT_ID!`,
 41 | 									clientSecret: `process.env.${provider.toUpperCase()}_CLIENT_SECRET!`,
 42 | 								},
 43 | 							};
 44 | 						}, {}),
 45 | 					).replace(/"/g, "")},`
 46 | 				: ""
 47 | 		}
 48 | 		${
 49 | 			options.magicLink || options.passkey
 50 | 				? `plugins: [
 51 | 			${
 52 | 				options.magicLink
 53 | 					? `magicLink({
 54 | 				async sendMagicLink(data) {
 55 | 					// Send an email to the user with a magic link
 56 | 				},
 57 | 			}),`
 58 | 					: `${options.passkey ? `passkey(),` : ""}`
 59 | 			}
 60 | 			${options.passkey && options.magicLink ? `passkey(),` : ""}
 61 | 		]`
 62 | 				: ""
 63 | 		}
 64 | 		/** if no database is provided, the user data will be stored in memory.
 65 | 	 * Make sure to provide a database to persist user data **/
 66 | 	});
 67 | 	`,
 68 | 		},
 69 | 		{
 70 | 			id: "2",
 71 | 			name: "auth-client.ts",
 72 | 			content: `import { createAuthClient } from "better-auth/react";
 73 | 			${
 74 | 				options.magicLink || options.passkey
 75 | 					? `import { ${options.magicLink ? "magicLinkClient, " : ""}, ${
 76 | 							options.passkey ? "passkeyClient" : ""
 77 | 						} } from "better-auth/client/plugins";`
 78 | 					: ""
 79 | 			}
 80 | 
 81 | 			export const authClient = createAuthClient({
 82 | 				baseURL: process.env.NEXT_PUBLIC_APP_URL,
 83 | 				${
 84 | 					options.magicLink || options.passkey
 85 | 						? `plugins: [${options.magicLink ? `magicLinkClient(),` : ""}${
 86 | 								options.passkey ? `passkeyClient(),` : ""
 87 | 							}],`
 88 | 						: ""
 89 | 				}
 90 | 			})
 91 | 
 92 | 			export const { signIn, signOut, signUp, useSession } = authClient;
 93 | 			`,
 94 | 		},
 95 | 		{
 96 | 			id: "3",
 97 | 			name: "sign-in.tsx",
 98 | 			content: signInString(options),
 99 | 		},
100 | 	];
101 | 	if (options.email) {
102 | 		initialFiles.push({
103 | 			id: "4",
104 | 			name: "sign-up.tsx",
105 | 			content: signUpString,
106 | 		});
107 | 	}
108 | 
109 | 	const [files, setFiles] = useState(initialFiles);
110 | 	const [activeFileId, setActiveFileId] = useState(files[0].id);
111 | 
112 | 	const handleTabClick = (fileId: string) => {
113 | 		setActiveFileId(fileId);
114 | 	};
115 | 
116 | 	const handleTabClose = (fileId: string) => {
117 | 		setFiles(files.filter((file) => file.id !== fileId));
118 | 		if (activeFileId === fileId) {
119 | 			setActiveFileId(files[0].id);
120 | 		}
121 | 	};
122 | 
123 | 	const activeFile = files.find((file) => file.id === activeFileId);
124 | 
125 | 	return (
126 | 		<div className="w-full mr-auto max-w-[45rem] mt-8 border border-border rounded-md overflow-hidden">
127 | 			<TabBar
128 | 				files={files}
129 | 				activeFileId={activeFileId}
130 | 				onTabClick={handleTabClick}
131 | 				onTabClose={handleTabClose}
132 | 			/>
133 | 			<div className="">
134 | 				{activeFile && (
135 | 					<CodeEditor
136 | 						language="typescript"
137 | 						code={
138 | 							activeFile.name.endsWith(".ts")
139 | 								? js_beautify(activeFile.content)
140 | 								: activeFile.content.replace(/\n{3,}/g, "\n\n")
141 | 						}
142 | 					/>
143 | 				)}
144 | 			</div>
145 | 		</div>
146 | 	);
147 | }
148 | 
```

--------------------------------------------------------------------------------
/packages/cli/test/__snapshots__/auth-schema-sqlite-number-id.txt:
--------------------------------------------------------------------------------

```
 1 | import { sql } from "drizzle-orm";
 2 | import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
 3 | 
 4 | export const custom_user = sqliteTable("custom_user", {
 5 |   id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
 6 |   name: text("name").notNull(),
 7 |   email: text("email").notNull().unique(),
 8 |   emailVerified: integer("email_verified", { mode: "boolean" })
 9 |     .default(false)
10 |     .notNull(),
11 |   image: text("image"),
12 |   createdAt: integer("created_at", { mode: "timestamp_ms" })
13 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
14 |     .notNull(),
15 |   updatedAt: integer("updated_at", { mode: "timestamp_ms" })
16 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
17 |     .$onUpdate(() => /* @__PURE__ */ new Date())
18 |     .notNull(),
19 |   twoFactorEnabled: integer("two_factor_enabled", { mode: "boolean" }).default(
20 |     false,
21 |   ),
22 |   username: text("username").unique(),
23 |   displayUsername: text("display_username"),
24 | });
25 | 
26 | export const custom_session = sqliteTable("custom_session", {
27 |   id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
28 |   expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
29 |   token: text("token").notNull().unique(),
30 |   createdAt: integer("created_at", { mode: "timestamp_ms" })
31 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
32 |     .notNull(),
33 |   updatedAt: integer("updated_at", { mode: "timestamp_ms" })
34 |     .$onUpdate(() => /* @__PURE__ */ new Date())
35 |     .notNull(),
36 |   ipAddress: text("ip_address"),
37 |   userAgent: text("user_agent"),
38 |   userId: integer("user_id")
39 |     .notNull()
40 |     .references(() => custom_user.id, { onDelete: "cascade" }),
41 | });
42 | 
43 | export const custom_account = sqliteTable("custom_account", {
44 |   id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
45 |   accountId: text("account_id").notNull(),
46 |   providerId: text("provider_id").notNull(),
47 |   userId: integer("user_id")
48 |     .notNull()
49 |     .references(() => custom_user.id, { onDelete: "cascade" }),
50 |   accessToken: text("access_token"),
51 |   refreshToken: text("refresh_token"),
52 |   idToken: text("id_token"),
53 |   accessTokenExpiresAt: integer("access_token_expires_at", {
54 |     mode: "timestamp_ms",
55 |   }),
56 |   refreshTokenExpiresAt: integer("refresh_token_expires_at", {
57 |     mode: "timestamp_ms",
58 |   }),
59 |   scope: text("scope"),
60 |   password: text("password"),
61 |   createdAt: integer("created_at", { mode: "timestamp_ms" })
62 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
63 |     .notNull(),
64 |   updatedAt: integer("updated_at", { mode: "timestamp_ms" })
65 |     .$onUpdate(() => /* @__PURE__ */ new Date())
66 |     .notNull(),
67 | });
68 | 
69 | export const custom_verification = sqliteTable("custom_verification", {
70 |   id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
71 |   identifier: text("identifier").notNull(),
72 |   value: text("value").notNull(),
73 |   expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
74 |   createdAt: integer("created_at", { mode: "timestamp_ms" })
75 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
76 |     .notNull(),
77 |   updatedAt: integer("updated_at", { mode: "timestamp_ms" })
78 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
79 |     .$onUpdate(() => /* @__PURE__ */ new Date())
80 |     .notNull(),
81 | });
82 | 
83 | export const twoFactor = sqliteTable("two_factor", {
84 |   id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
85 |   secret: text("secret").notNull(),
86 |   backupCodes: text("backup_codes").notNull(),
87 |   userId: integer("user_id")
88 |     .notNull()
89 |     .references(() => custom_user.id, { onDelete: "cascade" }),
90 | });
91 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/reference/faq.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: FAQ
  3 | description: Frequently asked questions about Better Auth.
  4 | ---
  5 | 
  6 | This page contains frequently asked questions, common issues, and other helpful information about Better Auth.
  7 | 
  8 | <Accordions>
  9 |   <Accordion title="Auth client not working">
 10 |   When encountering `createAuthClient` related errors, make sure to have the correct import path as it varies based on environment.
 11 | 
 12 | If you're using the auth client on react front-end, you'll need to import it from `/react`:
 13 | 
 14 | ```ts title="component.ts"
 15 | import { createAuthClient } from "better-auth/react";
 16 | ```
 17 | 
 18 | Where as if you're using the auth client in Next.js middleware, server-actions, server-components or anything server-related, you'll likely need to import it from `/client`:
 19 | 
 20 | ```ts title="server.ts"
 21 | import { createAuthClient } from "better-auth/client";
 22 | ```
 23 | 
 24 | </Accordion>
 25 | 
 26 | <Accordion title="getSession not working">
 27 | If you try to call `authClient.getSession` on a server environment (e.g, a Next.js server component), it doesn't work since it can't access the cookies. You can use the `auth.api.getSession` instead and pass the request headers to it. 
 28 | 
 29 | ```tsx title="server.tsx"
 30 | import { auth } from "./auth";
 31 | import { headers } from "next/headers";
 32 | 
 33 | const session = await auth.api.getSession({
 34 |     headers: await headers()
 35 | })
 36 | ```
 37 | 
 38 | if you need to use the auth client on the server for different purposes, you still can pass the request headers to it:
 39 | 
 40 | ```tsx title="server.tsx"
 41 | import { authClient } from "./auth-client";
 42 | import { headers } from "next/headers";
 43 | 
 44 | const session = await authClient.getSession({
 45 |     fetchOptions:{
 46 |       headers: await headers()
 47 |     }
 48 | })
 49 | ```
 50 | </Accordion>
 51 | 
 52 | <Accordion title="Adding custom fields to the users table">
 53 | 
 54 | Better Auth provides a type-safe way to extend the user and session schemas, take a look at our docs on <Link href="/docs/concepts/database#extending-core-schema">extending core schema</Link>.
 55 | 
 56 | </Accordion>
 57 | 
 58 | <Accordion title="Difference between getSession and useSession">
 59 | Both `useSession` and `getSession` instances are used fundamentally different based on the situation.
 60 | 
 61 | `useSession` is a hook, meaning it can trigger re-renders whenever session data changes.
 62 | 
 63 | If you have UI you need to change based on user or session data, you can use this hook.
 64 | 
 65 | <Callout type="warn">
 66 |   For performance reasons, do not use this hook on your `layout.tsx` file. We
 67 |   recommend using RSC and use your server auth instance to get the session data
 68 |   via `auth.api.getSession`.
 69 | </Callout>
 70 | 
 71 | `getSession` returns a promise containing data and error.
 72 | 
 73 | For all other situations where you shouldn't use `useSession`, is when you should be using `getSession`.
 74 | 
 75 | <Callout type="info">
 76 |    `getSession` is available on both server and client auth instances.
 77 |    Not just the latter.
 78 | </Callout>
 79 | </Accordion>
 80 | 
 81 | <Accordion title="Common TypeScript Errors">
 82 | If you're facing typescript errors, make sure your tsconfig has `strict` set to `true`:
 83 | ```json title="tsconfig.json"
 84 | {
 85 |   "compilerOptions": {
 86 |     "strict": true,
 87 |   }
 88 | }
 89 | ```
 90 | 
 91 | if you can't set strict to true, you can enable strictNullChecks:
 92 | ```json title="tsconfig.json"
 93 | {
 94 |   "compilerOptions": {
 95 |     "strictNullChecks": true,
 96 |   }
 97 | }
 98 | ```
 99 | 
100 | You can learn more in our <Link href="/docs/concepts/typescript#typescript-config">TypeScript docs</Link>.
101 | </Accordion>
102 | <Accordion title="Can I remove `name`, `image`, or `email` fields from the user table?">
103 | At this time, you can't remove the `name`, `image`, or `email` fields from the user table.
104 | 
105 | We do plan to have more customizability in the future in this regard, but for now, you can't remove these fields.
106 | </Accordion>
107 | </Accordions>
108 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/oauth2/state.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as z from "zod";
  2 | import { APIError } from "better-call";
  3 | import { generateRandomString } from "../crypto";
  4 | import type { GenericEndpointContext } from "@better-auth/core";
  5 | 
  6 | export async function generateState(
  7 | 	c: GenericEndpointContext,
  8 | 	link?: {
  9 | 		email: string;
 10 | 		userId: string;
 11 | 	},
 12 | ) {
 13 | 	const callbackURL = c.body?.callbackURL || c.context.options.baseURL;
 14 | 	if (!callbackURL) {
 15 | 		throw new APIError("BAD_REQUEST", {
 16 | 			message: "callbackURL is required",
 17 | 		});
 18 | 	}
 19 | 
 20 | 	const codeVerifier = generateRandomString(128);
 21 | 	const state = generateRandomString(32);
 22 | 	const stateCookie = c.context.createAuthCookie("state", {
 23 | 		maxAge: 5 * 60 * 1000, // 5 minutes
 24 | 	});
 25 | 	await c.setSignedCookie(
 26 | 		stateCookie.name,
 27 | 		state,
 28 | 		c.context.secret,
 29 | 		stateCookie.attributes,
 30 | 	);
 31 | 	const data = JSON.stringify({
 32 | 		callbackURL,
 33 | 		codeVerifier,
 34 | 		errorURL: c.body?.errorCallbackURL,
 35 | 		newUserURL: c.body?.newUserCallbackURL,
 36 | 		link,
 37 | 		/**
 38 | 		 * This is the actual expiry time of the state
 39 | 		 */
 40 | 		expiresAt: Date.now() + 10 * 60 * 1000,
 41 | 		requestSignUp: c.body?.requestSignUp,
 42 | 	});
 43 | 	const expiresAt = new Date();
 44 | 	expiresAt.setMinutes(expiresAt.getMinutes() + 10);
 45 | 	const verification = await c.context.internalAdapter.createVerificationValue({
 46 | 		value: data,
 47 | 		identifier: state,
 48 | 		expiresAt,
 49 | 	});
 50 | 	if (!verification) {
 51 | 		c.context.logger.error(
 52 | 			"Unable to create verification. Make sure the database adapter is properly working and there is a verification table in the database",
 53 | 		);
 54 | 		throw new APIError("INTERNAL_SERVER_ERROR", {
 55 | 			message: "Unable to create verification",
 56 | 		});
 57 | 	}
 58 | 	return {
 59 | 		state: verification.identifier,
 60 | 		codeVerifier,
 61 | 	};
 62 | }
 63 | 
 64 | export async function parseState(c: GenericEndpointContext) {
 65 | 	const state = c.query.state || c.body.state;
 66 | 	const data = await c.context.internalAdapter.findVerificationValue(state);
 67 | 	if (!data) {
 68 | 		c.context.logger.error("State Mismatch. Verification not found", {
 69 | 			state,
 70 | 		});
 71 | 		const errorURL =
 72 | 			c.context.options.onAPIError?.errorURL || `${c.context.baseURL}/error`;
 73 | 		throw c.redirect(`${errorURL}?error=please_restart_the_process`);
 74 | 	}
 75 | 
 76 | 	const parsedData = z
 77 | 		.object({
 78 | 			callbackURL: z.string(),
 79 | 			codeVerifier: z.string(),
 80 | 			errorURL: z.string().optional(),
 81 | 			newUserURL: z.string().optional(),
 82 | 			expiresAt: z.number(),
 83 | 			link: z
 84 | 				.object({
 85 | 					email: z.string(),
 86 | 					userId: z.coerce.string(),
 87 | 				})
 88 | 				.optional(),
 89 | 			requestSignUp: z.boolean().optional(),
 90 | 		})
 91 | 		.parse(JSON.parse(data.value));
 92 | 
 93 | 	if (!parsedData.errorURL) {
 94 | 		parsedData.errorURL = `${c.context.baseURL}/error`;
 95 | 	}
 96 | 	const stateCookie = c.context.createAuthCookie("state");
 97 | 	const stateCookieValue = await c.getSignedCookie(
 98 | 		stateCookie.name,
 99 | 		c.context.secret,
100 | 	);
101 | 	/**
102 | 	 * This is generally cause security issue and should only be used in
103 | 	 * dev or staging environments. It's currently used by the oauth-proxy
104 | 	 * plugin
105 | 	 */
106 | 	const skipStateCookieCheck = c.context.oauthConfig?.skipStateCookieCheck;
107 | 	if (
108 | 		!skipStateCookieCheck &&
109 | 		(!stateCookieValue || stateCookieValue !== state)
110 | 	) {
111 | 		const errorURL =
112 | 			c.context.options.onAPIError?.errorURL || `${c.context.baseURL}/error`;
113 | 		throw c.redirect(`${errorURL}?error=state_mismatch`);
114 | 	}
115 | 	c.setCookie(stateCookie.name, "", {
116 | 		maxAge: 0,
117 | 	});
118 | 	if (parsedData.expiresAt < Date.now()) {
119 | 		await c.context.internalAdapter.deleteVerificationValue(data.id);
120 | 		const errorURL =
121 | 			c.context.options.onAPIError?.errorURL || `${c.context.baseURL}/error`;
122 | 		throw c.redirect(`${errorURL}?error=please_restart_the_process`);
123 | 	}
124 | 	await c.context.internalAdapter.deleteVerificationValue(data.id);
125 | 	return parsedData;
126 | }
127 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/integrations/fastify.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: Better Auth Fastify Integration Guide
  3 | description: Learn how to seamlessly integrate Better Auth with your Fastify application.
  4 | ---
  5 | 
  6 | This guide provides step-by-step instructions for configuring both essential handlers and CORS settings.
  7 | 
  8 | <Callout type="important">
  9 | A configured Better Auth instance is required before proceeding. If you haven't set this up yet, please consult our [Installation Guide](/docs/installation).
 10 | </Callout>
 11 | 
 12 | ### Prerequisites
 13 | 
 14 | Verify the following requirements before integration:
 15 | 
 16 | - **Node.js Environment**: v16 or later installed
 17 | - **ES Module Support**: Enable ES modules in either:
 18 |   - `package.json`: `{ "type": "module" }`
 19 |   - TypeScript `tsconfig.json`: `{ "module": "ESNext" }`
 20 | - **Fastify Dependencies**:
 21 |   ```package-install
 22 |   fastify @fastify/cors
 23 |   ```
 24 | 
 25 | <Callout type="tip"> For TypeScript: Ensure your `tsconfig.json` includes `"esModuleInterop": true` for optimal compatibility. </Callout>
 26 | 
 27 | ### Authentication Handler Setup
 28 | 
 29 | Configure Better Auth to process authentication requests by creating a catch-all route:
 30 | 
 31 | ```ts title="server.ts"
 32 | import Fastify from "fastify";
 33 | import { auth } from "./auth"; // Your configured Better Auth instance
 34 | 
 35 | const fastify = Fastify({ logger: true });
 36 | 
 37 | // Register authentication endpoint
 38 | fastify.route({
 39 |   method: ["GET", "POST"],
 40 |   url: "/api/auth/*",
 41 |   async handler(request, reply) {
 42 |     try {
 43 |       // Construct request URL
 44 |       const url = new URL(request.url, `http://${request.headers.host}`);
 45 |       
 46 |       // Convert Fastify headers to standard Headers object
 47 |       const headers = new Headers();
 48 |       Object.entries(request.headers).forEach(([key, value]) => {
 49 |         if (value) headers.append(key, value.toString());
 50 |       });
 51 | 
 52 |       // Create Fetch API-compatible request
 53 |       const req = new Request(url.toString(), {
 54 |         method: request.method,
 55 |         headers,
 56 |         body: request.body ? JSON.stringify(request.body) : undefined,
 57 |       });
 58 | 
 59 |       // Process authentication request
 60 |       const response = await auth.handler(req);
 61 | 
 62 |       // Forward response to client
 63 |       reply.status(response.status);
 64 |       response.headers.forEach((value, key) => reply.header(key, value));
 65 |       reply.send(response.body ? await response.text() : null);
 66 | 
 67 |     } catch (error) {
 68 |       fastify.log.error("Authentication Error:", error);
 69 |       reply.status(500).send({ 
 70 |         error: "Internal authentication error",
 71 |         code: "AUTH_FAILURE"
 72 |       });
 73 |     }
 74 |   }
 75 | });
 76 | 
 77 | // Initialize server
 78 | fastify.listen({ port: 4000 }, (err) => {
 79 |   if (err) {
 80 |     fastify.log.error(err);
 81 |     process.exit(1);
 82 |   }
 83 |   console.log("Server running on port 4000");
 84 | });
 85 | ```
 86 | 
 87 | ### Trusted origins
 88 | 
 89 | When a request is made from a different origin, the request will be blocked by default. You can add trusted origins to the `auth` instance.
 90 | 
 91 | ```ts
 92 | export const auth = betterAuth({
 93 |   trustedOrigins: ["http://localhost:3000", "https://example.com"],
 94 | });
 95 | ```
 96 | 
 97 | ### Configuring CORS
 98 | 
 99 | Secure your API endpoints with proper CORS configuration:
100 | 
101 | ```ts
102 | import fastifyCors from "@fastify/cors";
103 | 
104 | // Configure CORS policies
105 | fastify.register(fastifyCors, {
106 |   origin: process.env.CLIENT_ORIGIN || "http://localhost:3000",
107 |   methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
108 |   allowedHeaders: [
109 |     "Content-Type",
110 |     "Authorization",
111 |     "X-Requested-With"
112 |   ],
113 |   credentials: true,
114 |   maxAge: 86400
115 | });
116 | 
117 | // Mount authentication handler after CORS registration
118 | // (Use previous handler configuration here)
119 | ```
120 | 
121 | <Callout type="warning"> Always restrict CORS origins in production environments. Use environment variables for dynamic configuration. </Callout>
122 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/components/ui/dialog.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import * as DialogPrimitive from "@radix-ui/react-dialog";
  5 | import { XIcon } from "lucide-react";
  6 | 
  7 | import { cn } from "@/lib/utils";
  8 | 
  9 | function Dialog({
 10 | 	...props
 11 | }: React.ComponentProps<typeof DialogPrimitive.Root>) {
 12 | 	return <DialogPrimitive.Root data-slot="dialog" {...props} />;
 13 | }
 14 | 
 15 | function DialogTrigger({
 16 | 	...props
 17 | }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
 18 | 	return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
 19 | }
 20 | 
 21 | function DialogPortal({
 22 | 	...props
 23 | }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
 24 | 	return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
 25 | }
 26 | 
 27 | function DialogClose({
 28 | 	...props
 29 | }: React.ComponentProps<typeof DialogPrimitive.Close>) {
 30 | 	return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
 31 | }
 32 | 
 33 | function DialogOverlay({
 34 | 	className,
 35 | 	...props
 36 | }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
 37 | 	return (
 38 | 		<DialogPrimitive.Overlay
 39 | 			data-slot="dialog-overlay"
 40 | 			className={cn(
 41 | 				"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80",
 42 | 				className,
 43 | 			)}
 44 | 			{...props}
 45 | 		/>
 46 | 	);
 47 | }
 48 | 
 49 | function DialogContent({
 50 | 	className,
 51 | 	children,
 52 | 	...props
 53 | }: React.ComponentProps<typeof DialogPrimitive.Content>) {
 54 | 	return (
 55 | 		<DialogPortal data-slot="dialog-portal">
 56 | 			<DialogOverlay />
 57 | 			<DialogPrimitive.Content
 58 | 				data-slot="dialog-content"
 59 | 				className={cn(
 60 | 					"fixed left-[50%] top-[50%] z-50 grid max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg w-11/12",
 61 | 					className,
 62 | 				)}
 63 | 				{...props}
 64 | 			>
 65 | 				{children}
 66 | 				<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
 67 | 					<XIcon />
 68 | 					<span className="sr-only">Close</span>
 69 | 				</DialogPrimitive.Close>
 70 | 			</DialogPrimitive.Content>
 71 | 		</DialogPortal>
 72 | 	);
 73 | }
 74 | 
 75 | function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
 76 | 	return (
 77 | 		<div
 78 | 			data-slot="dialog-header"
 79 | 			className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
 80 | 			{...props}
 81 | 		/>
 82 | 	);
 83 | }
 84 | 
 85 | function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
 86 | 	return (
 87 | 		<div
 88 | 			data-slot="dialog-footer"
 89 | 			className={cn(
 90 | 				"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
 91 | 				className,
 92 | 			)}
 93 | 			{...props}
 94 | 		/>
 95 | 	);
 96 | }
 97 | 
 98 | function DialogTitle({
 99 | 	className,
100 | 	...props
101 | }: React.ComponentProps<typeof DialogPrimitive.Title>) {
102 | 	return (
103 | 		<DialogPrimitive.Title
104 | 			data-slot="dialog-title"
105 | 			className={cn("text-lg leading-none font-semibold", className)}
106 | 			{...props}
107 | 		/>
108 | 	);
109 | }
110 | 
111 | function DialogDescription({
112 | 	className,
113 | 	...props
114 | }: React.ComponentProps<typeof DialogPrimitive.Description>) {
115 | 	return (
116 | 		<DialogPrimitive.Description
117 | 			data-slot="dialog-description"
118 | 			className={cn("text-muted-foreground text-sm", className)}
119 | 			{...props}
120 | 		/>
121 | 	);
122 | }
123 | 
124 | export {
125 | 	Dialog,
126 | 	DialogClose,
127 | 	DialogContent,
128 | 	DialogDescription,
129 | 	DialogFooter,
130 | 	DialogHeader,
131 | 	DialogOverlay,
132 | 	DialogPortal,
133 | 	DialogTitle,
134 | 	DialogTrigger,
135 | };
136 | 
```

--------------------------------------------------------------------------------
/docs/components/ui/dialog.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import * as DialogPrimitive from "@radix-ui/react-dialog";
  5 | import { XIcon } from "lucide-react";
  6 | 
  7 | import { cn } from "@/lib/utils";
  8 | 
  9 | function Dialog({
 10 | 	...props
 11 | }: React.ComponentProps<typeof DialogPrimitive.Root>) {
 12 | 	return <DialogPrimitive.Root data-slot="dialog" {...props} />;
 13 | }
 14 | 
 15 | function DialogTrigger({
 16 | 	...props
 17 | }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
 18 | 	return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
 19 | }
 20 | 
 21 | function DialogPortal({
 22 | 	...props
 23 | }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
 24 | 	return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
 25 | }
 26 | 
 27 | function DialogClose({
 28 | 	...props
 29 | }: React.ComponentProps<typeof DialogPrimitive.Close>) {
 30 | 	return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
 31 | }
 32 | 
 33 | function DialogOverlay({
 34 | 	className,
 35 | 	...props
 36 | }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
 37 | 	return (
 38 | 		<DialogPrimitive.Overlay
 39 | 			data-slot="dialog-overlay"
 40 | 			className={cn(
 41 | 				"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80",
 42 | 				className,
 43 | 			)}
 44 | 			{...props}
 45 | 		/>
 46 | 	);
 47 | }
 48 | 
 49 | function DialogContent({
 50 | 	className,
 51 | 	children,
 52 | 	...props
 53 | }: React.ComponentProps<typeof DialogPrimitive.Content>) {
 54 | 	return (
 55 | 		<DialogPortal data-slot="dialog-portal">
 56 | 			<DialogOverlay />
 57 | 			<DialogPrimitive.Content
 58 | 				data-slot="dialog-content"
 59 | 				className={cn(
 60 | 					"fixed left-[50%] top-[50%] z-50 grid max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg w-11/12",
 61 | 					className,
 62 | 				)}
 63 | 				{...props}
 64 | 			>
 65 | 				{children}
 66 | 				<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
 67 | 					<XIcon />
 68 | 					<span className="sr-only">Close</span>
 69 | 				</DialogPrimitive.Close>
 70 | 			</DialogPrimitive.Content>
 71 | 		</DialogPortal>
 72 | 	);
 73 | }
 74 | 
 75 | function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
 76 | 	return (
 77 | 		<div
 78 | 			data-slot="dialog-header"
 79 | 			className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
 80 | 			{...props}
 81 | 		/>
 82 | 	);
 83 | }
 84 | 
 85 | function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
 86 | 	return (
 87 | 		<div
 88 | 			data-slot="dialog-footer"
 89 | 			className={cn(
 90 | 				"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
 91 | 				className,
 92 | 			)}
 93 | 			{...props}
 94 | 		/>
 95 | 	);
 96 | }
 97 | 
 98 | function DialogTitle({
 99 | 	className,
100 | 	...props
101 | }: React.ComponentProps<typeof DialogPrimitive.Title>) {
102 | 	return (
103 | 		<DialogPrimitive.Title
104 | 			data-slot="dialog-title"
105 | 			className={cn("text-lg leading-none font-semibold", className)}
106 | 			{...props}
107 | 		/>
108 | 	);
109 | }
110 | 
111 | function DialogDescription({
112 | 	className,
113 | 	...props
114 | }: React.ComponentProps<typeof DialogPrimitive.Description>) {
115 | 	return (
116 | 		<DialogPrimitive.Description
117 | 			data-slot="dialog-description"
118 | 			className={cn("text-muted-foreground text-sm", className)}
119 | 			{...props}
120 | 		/>
121 | 	);
122 | }
123 | 
124 | export {
125 | 	Dialog,
126 | 	DialogClose,
127 | 	DialogContent,
128 | 	DialogDescription,
129 | 	DialogFooter,
130 | 	DialogHeader,
131 | 	DialogOverlay,
132 | 	DialogPortal,
133 | 	DialogTitle,
134 | 	DialogTrigger,
135 | };
136 | 
```

--------------------------------------------------------------------------------
/docs/components/ui/form.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import * as LabelPrimitive from "@radix-ui/react-label";
  5 | import { Slot } from "@radix-ui/react-slot";
  6 | import {
  7 | 	Controller,
  8 | 	FormProvider,
  9 | 	useFormContext,
 10 | 	useFormState,
 11 | 	type ControllerProps,
 12 | 	type FieldPath,
 13 | 	type FieldValues,
 14 | } from "react-hook-form";
 15 | 
 16 | import { cn } from "@/lib/utils";
 17 | import { Label } from "@/components/ui/label";
 18 | 
 19 | const Form = FormProvider;
 20 | 
 21 | type FormFieldContextValue<
 22 | 	TFieldValues extends FieldValues = FieldValues,
 23 | 	TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
 24 | > = {
 25 | 	name: TName;
 26 | };
 27 | 
 28 | const FormFieldContext = React.createContext<FormFieldContextValue>(
 29 | 	{} as FormFieldContextValue,
 30 | );
 31 | 
 32 | const FormField = <
 33 | 	TFieldValues extends FieldValues = FieldValues,
 34 | 	TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
 35 | >({
 36 | 	...props
 37 | }: ControllerProps<TFieldValues, TName>) => {
 38 | 	return (
 39 | 		<FormFieldContext.Provider value={{ name: props.name }}>
 40 | 			<Controller {...props} />
 41 | 		</FormFieldContext.Provider>
 42 | 	);
 43 | };
 44 | 
 45 | const useFormField = () => {
 46 | 	const fieldContext = React.useContext(FormFieldContext);
 47 | 	const itemContext = React.useContext(FormItemContext);
 48 | 	const { getFieldState } = useFormContext();
 49 | 	const formState = useFormState({ name: fieldContext.name });
 50 | 	const fieldState = getFieldState(fieldContext.name, formState);
 51 | 
 52 | 	if (!fieldContext) {
 53 | 		throw new Error("useFormField should be used within <FormField>");
 54 | 	}
 55 | 
 56 | 	const { id } = itemContext;
 57 | 
 58 | 	return {
 59 | 		id,
 60 | 		name: fieldContext.name,
 61 | 		formItemId: `${id}-form-item`,
 62 | 		formDescriptionId: `${id}-form-item-description`,
 63 | 		formMessageId: `${id}-form-item-message`,
 64 | 		...fieldState,
 65 | 	};
 66 | };
 67 | 
 68 | type FormItemContextValue = {
 69 | 	id: string;
 70 | };
 71 | 
 72 | const FormItemContext = React.createContext<FormItemContextValue>(
 73 | 	{} as FormItemContextValue,
 74 | );
 75 | 
 76 | function FormItem({ className, ...props }: React.ComponentProps<"div">) {
 77 | 	const id = React.useId();
 78 | 
 79 | 	return (
 80 | 		<FormItemContext.Provider value={{ id }}>
 81 | 			<div
 82 | 				data-slot="form-item"
 83 | 				className={cn("grid gap-2", className)}
 84 | 				{...props}
 85 | 			/>
 86 | 		</FormItemContext.Provider>
 87 | 	);
 88 | }
 89 | 
 90 | function FormLabel({
 91 | 	className,
 92 | 	...props
 93 | }: React.ComponentProps<typeof LabelPrimitive.Root>) {
 94 | 	const { error, formItemId } = useFormField();
 95 | 
 96 | 	return (
 97 | 		<Label
 98 | 			data-slot="form-label"
 99 | 			data-error={!!error}
100 | 			className={cn("data-[error=true]:text-destructive-foreground", className)}
101 | 			htmlFor={formItemId}
102 | 			{...props}
103 | 		/>
104 | 	);
105 | }
106 | 
107 | function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
108 | 	const { error, formItemId, formDescriptionId, formMessageId } =
109 | 		useFormField();
110 | 
111 | 	return (
112 | 		<Slot
113 | 			data-slot="form-control"
114 | 			id={formItemId}
115 | 			aria-describedby={
116 | 				!error
117 | 					? `${formDescriptionId}`
118 | 					: `${formDescriptionId} ${formMessageId}`
119 | 			}
120 | 			aria-invalid={!!error}
121 | 			{...props}
122 | 		/>
123 | 	);
124 | }
125 | 
126 | function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
127 | 	const { formDescriptionId } = useFormField();
128 | 
129 | 	return (
130 | 		<p
131 | 			data-slot="form-description"
132 | 			id={formDescriptionId}
133 | 			className={cn("text-muted-foreground text-sm", className)}
134 | 			{...props}
135 | 		/>
136 | 	);
137 | }
138 | 
139 | function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
140 | 	const { error, formMessageId } = useFormField();
141 | 	const body = error ? String(error?.message ?? "") : props.children;
142 | 
143 | 	if (!body) {
144 | 		return null;
145 | 	}
146 | 
147 | 	return (
148 | 		<p
149 | 			data-slot="form-message"
150 | 			id={formMessageId}
151 | 			className={cn("text-destructive-foreground text-sm", className)}
152 | 			{...props}
153 | 		>
154 | 			{body}
155 | 		</p>
156 | 	);
157 | }
158 | 
159 | export {
160 | 	useFormField,
161 | 	Form,
162 | 	FormItem,
163 | 	FormLabel,
164 | 	FormControl,
165 | 	FormDescription,
166 | 	FormMessage,
167 | 	FormField,
168 | };
169 | 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Report an issue
 2 | description: Create a report to help us improve
 3 | body:
 4 |   - type: checkboxes
 5 |     attributes:
 6 |       label: Is this suited for github?
 7 |       description: Feel free to join the discord community [here](https://discord.gg/better-auth), we can usually respond faster to any questions.
 8 |       options:
 9 |         - label: Yes, this is suited for github
10 |   - type: markdown
11 |     attributes:
12 |       value: |
13 |         This template is used for reporting a issue with better-auth.
14 | 
15 |         Feature requests should be opened in [here](https://github.com/better-auth/better-auth/issues/new?assignees=&labels=&projects=&template=feature_request.md&title=).
16 | 
17 |         Before opening a new issue, please do a [search](https://github.com/better-auth/better-auth/issues) of existing issues and :+1: upvote the existing issue instead. This will result in a quicker resolution.
18 |   - type: textarea
19 |     attributes:
20 |       label: To Reproduce
21 |       description: A step-by-step description of how to reproduce the issue, based on the linked reproduction. Screenshots can be provided in the issue body below. If using code blocks, make sure that [syntax highlighting is correct](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#syntax-highlighting) and double check that the rendered preview is not broken.
22 |       placeholder: |
23 |         Ex:
24 |         1. Create a backend
25 |         2. Create a frontend and use client
26 |         3. X will happen
27 |     validations:
28 |       required: true
29 |   - type: textarea
30 |     attributes:
31 |       label: Current vs. Expected behavior
32 |       description: |
33 |         A clear and concise description of what the bug is (e.g., screenshots, logs, etc.), and what you expected to happen.
34 | 
35 |         **Skipping this/failure to provide complete information of the bug will result in the issue being closed.**
36 |       placeholder: 'Following the steps from the previous section, I expected A to happen, but I observed B instead.'
37 |     validations:
38 |       required: true
39 |   - type: input
40 |     attributes:
41 |       label: What version of Better Auth are you using?
42 |       description: Please provide the current version of `better-auth` that you are reporting the bug on
43 |       placeholder: "1.x.x"
44 |     validations:
45 |       required: true
46 |   - type: textarea
47 |     attributes:
48 |       label: System info
49 |       description: Output of `npx @better-auth/cli info --json`
50 |       render: bash
51 |       placeholder: System and Better Auth info
52 |     validations:
53 |       required: true
54 |   - type: dropdown
55 |     attributes:
56 |       label: Which area(s) are affected? (Select all that apply)
57 |       multiple: true
58 |       options:
59 |         - 'Backend'
60 |         - 'Client'
61 |         - 'Types'
62 |         - 'Documentation'
63 |         - 'Package'
64 |         - 'Other'
65 |     validations:
66 |       required: true
67 |   - type: textarea
68 |     attributes:
69 |       label: Auth config (if applicable)
70 |       description: If you haven't already shared a reproducible example or don't think it's unrelated, please include your auth config. Make sure to remove any sensitive information.
71 |       render: typescript
72 |       value: |
73 |         import { betterAuth } from "better-auth"
74 |         export const auth = betterAuth({
75 |           emailAndPassword: {  
76 |             enabled: true
77 |           },
78 |         });
79 |   - type: textarea
80 |     attributes:
81 |       label: Additional context
82 |       description: |
83 |         Any extra information that might help us investigate. For example, is it only reproducible online, or locally too? Is the issue only happening in a specific browser? etc.
84 |       placeholder: |
85 |         I tested my reproduction against the latest release.
86 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/app/globals.css:
--------------------------------------------------------------------------------

```css
  1 | @import "tailwindcss";
  2 | @config "../tailwind.config.ts";
  3 | @import "tw-animate-css";
  4 | 
  5 | @custom-variant dark (&:is(.dark *));
  6 | 
  7 | :root {
  8 | 	--background: hsl(0 0% 100%);
  9 | 	--foreground: hsl(20 14.3% 4.1%);
 10 | 	--card: hsl(0 0% 100%);
 11 | 	--card-foreground: hsl(20 14.3% 4.1%);
 12 | 	--popover: hsl(0 0% 100%);
 13 | 	--popover-foreground: hsl(20 14.3% 4.1%);
 14 | 	--primary: hsl(24 9.8% 10%);
 15 | 	--primary-foreground: hsl(60 9.1% 97.8%);
 16 | 	--secondary: hsl(60 4.8% 95.9%);
 17 | 	--secondary-foreground: hsl(24 9.8% 10%);
 18 | 	--muted: hsl(60 4.8% 95.9%);
 19 | 	--muted-foreground: hsl(25 5.3% 44.7%);
 20 | 	--accent: hsl(60 4.8% 95.9%);
 21 | 	--accent-foreground: hsl(24 9.8% 10%);
 22 | 	--destructive: hsl(0 84.2% 60.2%);
 23 | 	--destructive-foreground: hsl(60 9.1% 97.8%);
 24 | 	--border: hsl(20 5.9% 90%);
 25 | 	--input: hsl(20 5.9% 90%);
 26 | 	--ring: hsl(20 14.3% 4.1%);
 27 | 	--radius: 0rem;
 28 | 	--chart-1: hsl(12 76% 61%);
 29 | 	--chart-2: hsl(173 58% 39%);
 30 | 	--chart-3: hsl(197 37% 24%);
 31 | 	--chart-4: hsl(43 74% 66%);
 32 | 	--chart-5: hsl(27 87% 67%);
 33 | }
 34 | 
 35 | .dark {
 36 | 	--background: hsl(20 14.3% 4.1%);
 37 | 	--foreground: hsl(60 9.1% 97.8%);
 38 | 	--card: hsl(20 14.3% 4.1%);
 39 | 	--card-foreground: hsl(60 9.1% 97.8%);
 40 | 	--popover: hsl(20 14.3% 4.1%);
 41 | 	--popover-foreground: hsl(60 9.1% 97.8%);
 42 | 	--primary: hsl(60 9.1% 97.8%);
 43 | 	--primary-foreground: hsl(24 9.8% 10%);
 44 | 	--secondary: hsl(12 6.5% 15.1%);
 45 | 	--secondary-foreground: hsl(60 9.1% 97.8%);
 46 | 	--muted: hsl(12 6.5% 15.1%);
 47 | 	--muted-foreground: hsl(24 5.4% 63.9%);
 48 | 	--accent: hsl(12 6.5% 15.1%);
 49 | 	--accent-foreground: hsl(60 9.1% 97.8%);
 50 | 	--destructive: hsl(0 62.8% 30.6%);
 51 | 	--destructive-foreground: hsl(60 9.1% 97.8%);
 52 | 	--border: hsl(12 6.5% 15.1%);
 53 | 	--input: hsl(12 6.5% 15.1%);
 54 | 	--ring: hsl(24 5.7% 82.9%);
 55 | 	--chart-1: hsl(220 70% 50%);
 56 | 	--chart-2: hsl(160 60% 45%);
 57 | 	--chart-3: hsl(30 80% 55%);
 58 | 	--chart-4: hsl(280 65% 60%);
 59 | 	--chart-5: hsl(340 75% 55%);
 60 | }
 61 | 
 62 | @theme inline {
 63 | 	--color-background: var(--background);
 64 | 	--color-foreground: var(--foreground);
 65 | 	--color-card: var(--card);
 66 | 	--color-card-foreground: var(--card-foreground);
 67 | 	--color-popover: var(--popover);
 68 | 	--color-popover-foreground: var(--popover-foreground);
 69 | 	--color-primary: var(--primary);
 70 | 	--color-primary-foreground: var(--primary-foreground);
 71 | 	--color-secondary: var(--secondary);
 72 | 	--color-secondary-foreground: var(--secondary-foreground);
 73 | 	--color-muted: var(--muted);
 74 | 	--color-muted-foreground: var(--muted-foreground);
 75 | 	--color-accent: var(--accent);
 76 | 	--color-accent-foreground: var(--accent-foreground);
 77 | 	--color-destructive: var(--destructive);
 78 | 	--color-destructive-foreground: var(--destructive-foreground);
 79 | 	--color-border: var(--border);
 80 | 	--color-input: var(--input);
 81 | 	--color-ring: var(--ring);
 82 | 	--color-chart-1: var(--chart-1);
 83 | 	--color-chart-2: var(--chart-2);
 84 | 	--color-chart-3: var(--chart-3);
 85 | 	--color-chart-4: var(--chart-4);
 86 | 	--color-chart-5: var(--chart-5);
 87 | 	--radius-sm: calc(var(--radius) - 4px);
 88 | 	--radius-md: calc(var(--radius) - 2px);
 89 | 	--radius-lg: var(--radius);
 90 | 	--radius-xl: calc(var(--radius) + 4px);
 91 | 	--color-sidebar: var(--sidebar);
 92 | 	--color-sidebar-foreground: var(--sidebar-foreground);
 93 | 	--color-sidebar-primary: var(--sidebar-primary);
 94 | 	--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
 95 | 	--color-sidebar-accent: var(--sidebar-accent);
 96 | 	--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
 97 | 	--color-sidebar-border: var(--sidebar-border);
 98 | 	--color-sidebar-ring: var(--sidebar-ring);
 99 | }
100 | 
101 | @layer base {
102 | 	* {
103 | 		@apply border-border;
104 | 	}
105 | 	body {
106 | 		@apply bg-background text-foreground;
107 | 	}
108 | }
109 | 
110 | .no-visible-scrollbar {
111 | 	scrollbar-width: none;
112 | 	-ms-overflow-style: none;
113 | 	-webkit-overflow-scrolling: touch;
114 | }
115 | 
116 | .no-visible-scrollbar::-webkit-scrollbar {
117 | 	display: none;
118 | }
119 | 
```

--------------------------------------------------------------------------------
/docs/components/blocks/features.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import { useId } from "react";
  2 | 
  3 | export function Features() {
  4 | 	return (
  5 | 		<div className="py-2">
  6 | 			<div className="mt-2 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 gap-10 md:gap-2 max-w-7xl mx-auto">
  7 | 				{grid.map((feature, i) => (
  8 | 					<div
  9 | 						key={feature.title}
 10 | 						className="relative bg-gradient-to-b dark:from-neutral-900 from-neutral-100 dark:to-neutral-950 to-white px-6 py-2 overflow-hidden"
 11 | 					>
 12 | 						<Grid size={i * 5 + 10} />
 13 | 						<p className="text-base font-bold text-neutral-800 dark:text-white relative z-0">
 14 | 							{feature.title}
 15 | 						</p>
 16 | 						<p className="text-neutral-600 dark:text-neutral-400 text-base font-normal relative z-0">
 17 | 							{feature.description}
 18 | 						</p>
 19 | 					</div>
 20 | 				))}
 21 | 			</div>
 22 | 		</div>
 23 | 	);
 24 | }
 25 | 
 26 | const grid = [
 27 | 	{
 28 | 		title: "Framework Agnostic",
 29 | 		description: "Support for most popular frameworks",
 30 | 	},
 31 | 	{
 32 | 		title: "Email & Password",
 33 | 		description:
 34 | 			"Built-in support for secure email and password authentication",
 35 | 	},
 36 | 	{
 37 | 		title: "Account & Session Management",
 38 | 		description: "Manage user accounts and sessions with ease",
 39 | 	},
 40 | 	{
 41 | 		title: "Built-In Rate Limiter",
 42 | 		description: "Built-in rate limiter with custom rules",
 43 | 	},
 44 | 	{
 45 | 		title: "Automatic Database Management",
 46 | 		description: "Automatic database management and migrations",
 47 | 	},
 48 | 	{
 49 | 		title: "Social Sign-on",
 50 | 		description: "Multiple social sign-on providers",
 51 | 	},
 52 | 	{
 53 | 		title: "Organization & Access Control",
 54 | 		description: "Manage organizations and access control",
 55 | 	},
 56 | 	{
 57 | 		title: "Two Factor Authentication",
 58 | 		description: "Secure your users with two factor authentication",
 59 | 	},
 60 | 	{
 61 | 		title: "Plugin Ecosystem",
 62 | 		description: "Even more capabilities with plugins",
 63 | 	},
 64 | ];
 65 | 
 66 | export const Grid = ({
 67 | 	pattern,
 68 | 	size,
 69 | }: {
 70 | 	pattern?: number[][];
 71 | 	size?: number;
 72 | }) => {
 73 | 	const p = pattern ?? [
 74 | 		[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],
 75 | 		[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],
 76 | 		[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],
 77 | 		[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],
 78 | 		[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],
 79 | 	];
 80 | 	return (
 81 | 		<div className="pointer-events-none absolute left-1/2 top-0  -ml-20 -mt-2 h-full w-full [mask-image:linear-gradient(white,transparent)]">
 82 | 			<div className="absolute inset-0 bg-gradient-to-r  [mask-image:radial-gradient(farthest-side_at_top,white,transparent)] dark:from-zinc-900/30 from-zinc-100/30 to-zinc-300/30 dark:to-zinc-900/30 opacity-100">
 83 | 				<GridPattern
 84 | 					width={size ?? 20}
 85 | 					height={size ?? 20}
 86 | 					x="-12"
 87 | 					y="4"
 88 | 					squares={p}
 89 | 					className="absolute inset-0 h-full w-full  mix-blend-overlay dark:fill-white/10 dark:stroke-white/10 stroke-black/10 fill-black/10"
 90 | 				/>
 91 | 			</div>
 92 | 		</div>
 93 | 	);
 94 | };
 95 | 
 96 | export function GridPattern({ width, height, x, y, squares, ...props }: any) {
 97 | 	const patternId = useId();
 98 | 
 99 | 	return (
100 | 		<svg aria-hidden="true" {...props}>
101 | 			<defs>
102 | 				<pattern
103 | 					id={patternId}
104 | 					width={width}
105 | 					height={height}
106 | 					patternUnits="userSpaceOnUse"
107 | 					x={x}
108 | 					y={y}
109 | 				>
110 | 					<path d={`M.5 ${height}V.5H${width}`} fill="none" />
111 | 				</pattern>
112 | 			</defs>
113 | 			<rect
114 | 				width="100%"
115 | 				height="100%"
116 | 				strokeWidth={0}
117 | 				fill={`url(#${patternId})`}
118 | 			/>
119 | 			{squares && (
120 | 				<svg x={x} y={y} className="overflow-visible">
121 | 					{squares.map(([x, y]: any, idx: number) => (
122 | 						<rect
123 | 							strokeWidth="0"
124 | 							key={`${x}-${y}-${idx}`}
125 | 							width={width + 1}
126 | 							height={height + 1}
127 | 							x={x * width}
128 | 							y={y * height}
129 | 						/>
130 | 					))}
131 | 				</svg>
132 | 			)}
133 | 		</svg>
134 | 	);
135 | }
136 | 
```

--------------------------------------------------------------------------------
/docs/components/ui/alert-dialog.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
  5 | 
  6 | import { cn } from "@/lib/utils";
  7 | import { buttonVariants } from "@/components/ui/button";
  8 | 
  9 | function AlertDialog({
 10 | 	...props
 11 | }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
 12 | 	return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
 13 | }
 14 | 
 15 | function AlertDialogTrigger({
 16 | 	...props
 17 | }: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
 18 | 	return (
 19 | 		<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
 20 | 	);
 21 | }
 22 | 
 23 | function AlertDialogPortal({
 24 | 	...props
 25 | }: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
 26 | 	return (
 27 | 		<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
 28 | 	);
 29 | }
 30 | 
 31 | function AlertDialogOverlay({
 32 | 	className,
 33 | 	...props
 34 | }: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
 35 | 	return (
 36 | 		<AlertDialogPrimitive.Overlay
 37 | 			data-slot="alert-dialog-overlay"
 38 | 			className={cn(
 39 | 				"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80",
 40 | 				className,
 41 | 			)}
 42 | 			{...props}
 43 | 		/>
 44 | 	);
 45 | }
 46 | 
 47 | function AlertDialogContent({
 48 | 	className,
 49 | 	...props
 50 | }: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
 51 | 	return (
 52 | 		<AlertDialogPortal>
 53 | 			<AlertDialogOverlay />
 54 | 			<AlertDialogPrimitive.Content
 55 | 				data-slot="alert-dialog-content"
 56 | 				className={cn(
 57 | 					"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
 58 | 					className,
 59 | 				)}
 60 | 				{...props}
 61 | 			/>
 62 | 		</AlertDialogPortal>
 63 | 	);
 64 | }
 65 | 
 66 | function AlertDialogHeader({
 67 | 	className,
 68 | 	...props
 69 | }: React.ComponentProps<"div">) {
 70 | 	return (
 71 | 		<div
 72 | 			data-slot="alert-dialog-header"
 73 | 			className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
 74 | 			{...props}
 75 | 		/>
 76 | 	);
 77 | }
 78 | 
 79 | function AlertDialogFooter({
 80 | 	className,
 81 | 	...props
 82 | }: React.ComponentProps<"div">) {
 83 | 	return (
 84 | 		<div
 85 | 			data-slot="alert-dialog-footer"
 86 | 			className={cn(
 87 | 				"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
 88 | 				className,
 89 | 			)}
 90 | 			{...props}
 91 | 		/>
 92 | 	);
 93 | }
 94 | 
 95 | function AlertDialogTitle({
 96 | 	className,
 97 | 	...props
 98 | }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
 99 | 	return (
100 | 		<AlertDialogPrimitive.Title
101 | 			data-slot="alert-dialog-title"
102 | 			className={cn("text-lg font-semibold", className)}
103 | 			{...props}
104 | 		/>
105 | 	);
106 | }
107 | 
108 | function AlertDialogDescription({
109 | 	className,
110 | 	...props
111 | }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
112 | 	return (
113 | 		<AlertDialogPrimitive.Description
114 | 			data-slot="alert-dialog-description"
115 | 			className={cn("text-muted-foreground text-sm", className)}
116 | 			{...props}
117 | 		/>
118 | 	);
119 | }
120 | 
121 | function AlertDialogAction({
122 | 	className,
123 | 	...props
124 | }: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
125 | 	return (
126 | 		<AlertDialogPrimitive.Action
127 | 			className={cn(buttonVariants(), className)}
128 | 			{...props}
129 | 		/>
130 | 	);
131 | }
132 | 
133 | function AlertDialogCancel({
134 | 	className,
135 | 	...props
136 | }: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
137 | 	return (
138 | 		<AlertDialogPrimitive.Cancel
139 | 			className={cn(buttonVariants({ variant: "outline" }), className)}
140 | 			{...props}
141 | 		/>
142 | 	);
143 | }
144 | 
145 | export {
146 | 	AlertDialog,
147 | 	AlertDialogPortal,
148 | 	AlertDialogOverlay,
149 | 	AlertDialogTrigger,
150 | 	AlertDialogContent,
151 | 	AlertDialogHeader,
152 | 	AlertDialogFooter,
153 | 	AlertDialogTitle,
154 | 	AlertDialogDescription,
155 | 	AlertDialogAction,
156 | 	AlertDialogCancel,
157 | };
158 | 
```

--------------------------------------------------------------------------------
/docs/tailwind.config.js:
--------------------------------------------------------------------------------

```javascript
  1 | import defaultTheme from "tailwindcss/defaultTheme";
  2 | import flattenColorPalette from "tailwindcss/lib/util/flattenColorPalette";
  3 | import svgToDataUri from "mini-svg-data-uri";
  4 | 
  5 | /** @type {import('tailwindcss').Config} */
  6 | export default {
  7 | 	darkMode: ["class"],
  8 | 	plugins: [
  9 | 		addVariablesForColors,
 10 | 		function ({ matchUtilities, theme }) {
 11 | 			matchUtilities(
 12 | 				{
 13 | 					"bg-grid": (value) => ({
 14 | 						backgroundImage: `url("${svgToDataUri(
 15 | 							`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="none" stroke="${value}"><path d="M0 .5H31.5V32"/></svg>`,
 16 | 						)}")`,
 17 | 					}),
 18 | 					"bg-grid-small": (value) => ({
 19 | 						backgroundImage: `url("${svgToDataUri(
 20 | 							`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="8" height="8" fill="none" stroke="${value}"><path d="M0 .5H31.5V32"/></svg>`,
 21 | 						)}")`,
 22 | 					}),
 23 | 					"bg-dot": (value) => ({
 24 | 						backgroundImage: `url("${svgToDataUri(
 25 | 							`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="16" height="16" fill="none"><circle fill="${value}" id="pattern-circle" cx="10" cy="10" r="1.6257413380501518"></circle></svg>`,
 26 | 						)}")`,
 27 | 					}),
 28 | 				},
 29 | 				{
 30 | 					values: flattenColorPalette(theme("backgroundColor")),
 31 | 					type: "color",
 32 | 				},
 33 | 			);
 34 | 		},
 35 | 	],
 36 | 	theme: {
 37 | 		extend: {
 38 | 			fontFamily: {
 39 | 				sans: ["var(--font-geist-sans)"],
 40 | 				mono: ["var(--font-geist-mono)"],
 41 | 				display: [...defaultTheme.fontFamily.sans],
 42 | 			},
 43 | 			borderRadius: {
 44 | 				lg: "var(--radius)",
 45 | 				md: "calc(var(--radius) - 2px)",
 46 | 				sm: "calc(var(--radius) - 4px)",
 47 | 			},
 48 | 			keyframes: {
 49 | 				marquee: {
 50 | 					from: { transform: "translateX(0)" },
 51 | 					to: { transform: "translateX(calc(-100% - var(--gap)))" },
 52 | 				},
 53 | 				"marquee-vertical": {
 54 | 					from: { transform: "translateY(0)" },
 55 | 					to: { transform: "translateY(calc(-100% - var(--gap)))" },
 56 | 				},
 57 | 				"hrtl-scroll": {
 58 | 					from: { transform: "translateX(0)" },
 59 | 					to: { transform: "translateX(calc(-95%))" },
 60 | 				},
 61 | 				"hrtl-scroll-reverse": {
 62 | 					from: { transform: "translateX(calc(-95%))" },
 63 | 					to: { transform: "translateX(0)" },
 64 | 				},
 65 | 				ripple: {
 66 | 					"0% , 100%": {
 67 | 						transform: "translate(-50% , -50%) scale(1)",
 68 | 					},
 69 | 					"50%": {
 70 | 						transform: "translate(-50% , -50%) scale(0.9)",
 71 | 					},
 72 | 				},
 73 | 				"accordion-down": {
 74 | 					from: {
 75 | 						height: "0",
 76 | 					},
 77 | 					to: {
 78 | 						height: "var(--radix-accordion-content-height)",
 79 | 					},
 80 | 				},
 81 | 				"accordion-up": {
 82 | 					from: {
 83 | 						height: "var(--radix-accordion-content-height)",
 84 | 					},
 85 | 					to: {
 86 | 						height: "0",
 87 | 					},
 88 | 				},
 89 | 				scroll: {
 90 | 					to: {
 91 | 						transform: "translate(calc(-50% - 0.5rem))",
 92 | 					},
 93 | 				},
 94 | 				spotlight: {
 95 | 					"0%": {
 96 | 						opacity: 0,
 97 | 						transform: "translate(-72%, -62%) scale(0.5)",
 98 | 					},
 99 | 					"100%": {
100 | 						opacity: 1,
101 | 						transform: "translate(-50%,-40%) scale(1)",
102 | 					},
103 | 				},
104 | 			},
105 | 			animation: {
106 | 				"accordion-down": "accordion-down 0.2s ease-out",
107 | 				"accordion-up": "accordion-up 0.2s ease-out",
108 | 				ripple: "ripple var(--duration,2s) ease calc(var(--i, 0)*.2s) infinite",
109 | 				scroll:
110 | 					"scroll var(--animation-duration, 40s) var(--animation-direction, forwards) linear infinite",
111 | 				"hrtl-scroll": "hrtl-scroll var(--anime-duration,10s) linear infinite",
112 | 				"hrtl-scroll-reverse":
113 | 					"hrtl-scroll-reverse var(--anime-duration,10s) linear infinite",
114 | 				spotlight: "spotlight 2s ease .30s 1 forwards",
115 | 			},
116 | 		},
117 | 	},
118 | };
119 | 
120 | function addVariablesForColors({ addBase, theme }) {
121 | 	let allColors = flattenColorPalette(theme("colors"));
122 | 	let newVars = Object.fromEntries(
123 | 		Object.entries(allColors).map(([key, val]) => [`--${key}`, val]),
124 | 	);
125 | 
126 | 	addBase({
127 | 		":root": newVars,
128 | 	});
129 | }
130 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/hooks/use-toast.ts:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | // Inspired by react-hot-toast library
  4 | import * as React from "react";
  5 | 
  6 | import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
  7 | 
  8 | const TOAST_LIMIT = 1;
  9 | const TOAST_REMOVE_DELAY = 1000000;
 10 | 
 11 | type ToasterToast = ToastProps & {
 12 | 	id: string;
 13 | 	title?: React.ReactNode;
 14 | 	description?: React.ReactNode;
 15 | 	action?: ToastActionElement;
 16 | };
 17 | 
 18 | const actionTypes = {
 19 | 	ADD_TOAST: "ADD_TOAST",
 20 | 	UPDATE_TOAST: "UPDATE_TOAST",
 21 | 	DISMISS_TOAST: "DISMISS_TOAST",
 22 | 	REMOVE_TOAST: "REMOVE_TOAST",
 23 | } as const;
 24 | 
 25 | let count = 0;
 26 | 
 27 | function genId() {
 28 | 	count = (count + 1) % Number.MAX_SAFE_INTEGER;
 29 | 	return count.toString();
 30 | }
 31 | 
 32 | type ActionType = typeof actionTypes;
 33 | 
 34 | type Action =
 35 | 	| {
 36 | 			type: ActionType["ADD_TOAST"];
 37 | 			toast: ToasterToast;
 38 | 	  }
 39 | 	| {
 40 | 			type: ActionType["UPDATE_TOAST"];
 41 | 			toast: Partial<ToasterToast>;
 42 | 	  }
 43 | 	| {
 44 | 			type: ActionType["DISMISS_TOAST"];
 45 | 			toastId?: ToasterToast["id"];
 46 | 	  }
 47 | 	| {
 48 | 			type: ActionType["REMOVE_TOAST"];
 49 | 			toastId?: ToasterToast["id"];
 50 | 	  };
 51 | 
 52 | interface State {
 53 | 	toasts: ToasterToast[];
 54 | }
 55 | 
 56 | const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
 57 | 
 58 | const addToRemoveQueue = (toastId: string) => {
 59 | 	if (toastTimeouts.has(toastId)) {
 60 | 		return;
 61 | 	}
 62 | 
 63 | 	const timeout = setTimeout(() => {
 64 | 		toastTimeouts.delete(toastId);
 65 | 		dispatch({
 66 | 			type: "REMOVE_TOAST",
 67 | 			toastId: toastId,
 68 | 		});
 69 | 	}, TOAST_REMOVE_DELAY);
 70 | 
 71 | 	toastTimeouts.set(toastId, timeout);
 72 | };
 73 | 
 74 | export const reducer = (state: State, action: Action): State => {
 75 | 	switch (action.type) {
 76 | 		case "ADD_TOAST":
 77 | 			return {
 78 | 				...state,
 79 | 				toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
 80 | 			};
 81 | 
 82 | 		case "UPDATE_TOAST":
 83 | 			return {
 84 | 				...state,
 85 | 				toasts: state.toasts.map((t) =>
 86 | 					t.id === action.toast.id ? { ...t, ...action.toast } : t,
 87 | 				),
 88 | 			};
 89 | 
 90 | 		case "DISMISS_TOAST": {
 91 | 			const { toastId } = action;
 92 | 
 93 | 			// ! Side effects ! - This could be extracted into a dismissToast() action,
 94 | 			// but I'll keep it here for simplicity
 95 | 			if (toastId) {
 96 | 				addToRemoveQueue(toastId);
 97 | 			} else {
 98 | 				state.toasts.forEach((toast) => {
 99 | 					addToRemoveQueue(toast.id);
100 | 				});
101 | 			}
102 | 
103 | 			return {
104 | 				...state,
105 | 				toasts: state.toasts.map((t) =>
106 | 					t.id === toastId || toastId === undefined
107 | 						? {
108 | 								...t,
109 | 								open: false,
110 | 							}
111 | 						: t,
112 | 				),
113 | 			};
114 | 		}
115 | 		case "REMOVE_TOAST":
116 | 			if (action.toastId === undefined) {
117 | 				return {
118 | 					...state,
119 | 					toasts: [],
120 | 				};
121 | 			}
122 | 			return {
123 | 				...state,
124 | 				toasts: state.toasts.filter((t) => t.id !== action.toastId),
125 | 			};
126 | 	}
127 | };
128 | 
129 | const listeners: Array<(state: State) => void> = [];
130 | 
131 | let memoryState: State = { toasts: [] };
132 | 
133 | function dispatch(action: Action) {
134 | 	memoryState = reducer(memoryState, action);
135 | 
136 | 	listeners.forEach((listener) => {
137 | 		listener(memoryState);
138 | 	});
139 | }
140 | 
141 | type Toast = Omit<ToasterToast, "id">;
142 | 
143 | function toast({ ...props }: Toast) {
144 | 	const id = genId();
145 | 
146 | 	const update = (props: ToasterToast) =>
147 | 		dispatch({
148 | 			type: "UPDATE_TOAST",
149 | 			toast: { ...props, id },
150 | 		});
151 | 	const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
152 | 
153 | 	dispatch({
154 | 		type: "ADD_TOAST",
155 | 		toast: {
156 | 			...props,
157 | 			id,
158 | 			open: true,
159 | 			// @ts-expect-error
160 | 			onOpenChange: (open) => {
161 | 				if (!open) dismiss();
162 | 			},
163 | 		},
164 | 	});
165 | 
166 | 	return {
167 | 		id: id,
168 | 		dismiss,
169 | 		update,
170 | 	};
171 | }
172 | 
173 | function useToast() {
174 | 	const [state, setState] = React.useState<State>(memoryState);
175 | 
176 | 	React.useEffect(() => {
177 | 		listeners.push(setState);
178 | 		return () => {
179 | 			const index = listeners.indexOf(setState);
180 | 			if (index > -1) {
181 | 				listeners.splice(index, 1);
182 | 			}
183 | 		};
184 | 	}, [state]);
185 | 
186 | 	return {
187 | 		...state,
188 | 		toast,
189 | 		dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
190 | 	};
191 | }
192 | 
193 | export { useToast, toast };
194 | 
```
Page 12/69FirstPrevNextLast