#
tokens: 47413/50000 11/1099 files (page 20/51)
lines: off (toggle) GitHub
raw markdown copy
This is page 20 of 51. Use http://codebase.md/better-auth/better-auth?lines=false&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

--------------------------------------------------------------------------------
/docs/content/docs/plugins/siwe.mdx:
--------------------------------------------------------------------------------

```markdown
---
title: Sign In With Ethereum (SIWE)
description: Sign in with Ethereum plugin for Better Auth
---

The Sign in with Ethereum (SIWE) plugin allows users to authenticate using their Ethereum wallets following the [ERC-4361 standard](https://eips.ethereum.org/EIPS/eip-4361). This plugin provides flexibility by allowing you to implement your own message verification and nonce generation logic.

## Installation

<Steps>
    <Step>
        ### Add the Server Plugin

        Add the SIWE plugin to your auth configuration:

        ```ts title="auth.ts"
        import { betterAuth } from "better-auth";
        import { siwe } from "better-auth/plugins";

        export const auth = betterAuth({
            plugins: [
                siwe({
                    domain: "example.com",
                    emailDomainName: "example.com", // optional
                    anonymous: false, // optional, default is true
                    getNonce: async () => {
                        // Implement your nonce generation logic here
                        return "your-secure-random-nonce";
                    },
                    verifyMessage: async (args) => {
                        // Implement your SIWE message verification logic here
                        // This should verify the signature against the message
                        return true; // return true if signature is valid
                    },
                    ensLookup: async (args) => {
                        // Optional: Implement ENS lookup for user names and avatars
                        return {
                            name: "user.eth",
                            avatar: "https://example.com/avatar.png"
                        };
                    },
                }),
            ],
        });
        ```
    </Step>

    <Step>
        ### Migrate the database

        Run the migration or generate the schema to add the necessary fields and tables to the database.

        <Tabs items={["migrate", "generate"]}>
            <Tab value="migrate">
            ```bash
            npx @better-auth/cli migrate
            ```
            </Tab>
            <Tab value="generate">
            ```bash
            npx @better-auth/cli generate
            ```
            </Tab>
        </Tabs>
        See the [Schema](#schema) section to add the fields manually.
    </Step>

    <Step>
        ### Add the Client Plugin

        ```ts title="auth-client.ts"
        import { createAuthClient } from "better-auth/client";
        import { siweClient } from "better-auth/client/plugins";

        export const authClient = createAuthClient({
            plugins: [siweClient()],
        });
        ```
    </Step>

</Steps>

## Usage

### Generate a Nonce

Before signing a SIWE message, you need to generate a nonce for the wallet address:

```ts title="generate-nonce.ts"
const { data, error } = await authClient.siwe.nonce({
  walletAddress: "0x1234567890abcdef1234567890abcdef12345678",
  chainId: 1, // optional for Ethereum mainnet, required for other chains. Defaults to 1
});

if (data) {
  console.log("Nonce:", data.nonce);
}
```

### Sign In with Ethereum

After generating a nonce and creating a SIWE message, verify the signature to authenticate:

```ts title="sign-in-siwe.ts"
const { data, error } = await authClient.siwe.verify({
  message: "Your SIWE message string",
  signature: "0x...", // The signature from the user's wallet
  walletAddress: "0x1234567890abcdef1234567890abcdef12345678",
  chainId: 1, // optional for Ethereum mainnet, required for other chains. Must match Chain ID in SIWE message
  email: "[email protected]", // optional, required if anonymous is false
});

if (data) {
  console.log("Authentication successful:", data.user);
}
```

### Chain-Specific Examples

Here are examples for different blockchain networks:

```ts title="ethereum-mainnet.ts"
// Ethereum Mainnet (chainId can be omitted, defaults to 1)
const { data, error } = await authClient.siwe.verify({
  message,
  signature,
  walletAddress,
  // chainId: 1 (default)
});
```

```ts title="polygon.ts"
// Polygon (chainId REQUIRED)
const { data, error } = await authClient.siwe.verify({
  message,
  signature,
  walletAddress,
  chainId: 137, // Required for Polygon
});
```

```ts title="arbitrum.ts"
// Arbitrum (chainId REQUIRED)
const { data, error } = await authClient.siwe.verify({
  message,
  signature,
  walletAddress,
  chainId: 42161, // Required for Arbitrum
});
```

```ts title="base.ts"
// Base (chainId REQUIRED)
const { data, error } = await authClient.siwe.verify({
  message,
  signature,
  walletAddress,
  chainId: 8453, // Required for Base
});
```

<Callout type="warning">
  The `chainId` must match the Chain ID specified in your SIWE message. Verification will fail with a 401 error if there's a mismatch between the message's Chain ID and the `chainId` parameter.
</Callout>

## Configuration Options

### Server Options

The SIWE plugin accepts the following configuration options:

- **domain**: The domain name of your application (required for SIWE message generation)
- **emailDomainName**: The email domain name for creating user accounts when not using anonymous mode. Defaults to the domain from your base URL
- **anonymous**: Whether to allow anonymous sign-ins without requiring an email. Default is `true`
- **getNonce**: Function to generate a unique nonce for each sign-in attempt. You must implement this function to return a cryptographically secure random string. Must return a `Promise<string>`
- **verifyMessage**: Function to verify the signed SIWE message. Receives message details and should return `Promise<boolean>`
- **ensLookup**: Optional function to lookup ENS names and avatars for Ethereum addresses

### Client Options

The SIWE client plugin doesn't require any configuration options, but you can pass them if needed for future extensibility:

```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client";
import { siweClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [
    siweClient({
      // Optional client configuration can go here
    }),
  ],
});
```

## Schema

The SIWE plugin adds a `walletAddress` table to store user wallet associations:

| Field     | Type    | Description                               |
| --------- | ------- | ----------------------------------------- |
| id        | string  | Primary key                               |
| userId    | string  | Reference to user.id                      |
| address   | string  | Ethereum wallet address                   |
| chainId   | number  | Chain ID (e.g., 1 for Ethereum mainnet)   |
| isPrimary | boolean | Whether this is the user's primary wallet |
| createdAt | date    | Creation timestamp                        |

## Example Implementation

Here's a complete example showing how to implement SIWE authentication:

```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { siwe } from "better-auth/plugins";
import { generateRandomString } from "better-auth/crypto";
import { verifyMessage, createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";

export const auth = betterAuth({
  database: {
    // your database configuration
  },
  plugins: [
    siwe({
      domain: "myapp.com",
      emailDomainName: "myapp.com",
      anonymous: false,
      getNonce: async () => {
        // Generate a cryptographically secure random nonce
        return generateRandomString(32);
      },
      verifyMessage: async ({ message, signature, address }) => {
        try {
          // Verify the signature using viem (recommended)
          const isValid = await verifyMessage({
            address: address as `0x${string}`,
            message,
            signature: signature as `0x${string}`,
          });
          return isValid;
        } catch (error) {
          console.error("SIWE verification failed:", error);
          return false;
        }
      },
      ensLookup: async ({ walletAddress }) => {
        try {
          // Optional: lookup ENS name and avatar using viem
          // You can use viem's ENS utilities here
          const client = createPublicClient({
            chain: mainnet,
            transport: http(),
          });

          const ensName = await client.getEnsName({
            address: walletAddress as `0x${string}`,
          });

          const ensAvatar = ensName
            ? await client.getEnsAvatar({
                name: ensName,
              })
            : null;

          return {
            name: ensName || walletAddress,
            avatar: ensAvatar || "",
          };
        } catch {
          return {
            name: walletAddress,
            avatar: "",
          };
        }
      },
    }),
  ],
});
```

```

--------------------------------------------------------------------------------
/packages/better-auth/src/adapters/kysely-adapter/test/adapter.kysely.mssql.test.ts:
--------------------------------------------------------------------------------

```typescript
import { Kysely, MssqlDialect } from "kysely";
import { testAdapter } from "../../test-adapter";
import { kyselyAdapter } from "../kysely-adapter";
import {
	authFlowTestSuite,
	normalTestSuite,
	numberIdTestSuite,
	performanceTestSuite,
	transactionsTestSuite,
} from "../../tests";
import { getMigrations } from "../../../db";
import * as Tedious from "tedious";
import * as Tarn from "tarn";
import type { BetterAuthOptions } from "@better-auth/core";

// We are not allowed to handle the mssql connection
// we must let kysely handle it. This is because if kysely is already
// handling it, and we were to connect it ourselves, it will create bugs.
const dialect = new MssqlDialect({
	tarn: {
		...Tarn,
		options: {
			min: 0,
			max: 10,
		},
	},
	tedious: {
		...Tedious,
		connectionFactory: () =>
			new Tedious.Connection({
				authentication: {
					options: {
						password: "Password123!",
						userName: "sa",
					},
					type: "default",
				},
				options: {
					database: "master", // Start with master database, will create better_auth if needed
					port: 1433,
					trustServerCertificate: true,
					encrypt: false,
				},
				server: "localhost",
			}),
		TYPES: {
			...Tedious.TYPES,
			DateTime: Tedious.TYPES.DateTime2,
		},
	},
});

const kyselyDB = new Kysely({
	dialect: dialect,
});

// Create better_auth database if it doesn't exist
const ensureDatabaseExists = async () => {
	try {
		console.log("Ensuring better_auth database exists...");
		await kyselyDB.getExecutor().executeQuery({
			sql: `
				IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = 'better_auth')
				BEGIN
					CREATE DATABASE better_auth;
					PRINT 'Database better_auth created successfully';
				END
				ELSE
				BEGIN
					PRINT 'Database better_auth already exists';
				END
			`,
			parameters: [],
			query: { kind: "SelectQueryNode" },
			queryId: { queryId: "ensure-db" },
		});
		console.log("Database check/creation completed");
	} catch (error) {
		console.error("Failed to ensure database exists:", error);
		throw error;
	}
};

// Warm up connection for CI environments
const warmupConnection = async () => {
	const isCI =
		process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
	if (isCI) {
		console.log("Warming up MSSQL connection for CI environment...");
		console.log(
			`Environment: CI=${process.env.CI}, GITHUB_ACTIONS=${process.env.GITHUB_ACTIONS}`,
		);

		try {
			await ensureDatabaseExists();

			// Try a simple query to establish the connection
			await kyselyDB.getExecutor().executeQuery({
				sql: "SELECT 1 as warmup, @@VERSION as version",
				parameters: [],
				query: { kind: "SelectQueryNode" },
				queryId: { queryId: "warmup" },
			});
			console.log("Connection warmup successful");
		} catch (error) {
			console.warn(
				"Connection warmup failed, will retry during validation:",
				error,
			);
			// Log additional debugging info for CI
			if (isCI) {
				console.log("CI Debug Info:");
				console.log("- MSSQL server may not be ready yet");
				console.log("- Network connectivity issues possible");
				console.log("- Database may not exist yet");
			}
		}
	} else {
		// For local development, also ensure database exists
		await ensureDatabaseExists();
	}
};

// Add connection validation helper with CI-specific handling
const validateConnection = async (retries: number = 10): Promise<boolean> => {
	const isCI =
		process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
	const maxRetries = isCI ? 15 : retries; // More retries in CI
	const baseDelay = isCI ? 2000 : 1000; // Longer delays in CI

	console.log(
		`Validating connection (CI: ${isCI}, max retries: ${maxRetries})`,
	);

	for (let i = 0; i < maxRetries; i++) {
		try {
			await query("SELECT 1 as test", isCI ? 10000 : 5000);
			console.log("Connection validated successfully");
			return true;
		} catch (error) {
			console.warn(
				`Connection validation attempt ${i + 1}/${maxRetries} failed:`,
				error,
			);
			if (i === maxRetries - 1) {
				console.error("All connection validation attempts failed");
				return false;
			}
			// Exponential backoff with longer delays in CI
			const delay = baseDelay * Math.pow(1.5, i);
			console.log(`Waiting ${delay}ms before retry...`);
			await new Promise((resolve) => setTimeout(resolve, delay));
		}
	}
	return false;
};

const query = async (sql: string, timeoutMs: number = 30000) => {
	const isCI =
		process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
	const actualTimeout = isCI ? Math.max(timeoutMs, 60000) : timeoutMs; // Minimum 60s timeout in CI

	try {
		console.log(
			`Executing SQL: ${sql.substring(0, 100)}... (timeout: ${actualTimeout}ms, CI: ${isCI})`,
		);

		// Ensure we're using the better_auth database for queries
		const sqlWithContext = sql.includes("USE ")
			? sql
			: `USE better_auth; ${sql}`;

		const result = (await Promise.race([
			kyselyDB.getExecutor().executeQuery({
				sql: sqlWithContext,
				parameters: [],
				query: { kind: "SelectQueryNode" },
				queryId: { queryId: "" },
			}),
			new Promise((_, reject) =>
				setTimeout(
					() => reject(new Error(`Query timeout after ${actualTimeout}ms`)),
					actualTimeout,
				),
			),
		])) as any;
		console.log(`Query completed successfully`);
		return { rows: result.rows, rowCount: result.rows.length };
	} catch (error) {
		console.error(`Query failed: ${error}`);
		throw error;
	}
};

const showDB = async () => {
	const DB = {
		users: await query("SELECT * FROM [user]"),
		sessions: await query("SELECT * FROM [session]"),
		accounts: await query("SELECT * FROM [account]"),
		verifications: await query("SELECT * FROM [verification]"),
	};
	console.log(`DB`, DB);
};

const resetDB = async (retryCount: number = 0) => {
	const isCI =
		process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
	const maxRetries = isCI ? 3 : 1; // Allow retries in CI

	try {
		console.log(
			`Starting database reset... (attempt ${retryCount + 1}/${maxRetries + 1})`,
		);

		// Warm up connection first (especially important for CI)
		await warmupConnection();

		const isConnected = await validateConnection();
		if (!isConnected) {
			throw new Error("Database connection validation failed");
		}

		// First, try to disable foreign key checks and drop constraints
		await query(
			`
			-- Disable all foreign key constraints
			EXEC sp_MSforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT all";
		`,
			15000,
		);

		// Drop foreign key constraints
		await query(
			`
			DECLARE @sql NVARCHAR(MAX) = '';
			SELECT @sql = @sql + 'ALTER TABLE [' + TABLE_SCHEMA + '].[' + TABLE_NAME + '] DROP CONSTRAINT [' + CONSTRAINT_NAME + '];' + CHAR(13)
			FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
			WHERE CONSTRAINT_TYPE = 'FOREIGN KEY'
			AND TABLE_CATALOG = DB_NAME();
			IF LEN(@sql) > 0
				EXEC sp_executesql @sql;
		`,
			15000,
		);

		// Then drop all tables
		await query(
			`
			DECLARE @sql NVARCHAR(MAX) = '';
			SELECT @sql = @sql + 'DROP TABLE [' + TABLE_NAME + '];' + CHAR(13)
			FROM INFORMATION_SCHEMA.TABLES 
			WHERE TABLE_TYPE = 'BASE TABLE' 
			AND TABLE_CATALOG = DB_NAME()
			AND TABLE_SCHEMA = 'dbo';
			IF LEN(@sql) > 0
				EXEC sp_executesql @sql;
		`,
			15000,
		);

		console.log("Database reset completed successfully");
	} catch (error) {
		console.error("Database reset failed:", error);

		// Retry logic for CI environments
		if (retryCount < maxRetries) {
			const delay = 5000 * (retryCount + 1); // Increasing delay
			console.log(
				`Retrying in ${delay}ms... (attempt ${retryCount + 2}/${maxRetries + 1})`,
			);
			await new Promise((resolve) => setTimeout(resolve, delay));
			return resetDB(retryCount + 1);
		}

		// Final fallback - try to recreate the database
		try {
			console.log("Attempting database recreation...");
			// This would require a separate connection to master database
			// For now, just throw the error with better context
			throw new Error(`Database reset failed completely: ${error}`);
		} catch (finalError) {
			console.error("Final fallback also failed:", finalError);
			throw new Error(
				`Database reset failed: ${error}. All fallback attempts failed: ${finalError}`,
			);
		}
	}
};

const { execute } = await testAdapter({
	adapter: () => {
		return kyselyAdapter(kyselyDB, {
			type: "mssql",
			debugLogs: { isRunningAdapterTests: true },
		});
	},
	async runMigrations(betterAuthOptions) {
		await resetDB();
		const opts = Object.assign(betterAuthOptions, {
			database: { db: kyselyDB, type: "mssql" },
		} satisfies BetterAuthOptions);
		const { runMigrations } = await getMigrations(opts);
		await runMigrations();
	},
	prefixTests: "mssql",
	tests: [
		normalTestSuite(),
		transactionsTestSuite({ disableTests: { ALL: true } }),
		authFlowTestSuite({ showDB }),
		numberIdTestSuite(),
		performanceTestSuite({ dialect: "mssql" }),
	],
	async onFinish() {
		kyselyDB.destroy();
	},
});
execute();

```

--------------------------------------------------------------------------------
/packages/better-auth/src/api/check-endpoint-conflicts.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, vi, beforeEach } from "vitest";
import { checkEndpointConflicts } from "./index";
import type { BetterAuthOptions, BetterAuthPlugin } from "@better-auth/core";
import { createEndpoint } from "better-call";
import type { InternalLogger, LogLevel } from "@better-auth/core/env";

export let mockLoggerLevel: LogLevel = "debug";
export const mockLogger = {
	error: vi.fn(),
	warn: vi.fn(),
	info: vi.fn(),
	debug: vi.fn(),
	success: vi.fn(),
	get level(): LogLevel {
		return mockLoggerLevel;
	},
} satisfies InternalLogger;

describe("checkEndpointConflicts", () => {
	const endpoint = createEndpoint.create({});

	beforeEach(() => {
		mockLoggerLevel = "debug";
		mockLogger.error.mockReset();
		mockLogger.warn.mockReset();
		mockLogger.info.mockReset();
		mockLogger.debug.mockReset();
		mockLogger.success.mockReset();
	});

	it("should not log errors when there are no endpoint conflicts", () => {
		const plugin1: BetterAuthPlugin = {
			id: "plugin1",
			endpoints: {
				endpoint1: endpoint(
					"/api/endpoint1",
					{
						method: "GET",
					},
					vi.fn(),
				),
				endpoint2: endpoint(
					"/api/endpoint2",
					{
						method: "POST",
					},
					vi.fn(),
				),
			},
		};

		const plugin2: BetterAuthPlugin = {
			id: "plugin2",
			endpoints: {
				endpoint3: endpoint(
					"/api/endpoint3",
					{
						method: "GET",
					},
					vi.fn(),
				),
				endpoint4: endpoint(
					"/api/endpoint4",
					{
						method: "POST",
					},
					vi.fn(),
				),
			},
		};

		const options: BetterAuthOptions = {
			plugins: [plugin1, plugin2],
		};

		checkEndpointConflicts(options, mockLogger);

		expect(mockLogger.error).not.toHaveBeenCalled();
	});

	it("should NOT log an error when two plugins use the same endpoint path with different methods", () => {
		const plugin1: BetterAuthPlugin = {
			id: "plugin1",
			endpoints: {
				endpoint1: endpoint(
					"/api/shared",
					{
						method: "GET",
					},
					vi.fn(),
				),
			},
		};

		const plugin2: BetterAuthPlugin = {
			id: "plugin2",
			endpoints: {
				endpoint2: endpoint(
					"/api/shared",
					{
						method: "POST",
					},
					vi.fn(),
				),
			},
		};

		const options: BetterAuthOptions = {
			plugins: [plugin1, plugin2],
		};

		checkEndpointConflicts(options, mockLogger);

		// Should NOT report an error since methods are different
		expect(mockLogger.error).not.toHaveBeenCalled();
	});

	it("should log an error when two plugins use the same endpoint path with the same method", () => {
		const plugin1: BetterAuthPlugin = {
			id: "plugin1",
			endpoints: {
				endpoint1: endpoint(
					"/api/shared",
					{
						method: "GET",
					},
					vi.fn(),
				),
			},
		};

		const plugin2: BetterAuthPlugin = {
			id: "plugin2",
			endpoints: {
				endpoint2: endpoint(
					"/api/shared",
					{
						method: "GET",
					},
					vi.fn(),
				),
			},
		};

		const options: BetterAuthOptions = {
			plugins: [plugin1, plugin2],
		};

		checkEndpointConflicts(options, mockLogger);

		expect(mockLogger.error).toHaveBeenCalledTimes(1);
		expect(mockLogger.error).toHaveBeenCalledWith(
			expect.stringContaining("Endpoint path conflicts detected"),
		);
		expect(mockLogger.error).toHaveBeenCalledWith(
			expect.stringContaining(
				'"/api/shared" [GET] used by plugins: plugin1, plugin2',
			),
		);
	});

	it("should NOT detect conflicts when plugins use different methods on same paths", () => {
		const plugin1: BetterAuthPlugin = {
			id: "plugin1",
			endpoints: {
				endpoint1: endpoint(
					"/api/resource1",
					{
						method: "GET",
					},
					vi.fn(),
				),
				endpoint2: endpoint(
					"/api/resource2",
					{
						method: "POST",
					},
					vi.fn(),
				),
			},
		};

		const plugin2: BetterAuthPlugin = {
			id: "plugin2",
			endpoints: {
				endpoint3: endpoint(
					"/api/resource1",
					{
						method: "POST",
					},
					vi.fn(),
				),
			},
		};

		const plugin3: BetterAuthPlugin = {
			id: "plugin3",
			endpoints: {
				endpoint4: endpoint(
					"/api/resource2",
					{
						method: "GET",
					},
					vi.fn(),
				),
			},
		};

		const options: BetterAuthOptions = {
			plugins: [plugin1, plugin2, plugin3],
		};

		checkEndpointConflicts(options, mockLogger);

		// Should not report errors since all methods are different
		expect(mockLogger.error).not.toHaveBeenCalled();
	});

	it("should detect conflicts when plugins use the same method on the same path", () => {
		const plugin1: BetterAuthPlugin = {
			id: "plugin1",
			endpoints: {
				endpoint1: endpoint(
					"/api/conflict",
					{
						method: "GET",
					},
					vi.fn(),
				),
			},
		};

		const plugin2: BetterAuthPlugin = {
			id: "plugin2",
			endpoints: {
				endpoint2: endpoint(
					"/api/conflict",
					{
						method: "GET",
					},
					vi.fn(),
				),
			},
		};

		const options: BetterAuthOptions = {
			plugins: [plugin1, plugin2],
		};

		checkEndpointConflicts(options, mockLogger);

		expect(mockLogger.error).toHaveBeenCalledTimes(1);
		const errorCall = mockLogger.error.mock.calls[0]![0];
		expect(errorCall).toContain(
			'"/api/conflict" [GET] used by plugins: plugin1, plugin2',
		);
	});

	it("should allow multiple endpoints from the same plugin using the same path with different methods", () => {
		const plugin1: BetterAuthPlugin = {
			id: "plugin1",
			endpoints: {
				endpoint1: endpoint(
					"/api/same",
					{
						method: "GET",
					},
					vi.fn(),
				),
				endpoint2: endpoint(
					"/api/same",
					{
						method: "POST",
					},
					vi.fn(),
				),
			},
		};

		const options: BetterAuthOptions = {
			plugins: [plugin1],
		};

		checkEndpointConflicts(options, mockLogger);

		// Should not report error since methods are different
		expect(mockLogger.error).not.toHaveBeenCalled();
	});

	it("should detect conflicts when same plugin has duplicate methods on same path", () => {
		const plugin1: BetterAuthPlugin = {
			id: "plugin1",
			endpoints: {
				endpoint1: endpoint(
					"/api/same",
					{
						method: "GET",
					},
					vi.fn(),
				),
				endpoint2: endpoint(
					"/api/same",
					{
						method: "GET",
					},
					vi.fn(),
				),
			},
		};

		const options: BetterAuthOptions = {
			plugins: [plugin1],
		};

		checkEndpointConflicts(options, mockLogger);

		expect(mockLogger.error).toHaveBeenCalledTimes(1);
		expect(mockLogger.error).toHaveBeenCalledWith(
			expect.stringContaining('"/api/same" [GET] used by plugins: plugin1'),
		);
	});

	it("should allow three plugins on the same path with different methods", () => {
		const plugin1: BetterAuthPlugin = {
			id: "plugin1",
			endpoints: {
				endpoint1: endpoint(
					"/api/resource",
					{
						method: "GET",
					},
					vi.fn(),
				),
			},
		};

		const plugin2: BetterAuthPlugin = {
			id: "plugin2",
			endpoints: {
				endpoint2: endpoint(
					"/api/resource",
					{
						method: "POST",
					},
					vi.fn(),
				),
			},
		};

		const plugin3: BetterAuthPlugin = {
			id: "plugin3",
			endpoints: {
				endpoint3: endpoint(
					"/api/resource",
					{
						method: "DELETE",
					},
					vi.fn(),
				),
			},
		};

		const options: BetterAuthOptions = {
			plugins: [plugin1, plugin2, plugin3],
		};

		checkEndpointConflicts(options, mockLogger);

		// Should not report error since all methods are different
		expect(mockLogger.error).not.toHaveBeenCalled();
	});

	it("should detect conflicts when endpoints don't specify a method (wildcard)", () => {
		const plugin1: BetterAuthPlugin = {
			id: "plugin1",
			endpoints: {
				endpoint1: endpoint(
					"/api/wildcard",
					{
						method: "*",
					},
					vi.fn(),
				),
			},
		};

		const plugin2: BetterAuthPlugin = {
			id: "plugin2",
			endpoints: {
				endpoint2: endpoint(
					"/api/wildcard",
					{
						method: "GET",
					},
					vi.fn(),
				),
			},
		};

		const options: BetterAuthOptions = {
			plugins: [plugin1, plugin2],
		};

		checkEndpointConflicts(options, mockLogger);

		expect(mockLogger.error).toHaveBeenCalledTimes(1);
		expect(mockLogger.error).toHaveBeenCalledWith(
			expect.stringContaining('"/api/wildcard"'),
		);
	});

	it("should handle plugins with no endpoints", () => {
		const plugin1: BetterAuthPlugin = {
			id: "plugin1",
		};

		const plugin2: BetterAuthPlugin = {
			id: "plugin2",
			endpoints: {},
		};

		const options: BetterAuthOptions = {
			plugins: [plugin1, plugin2],
		};

		checkEndpointConflicts(options, mockLogger);

		expect(mockLogger.error).not.toHaveBeenCalled();
	});

	it("should handle options with no plugins", () => {
		const options: BetterAuthOptions = {};

		checkEndpointConflicts(options, mockLogger);

		expect(mockLogger.error).not.toHaveBeenCalled();
	});

	it("should handle options with empty plugins array", () => {
		const options: BetterAuthOptions = {
			plugins: [],
		};

		checkEndpointConflicts(options, mockLogger);

		expect(mockLogger.error).not.toHaveBeenCalled();
	});
});

```

--------------------------------------------------------------------------------
/demo/nextjs/components/sign-in.tsx:
--------------------------------------------------------------------------------

```typescript
"use client";

import { Button } from "@/components/ui/button";
import {
	Card,
	CardContent,
	CardHeader,
	CardTitle,
	CardDescription,
	CardFooter,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import { useState, useTransition } from "react";
import { Loader2 } from "lucide-react";
import { client, signIn } from "@/lib/auth-client";
import Link from "next/link";
import { cn } from "@/lib/utils";
import { useRouter, useSearchParams } from "next/navigation";
import { toast } from "sonner";
import { getCallbackURL } from "@/lib/shared";

export default function SignIn() {
	const [email, setEmail] = useState("");
	const [password, setPassword] = useState("");
	const [loading, startTransition] = useTransition();
	const [rememberMe, setRememberMe] = useState(false);
	const router = useRouter();
	const params = useSearchParams();

	const LastUsedIndicator = () => (
		<span className="ml-auto absolute top-0 right-0 px-2 py-1 text-xs bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300 rounded-md font-medium">
			Last Used
		</span>
	);

	return (
		<Card className="max-w-md rounded-none">
			<CardHeader>
				<CardTitle className="text-lg md:text-xl">Sign In</CardTitle>
				<CardDescription className="text-xs md:text-sm">
					Enter your email below to login to your account
				</CardDescription>
			</CardHeader>
			<CardContent>
				<div className="grid gap-4">
					<div className="grid gap-2">
						<Label htmlFor="email">Email</Label>
						<Input
							id="email"
							type="email"
							placeholder="[email protected]"
							required
							onChange={(e) => {
								setEmail(e.target.value);
							}}
							value={email}
						/>
					</div>

					<div className="grid gap-2">
						<div className="flex items-center">
							<Label htmlFor="password">Password</Label>
							<Link
								href="/forget-password"
								className="ml-auto inline-block text-sm underline"
							>
								Forgot your password?
							</Link>
						</div>

						<Input
							id="password"
							type="password"
							placeholder="password"
							autoComplete="password"
							value={password}
							onChange={(e) => setPassword(e.target.value)}
						/>
					</div>

					<div className="flex items-center gap-2">
						<Checkbox
							id="remember"
							onClick={() => {
								setRememberMe(!rememberMe);
							}}
						/>
						<Label htmlFor="remember">Remember me</Label>
					</div>

					<Button
						type="submit"
						className="w-full flex items-center justify-center"
						disabled={loading}
						onClick={async () => {
							startTransition(async () => {
								await signIn.email(
									{ email, password, rememberMe },
									{
										onSuccess(context) {
											toast.success("Successfully signed in");
											router.push(getCallbackURL(params));
										},
										onError(context) {
											toast.error(context.error.message);
										},
									},
								);
							});
						}}
					>
						<div className="flex items-center justify-center w-full relative">
							{loading ? (
								<Loader2 size={16} className="animate-spin" />
							) : (
								"Login"
							)}
							{client.isLastUsedLoginMethod("email") && <LastUsedIndicator />}
						</div>
					</Button>

					<div
						className={cn(
							"w-full gap-2 flex items-center",
							"justify-between flex-col",
						)}
					>
						<Button
							variant="outline"
							className={cn("w-full gap-2 flex relative")}
							onClick={async () => {
								await signIn.social({
									provider: "google",
									callbackURL: "/dashboard",
								});
							}}
						>
							<svg
								xmlns="http://www.w3.org/2000/svg"
								width="0.98em"
								height="1em"
								viewBox="0 0 256 262"
							>
								<path
									fill="#4285F4"
									d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
								></path>
								<path
									fill="#34A853"
									d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
								></path>
								<path
									fill="#FBBC05"
									d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z"
								></path>
								<path
									fill="#EB4335"
									d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
								></path>
							</svg>
							<span>Sign in with Google</span>
							{client.isLastUsedLoginMethod("google") && <LastUsedIndicator />}
						</Button>
						<Button
							variant="outline"
							className={cn("w-full gap-2 flex relative")}
							onClick={async () => {
								await signIn.social({
									provider: "twitch",
									callbackURL: "/dashboard",
								});
							}}
						>
							<svg
								xmlns="http://www.w3.org/2000/svg"
								width="0.98em"
								height="1em"
								viewBox="0 0 256 262"
							>
								<path
									fill="#4285F4"
									d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
								></path>
								<path
									fill="#34A853"
									d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
								></path>
								<path
									fill="#FBBC05"
									d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z"
								></path>
								<path
									fill="#EB4335"
									d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
								></path>
							</svg>
							<span>Sign in with Twitch</span>
							{client.isLastUsedLoginMethod("apple") && <LastUsedIndicator />}
						</Button>
						<Button
							variant="outline"
							className={cn("w-full gap-2 flex items-center relative")}
							onClick={async () => {
								await signIn.social({
									provider: "github",
									callbackURL: "/dashboard",
								});
							}}
						>
							<svg
								xmlns="http://www.w3.org/2000/svg"
								width="1em"
								height="1em"
								viewBox="0 0 24 24"
							>
								<path
									fill="currentColor"
									d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33s1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2"
								></path>
							</svg>
							<span>Sign in with GitHub</span>
							{client.isLastUsedLoginMethod("github") && <LastUsedIndicator />}
						</Button>
						<Button
							variant="outline"
							className={cn("w-full gap-2 flex items-center relative")}
							onClick={async () => {
								await signIn.social({
									provider: "microsoft",
									callbackURL: "/dashboard",
								});
							}}
						>
							<svg
								xmlns="http://www.w3.org/2000/svg"
								width="1em"
								height="1em"
								viewBox="0 0 24 24"
							>
								<path
									fill="currentColor"
									d="M2 3h9v9H2zm9 19H2v-9h9zM21 3v9h-9V3zm0 19h-9v-9h9z"
								></path>
							</svg>
							<span>Sign in with Microsoft</span>
							{client.isLastUsedLoginMethod("microsoft") && (
								<LastUsedIndicator />
							)}
						</Button>
					</div>
				</div>
			</CardContent>
			<CardFooter>
				<div className="flex justify-center w-full border-t pt-4">
					<p className="text-center text-xs text-neutral-500">
						built with{" "}
						<Link
							href="https://better-auth.com"
							className="underline"
							target="_blank"
						>
							<span className="dark:text-white/70 cursor-pointer">
								better-auth.
							</span>
						</Link>
					</p>
				</div>
			</CardFooter>
		</Card>
	);
}

```

--------------------------------------------------------------------------------
/docs/content/docs/plugins/email-otp.mdx:
--------------------------------------------------------------------------------

```markdown
---
title: Email OTP
description: Email OTP plugin for Better Auth.
---

The Email OTP plugin allows user to sign in, verify their email, or reset their password using a one-time password (OTP) sent to their email address.


## Installation

<Steps>
  <Step>
    ### Add the plugin to your auth config

    Add the `emailOTP` plugin to your auth config and implement the `sendVerificationOTP()` method.

    ```ts title="auth.ts"
    import { betterAuth } from "better-auth"
    import { emailOTP } from "better-auth/plugins" // [!code highlight]
    
    export const auth = betterAuth({
        // ... other config options
        plugins: [
            emailOTP({ // [!code highlight]
                async sendVerificationOTP({ email, otp, type }) { // [!code highlight]
                    if (type === "sign-in") { // [!code highlight]
                        // Send the OTP for sign in // [!code highlight]
                    } else if (type === "email-verification") { // [!code highlight]
                        // Send the OTP for email verification // [!code highlight]
                    } else { // [!code highlight]
                        // Send the OTP for password reset // [!code highlight]
                    } // [!code highlight]
                }, // [!code highlight]
            }) // [!code highlight]
        ]
    })
    ```
    </Step>
    <Step>
    ### Add the client plugin

    ```ts title="auth-client.ts"
    import { createAuthClient } from "better-auth/client"
    import { emailOTPClient } from "better-auth/client/plugins"
    
    export const authClient = createAuthClient({
        plugins: [
            emailOTPClient()
        ]
    })
    ```
  </Step>
</Steps>

## Usage

### Send an OTP

Use the `sendVerificationOtp()` method to send an OTP to the user's email address.

<APIMethod path="/email-otp/send-verification-otp" method="POST">
```ts
type sendVerificationOTP = {
    /**
     * Email address to send the OTP. 
     */
    email: string = "[email protected]"
    /**
     * Type of the OTP. `sign-in`, `email-verification`, or `forget-password`. 
     */
    type: "email-verification" | "sign-in" | "forget-password" = "sign-in"
}
```
</APIMethod>

### Check an OTP (optional)

Use the `checkVerificationOtp()` method to check if an OTP is valid.

<APIMethod path="/email-otp/check-verification-otp" method="POST">
```ts
type checkVerificationOTP = {
    /**
     * Email address to send the OTP. 
     */
    email: string = "[email protected]"
    /**
     * Type of the OTP. `sign-in`, `email-verification`, or `forget-password`. 
     */
    type: "email-verification" | "sign-in" | "forget-password" = "sign-in"
    /**
     * OTP sent to the email. 
     */
    otp: string = "123456"
}
```
</APIMethod>

### Sign In with OTP

To sign in with OTP, use the `sendVerificationOtp()` method to send a "sign-in" OTP to the user's email address.

<APIMethod path="/email-otp/send-verification-otp" method="POST">
```ts
type sendVerificationOTP = {
    /**
     * Email address to send the OTP. 
     */
    email: string = "[email protected]"
    /**
     * Type of the OTP.
     */
    type: "sign-in" = "sign-in"
}
```
</APIMethod>

Once the user provides the OTP, you can sign in the user using the `signIn.emailOtp()` method.

<APIMethod path="/sign-in/email-otp" method="POST">
```ts
type signInEmailOTP = {
    /**
     * Email address to sign in. 
     */
    email: string = "[email protected]"
    /**
     * OTP sent to the email. 
     */
    otp: string = "123456"
}
```
</APIMethod>

<Callout>
If the user is not registered, they'll be automatically registered. If you want to prevent this, you can pass `disableSignUp` as `true` in the [options](#options).
</Callout>

### Verify Email with OTP

To verify the user's email address with OTP, use the `sendVerificationOtp()` method to send an "email-verification" OTP to the user's email address.

<APIMethod path="/email-otp/send-verification-otp" method="POST">
```ts
type sendVerificationOTP = {
    /**
     * Email address to send the OTP. 
     */
    email: string = "[email protected]"
    /**
     * Type of the OTP.
     */
    type: "email-verification" = "email-verification"
}
```
</APIMethod>

Once the user provides the OTP, use the `verifyEmail()` method to complete email verification.

<APIMethod path="/email-otp/verify-email" method="POST">
```ts
type verifyEmailOTP = {
    /**
     * Email address to verify. 
     */
    email: string = "[email protected]"
    /**
     * OTP to verify. 
     */
    otp: string = "123456"
}
```
</APIMethod>

### Reset Password with OTP

To reset the user's password with OTP, use the `forgetPassword.emailOTP()` method to send a "forget-password" OTP to the user's email address.

<APIMethod path="/forget-password/email-otp" method="POST">
```ts
type forgetPasswordEmailOTP = {
    /**
     * Email address to send the OTP. 
     */
    email: string = "[email protected]"
}
```
</APIMethod>

Once the user provides the OTP, use the `checkVerificationOtp()` method to check if it's valid (optional).

<APIMethod path="/email-otp/check-verification-otp" method="POST">
```ts
type checkVerificationOTP = {
    /**
     * Email address to send the OTP. 
     */
    email: string = "[email protected]"
    /**
     * Type of the OTP.
     */
    type: "forget-password" = "forget-password"
    /**
     * OTP sent to the email. 
     */
    otp: string = "123456"
}
```
</APIMethod>

Then, use the `resetPassword()` method to reset the user's password.

<APIMethod path="/email-otp/reset-password" method="POST">
```ts
type resetPasswordEmailOTP = {
    /**
     * Email address to reset the password. 
     */
    email: string = "[email protected]"
    /**
     * OTP sent to the email. 
     */
    otp: string = "123456"
    /**
     * New password. 
     */
    password: string = "new-secure-password"
}
```
</APIMethod>

### Override Default Email Verification

To override the default email verification, pass `overrideDefaultEmailVerification: true` in the options. This will make the system use an email OTP instead of the default verification link whenever email verification is triggered. In other words, the user will verify their email using an OTP rather than clicking a link.

```ts title="auth.ts"
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  plugins: [
    emailOTP({
      overrideDefaultEmailVerification: true, // [!code highlight]
      async sendVerificationOTP({ email, otp, type }) {
        // Implement the sendVerificationOTP method to send the OTP to the user's email address
      },
    }),
  ],
});
```


## Options

- `sendVerificationOTP`: A function that sends the OTP to the user's email address. The function receives an object with the following properties:
  - `email`: The user's email address.
  - `otp`: The OTP to send.
  - `type`: The type of OTP to send. Can be "sign-in", "email-verification", or "forget-password".

- `otpLength`: The length of the OTP. Defaults to `6`.

- `expiresIn`: The expiry time of the OTP in seconds. Defaults to `300` seconds.

```ts title="auth.ts"
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    plugins: [
        emailOTP({
            otpLength: 8,
            expiresIn: 600
        })
    ]
})
```

- `sendVerificationOnSignUp`: A boolean value that determines whether to send the OTP when a user signs up. Defaults to `false`.

- `disableSignUp`: A boolean value that determines whether to prevent automatic sign-up when the user is not registered. Defaults to `false`.

- `generateOTP`: A function that generates the OTP. Defaults to a random 6-digit number.

- `allowedAttempts`: The maximum number of attempts allowed for verifying an OTP. Defaults to `3`. After exceeding this limit, the OTP becomes invalid and the user needs to request a new one.

```ts title="auth.ts"
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    plugins: [
        emailOTP({
            allowedAttempts: 5, // Allow 5 attempts before invalidating the OTP
            expiresIn: 300
        })
    ]
})
```

When the maximum attempts are exceeded, the `verifyOTP`, `signIn.emailOtp`, `verifyEmail`, and `resetPassword` methods will return an error with code `TOO_MANY_ATTEMPTS`.

- `storeOTP`: The method to store the OTP in your database, wether `encrypted`, `hashed` or `plain` text. Default is `plain` text.

<Callout>
Note: This will not affect the OTP sent to the user, it will only affect the OTP stored in your database.
</Callout>

Alternatively, you can pass a custom encryptor or hasher to store the OTP in your database.

**Custom encryptor**

```ts title="auth.ts"
emailOTP({
    storeOTP: { 
        encrypt: async (otp) => {
            return myCustomEncryptor(otp);
        },
        decrypt: async (otp) => {
            return myCustomDecryptor(otp);
        },
    }
})
```

**Custom hasher**

```ts title="auth.ts"
emailOTP({
    storeOTP: {
        hash: async (otp) => {
            return myCustomHasher(otp);
        },
    }
})
```

```

--------------------------------------------------------------------------------
/docs/components/builder/beam.tsx:
--------------------------------------------------------------------------------

```typescript
"use client";
import React from "react";
import { motion } from "framer-motion";
import { cn } from "@/lib/utils";

export const BackgroundBeams = React.memo(
	({ className }: { className?: string }) => {
		const paths = [
			"M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875",
			"M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867",
			"M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859",
			"M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851",
			"M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843",
			"M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835",
			"M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827",
			"M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819",
			"M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811",
			"M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803",
			"M-310 -269C-310 -269 -242 136 222 263C686 390 754 795 754 795",
			"M-303 -277C-303 -277 -235 128 229 255C693 382 761 787 761 787",
			"M-296 -285C-296 -285 -228 120 236 247C700 374 768 779 768 779",
			"M-289 -293C-289 -293 -221 112 243 239C707 366 775 771 775 771",
			"M-282 -301C-282 -301 -214 104 250 231C714 358 782 763 782 763",
			"M-275 -309C-275 -309 -207 96 257 223C721 350 789 755 789 755",
			"M-268 -317C-268 -317 -200 88 264 215C728 342 796 747 796 747",
			"M-261 -325C-261 -325 -193 80 271 207C735 334 803 739 803 739",
			"M-254 -333C-254 -333 -186 72 278 199C742 326 810 731 810 731",
			"M-247 -341C-247 -341 -179 64 285 191C749 318 817 723 817 723",
			"M-240 -349C-240 -349 -172 56 292 183C756 310 824 715 824 715",
			"M-233 -357C-233 -357 -165 48 299 175C763 302 831 707 831 707",
			"M-226 -365C-226 -365 -158 40 306 167C770 294 838 699 838 699",
			"M-219 -373C-219 -373 -151 32 313 159C777 286 845 691 845 691",
			"M-212 -381C-212 -381 -144 24 320 151C784 278 852 683 852 683",
			"M-205 -389C-205 -389 -137 16 327 143C791 270 859 675 859 675",
			"M-198 -397C-198 -397 -130 8 334 135C798 262 866 667 866 667",
			"M-191 -405C-191 -405 -123 0 341 127C805 254 873 659 873 659",
			"M-184 -413C-184 -413 -116 -8 348 119C812 246 880 651 880 651",
			"M-177 -421C-177 -421 -109 -16 355 111C819 238 887 643 887 643",
			"M-170 -429C-170 -429 -102 -24 362 103C826 230 894 635 894 635",
			"M-163 -437C-163 -437 -95 -32 369 95C833 222 901 627 901 627",
			"M-156 -445C-156 -445 -88 -40 376 87C840 214 908 619 908 619",
			"M-149 -453C-149 -453 -81 -48 383 79C847 206 915 611 915 611",
			"M-142 -461C-142 -461 -74 -56 390 71C854 198 922 603 922 603",
			"M-135 -469C-135 -469 -67 -64 397 63C861 190 929 595 929 595",
			"M-128 -477C-128 -477 -60 -72 404 55C868 182 936 587 936 587",
			"M-121 -485C-121 -485 -53 -80 411 47C875 174 943 579 943 579",
			"M-114 -493C-114 -493 -46 -88 418 39C882 166 950 571 950 571",
			"M-107 -501C-107 -501 -39 -96 425 31C889 158 957 563 957 563",
			"M-100 -509C-100 -509 -32 -104 432 23C896 150 964 555 964 555",
			"M-93 -517C-93 -517 -25 -112 439 15C903 142 971 547 971 547",
			"M-86 -525C-86 -525 -18 -120 446 7C910 134 978 539 978 539",
			"M-79 -533C-79 -533 -11 -128 453 -1C917 126 985 531 985 531",
			"M-72 -541C-72 -541 -4 -136 460 -9C924 118 992 523 992 523",
			"M-65 -549C-65 -549 3 -144 467 -17C931 110 999 515 999 515",
			"M-58 -557C-58 -557 10 -152 474 -25C938 102 1006 507 1006 507",
			"M-51 -565C-51 -565 17 -160 481 -33C945 94 1013 499 1013 499",
			"M-44 -573C-44 -573 24 -168 488 -41C952 86 1020 491 1020 491",
			"M-37 -581C-37 -581 31 -176 495 -49C959 78 1027 483 1027 483",
		];
		return (
			<div
				className={cn(
					"absolute  h-full w-full inset-0  [mask-size:40px] [mask-repeat:no-repeat] flex items-center justify-center",
					className,
				)}
			>
				<svg
					className=" z-0 h-full w-full pointer-events-none absolute "
					width="100%"
					height="100%"
					viewBox="0 0 696 316"
					fill="none"
					xmlns="http://www.w3.org/2000/svg"
				>
					<path
						d="M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803M-310 -269C-310 -269 -242 136 222 263C686 390 754 795 754 795M-303 -277C-303 -277 -235 128 229 255C693 382 761 787 761 787M-296 -285C-296 -285 -228 120 236 247C700 374 768 779 768 779M-289 -293C-289 -293 -221 112 243 239C707 366 775 771 775 771M-282 -301C-282 -301 -214 104 250 231C714 358 782 763 782 763M-275 -309C-275 -309 -207 96 257 223C721 350 789 755 789 755M-268 -317C-268 -317 -200 88 264 215C728 342 796 747 796 747M-261 -325C-261 -325 -193 80 271 207C735 334 803 739 803 739M-254 -333C-254 -333 -186 72 278 199C742 326 810 731 810 731M-247 -341C-247 -341 -179 64 285 191C749 318 817 723 817 723M-240 -349C-240 -349 -172 56 292 183C756 310 824 715 824 715M-233 -357C-233 -357 -165 48 299 175C763 302 831 707 831 707M-226 -365C-226 -365 -158 40 306 167C770 294 838 699 838 699M-219 -373C-219 -373 -151 32 313 159C777 286 845 691 845 691M-212 -381C-212 -381 -144 24 320 151C784 278 852 683 852 683M-205 -389C-205 -389 -137 16 327 143C791 270 859 675 859 675M-198 -397C-198 -397 -130 8 334 135C798 262 866 667 866 667M-191 -405C-191 -405 -123 0 341 127C805 254 873 659 873 659M-184 -413C-184 -413 -116 -8 348 119C812 246 880 651 880 651M-177 -421C-177 -421 -109 -16 355 111C819 238 887 643 887 643M-170 -429C-170 -429 -102 -24 362 103C826 230 894 635 894 635M-163 -437C-163 -437 -95 -32 369 95C833 222 901 627 901 627M-156 -445C-156 -445 -88 -40 376 87C840 214 908 619 908 619M-149 -453C-149 -453 -81 -48 383 79C847 206 915 611 915 611M-142 -461C-142 -461 -74 -56 390 71C854 198 922 603 922 603M-135 -469C-135 -469 -67 -64 397 63C861 190 929 595 929 595M-128 -477C-128 -477 -60 -72 404 55C868 182 936 587 936 587M-121 -485C-121 -485 -53 -80 411 47C875 174 943 579 943 579M-114 -493C-114 -493 -46 -88 418 39C882 166 950 571 950 571M-107 -501C-107 -501 -39 -96 425 31C889 158 957 563 957 563M-100 -509C-100 -509 -32 -104 432 23C896 150 964 555 964 555M-93 -517C-93 -517 -25 -112 439 15C903 142 971 547 971 547M-86 -525C-86 -525 -18 -120 446 7C910 134 978 539 978 539M-79 -533C-79 -533 -11 -128 453 -1C917 126 985 531 985 531M-72 -541C-72 -541 -4 -136 460 -9C924 118 992 523 992 523M-65 -549C-65 -549 3 -144 467 -17C931 110 999 515 999 515M-58 -557C-58 -557 10 -152 474 -25C938 102 1006 507 1006 507M-51 -565C-51 -565 17 -160 481 -33C945 94 1013 499 1013 499M-44 -573C-44 -573 24 -168 488 -41C952 86 1020 491 1020 491M-37 -581C-37 -581 31 -176 495 -49C959 78 1027 483 1027 483M-30 -589C-30 -589 38 -184 502 -57C966 70 1034 475 1034 475M-23 -597C-23 -597 45 -192 509 -65C973 62 1041 467 1041 467M-16 -605C-16 -605 52 -200 516 -73C980 54 1048 459 1048 459M-9 -613C-9 -613 59 -208 523 -81C987 46 1055 451 1055 451M-2 -621C-2 -621 66 -216 530 -89C994 38 1062 443 1062 443M5 -629C5 -629 73 -224 537 -97C1001 30 1069 435 1069 435M12 -637C12 -637 80 -232 544 -105C1008 22 1076 427 1076 427M19 -645C19 -645 87 -240 551 -113C1015 14 1083 419 1083 419"
						stroke="url(#paint0_radial_242_278)"
						strokeOpacity="0.05"
						strokeWidth="0.5"
					></path>

					{paths.map((path, index) => (
						<motion.path
							key={`path-` + index}
							d={path}
							stroke={`url(#linearGradient-${index})`}
							strokeOpacity="0.4"
							strokeWidth="0.5"
						></motion.path>
					))}
					<defs>
						{paths.map((path, index) => (
							<motion.linearGradient
								id={`linearGradient-${index}`}
								key={`gradient-${index}`}
								initial={{
									x1: "0%",
									x2: "0%",
									y1: "0%",
									y2: "0%",
								}}
								animate={{
									x1: ["0%", "100%"],
									x2: ["0%", "95%"],
									y1: ["0%", "100%"],
									y2: ["0%", `${93 + Math.random() * 8}%`],
								}}
								transition={{
									duration: Math.random() * 10 + 10,
									ease: "easeInOut",
									repeat: Infinity,
									delay: Math.random() * 10,
								}}
							>
								<stop stopColor="#18CCFC" stopOpacity="0"></stop>
								<stop stopColor="#18CCFC"></stop>
								<stop offset="32.5%" stopColor="#6344F5"></stop>
								<stop offset="100%" stopColor="#AE48FF" stopOpacity="0"></stop>
							</motion.linearGradient>
						))}

						<radialGradient
							id="paint0_radial_242_278"
							cx="0"
							cy="0"
							r="1"
							gradientUnits="userSpaceOnUse"
							gradientTransform="translate(352 34) rotate(90) scale(555 1560.62)"
						>
							<stop offset="0.0666667" stopColor="var(--neutral-300)"></stop>
							<stop offset="0.243243" stopColor="var(--neutral-300)"></stop>
							<stop offset="0.43594" stopColor="white" stopOpacity="0"></stop>
						</radialGradient>
					</defs>
				</svg>
			</div>
		);
	},
);

BackgroundBeams.displayName = "BackgroundBeams";

```

--------------------------------------------------------------------------------
/docs/components/ui/background-beams.tsx:
--------------------------------------------------------------------------------

```typescript
"use client";
import React from "react";
import { motion } from "framer-motion";
import { cn } from "@/lib/utils";

export const BackgroundBeams = React.memo(
	({ className }: { className?: string }) => {
		const paths = [
			"M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875",
			"M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867",
			"M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859",
			"M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851",
			"M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843",
			"M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835",
			"M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827",
			"M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819",
			"M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811",
			"M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803",
			"M-310 -269C-310 -269 -242 136 222 263C686 390 754 795 754 795",
			"M-303 -277C-303 -277 -235 128 229 255C693 382 761 787 761 787",
			"M-296 -285C-296 -285 -228 120 236 247C700 374 768 779 768 779",
			"M-289 -293C-289 -293 -221 112 243 239C707 366 775 771 775 771",
			"M-282 -301C-282 -301 -214 104 250 231C714 358 782 763 782 763",
			"M-275 -309C-275 -309 -207 96 257 223C721 350 789 755 789 755",
			"M-268 -317C-268 -317 -200 88 264 215C728 342 796 747 796 747",
			"M-261 -325C-261 -325 -193 80 271 207C735 334 803 739 803 739",
			"M-254 -333C-254 -333 -186 72 278 199C742 326 810 731 810 731",
			"M-247 -341C-247 -341 -179 64 285 191C749 318 817 723 817 723",
			"M-240 -349C-240 -349 -172 56 292 183C756 310 824 715 824 715",
			"M-233 -357C-233 -357 -165 48 299 175C763 302 831 707 831 707",
			"M-226 -365C-226 -365 -158 40 306 167C770 294 838 699 838 699",
			"M-219 -373C-219 -373 -151 32 313 159C777 286 845 691 845 691",
			"M-212 -381C-212 -381 -144 24 320 151C784 278 852 683 852 683",
			"M-205 -389C-205 -389 -137 16 327 143C791 270 859 675 859 675",
			"M-198 -397C-198 -397 -130 8 334 135C798 262 866 667 866 667",
			"M-191 -405C-191 -405 -123 0 341 127C805 254 873 659 873 659",
			"M-184 -413C-184 -413 -116 -8 348 119C812 246 880 651 880 651",
			"M-177 -421C-177 -421 -109 -16 355 111C819 238 887 643 887 643",
			"M-170 -429C-170 -429 -102 -24 362 103C826 230 894 635 894 635",
			"M-163 -437C-163 -437 -95 -32 369 95C833 222 901 627 901 627",
			"M-156 -445C-156 -445 -88 -40 376 87C840 214 908 619 908 619",
			"M-149 -453C-149 -453 -81 -48 383 79C847 206 915 611 915 611",
			"M-142 -461C-142 -461 -74 -56 390 71C854 198 922 603 922 603",
			"M-135 -469C-135 -469 -67 -64 397 63C861 190 929 595 929 595",
			"M-128 -477C-128 -477 -60 -72 404 55C868 182 936 587 936 587",
			"M-121 -485C-121 -485 -53 -80 411 47C875 174 943 579 943 579",
			"M-114 -493C-114 -493 -46 -88 418 39C882 166 950 571 950 571",
			"M-107 -501C-107 -501 -39 -96 425 31C889 158 957 563 957 563",
			"M-100 -509C-100 -509 -32 -104 432 23C896 150 964 555 964 555",
			"M-93 -517C-93 -517 -25 -112 439 15C903 142 971 547 971 547",
			"M-86 -525C-86 -525 -18 -120 446 7C910 134 978 539 978 539",
			"M-79 -533C-79 -533 -11 -128 453 -1C917 126 985 531 985 531",
			"M-72 -541C-72 -541 -4 -136 460 -9C924 118 992 523 992 523",
			"M-65 -549C-65 -549 3 -144 467 -17C931 110 999 515 999 515",
			"M-58 -557C-58 -557 10 -152 474 -25C938 102 1006 507 1006 507",
			"M-51 -565C-51 -565 17 -160 481 -33C945 94 1013 499 1013 499",
			"M-44 -573C-44 -573 24 -168 488 -41C952 86 1020 491 1020 491",
			"M-37 -581C-37 -581 31 -176 495 -49C959 78 1027 483 1027 483",
		];
		return (
			<div
				className={cn(
					"absolute  h-full w-full inset-0  [mask-size:40px] [mask-repeat:no-repeat] flex items-center justify-center",
					className,
				)}
			>
				<svg
					className=" z-0 h-full w-full pointer-events-none absolute "
					width="100%"
					height="100%"
					viewBox="0 0 696 316"
					fill="none"
					xmlns="http://www.w3.org/2000/svg"
				>
					<path
						d="M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803M-310 -269C-310 -269 -242 136 222 263C686 390 754 795 754 795M-303 -277C-303 -277 -235 128 229 255C693 382 761 787 761 787M-296 -285C-296 -285 -228 120 236 247C700 374 768 779 768 779M-289 -293C-289 -293 -221 112 243 239C707 366 775 771 775 771M-282 -301C-282 -301 -214 104 250 231C714 358 782 763 782 763M-275 -309C-275 -309 -207 96 257 223C721 350 789 755 789 755M-268 -317C-268 -317 -200 88 264 215C728 342 796 747 796 747M-261 -325C-261 -325 -193 80 271 207C735 334 803 739 803 739M-254 -333C-254 -333 -186 72 278 199C742 326 810 731 810 731M-247 -341C-247 -341 -179 64 285 191C749 318 817 723 817 723M-240 -349C-240 -349 -172 56 292 183C756 310 824 715 824 715M-233 -357C-233 -357 -165 48 299 175C763 302 831 707 831 707M-226 -365C-226 -365 -158 40 306 167C770 294 838 699 838 699M-219 -373C-219 -373 -151 32 313 159C777 286 845 691 845 691M-212 -381C-212 -381 -144 24 320 151C784 278 852 683 852 683M-205 -389C-205 -389 -137 16 327 143C791 270 859 675 859 675M-198 -397C-198 -397 -130 8 334 135C798 262 866 667 866 667M-191 -405C-191 -405 -123 0 341 127C805 254 873 659 873 659M-184 -413C-184 -413 -116 -8 348 119C812 246 880 651 880 651M-177 -421C-177 -421 -109 -16 355 111C819 238 887 643 887 643M-170 -429C-170 -429 -102 -24 362 103C826 230 894 635 894 635M-163 -437C-163 -437 -95 -32 369 95C833 222 901 627 901 627M-156 -445C-156 -445 -88 -40 376 87C840 214 908 619 908 619M-149 -453C-149 -453 -81 -48 383 79C847 206 915 611 915 611M-142 -461C-142 -461 -74 -56 390 71C854 198 922 603 922 603M-135 -469C-135 -469 -67 -64 397 63C861 190 929 595 929 595M-128 -477C-128 -477 -60 -72 404 55C868 182 936 587 936 587M-121 -485C-121 -485 -53 -80 411 47C875 174 943 579 943 579M-114 -493C-114 -493 -46 -88 418 39C882 166 950 571 950 571M-107 -501C-107 -501 -39 -96 425 31C889 158 957 563 957 563M-100 -509C-100 -509 -32 -104 432 23C896 150 964 555 964 555M-93 -517C-93 -517 -25 -112 439 15C903 142 971 547 971 547M-86 -525C-86 -525 -18 -120 446 7C910 134 978 539 978 539M-79 -533C-79 -533 -11 -128 453 -1C917 126 985 531 985 531M-72 -541C-72 -541 -4 -136 460 -9C924 118 992 523 992 523M-65 -549C-65 -549 3 -144 467 -17C931 110 999 515 999 515M-58 -557C-58 -557 10 -152 474 -25C938 102 1006 507 1006 507M-51 -565C-51 -565 17 -160 481 -33C945 94 1013 499 1013 499M-44 -573C-44 -573 24 -168 488 -41C952 86 1020 491 1020 491M-37 -581C-37 -581 31 -176 495 -49C959 78 1027 483 1027 483M-30 -589C-30 -589 38 -184 502 -57C966 70 1034 475 1034 475M-23 -597C-23 -597 45 -192 509 -65C973 62 1041 467 1041 467M-16 -605C-16 -605 52 -200 516 -73C980 54 1048 459 1048 459M-9 -613C-9 -613 59 -208 523 -81C987 46 1055 451 1055 451M-2 -621C-2 -621 66 -216 530 -89C994 38 1062 443 1062 443M5 -629C5 -629 73 -224 537 -97C1001 30 1069 435 1069 435M12 -637C12 -637 80 -232 544 -105C1008 22 1076 427 1076 427M19 -645C19 -645 87 -240 551 -113C1015 14 1083 419 1083 419"
						stroke="url(#paint0_radial_242_278)"
						strokeOpacity="0.05"
						strokeWidth="0.5"
					></path>

					{paths.map((path, index) => (
						<motion.path
							key={`path-` + index}
							d={path}
							stroke={`url(#linearGradient-${index})`}
							strokeOpacity="0.4"
							strokeWidth="0.5"
						></motion.path>
					))}
					<defs>
						{paths.map((path, index) => (
							<motion.linearGradient
								id={`linearGradient-${index}`}
								key={`gradient-${index}`}
								initial={{
									x1: "0%",
									x2: "0%",
									y1: "0%",
									y2: "0%",
								}}
								animate={{
									x1: ["0%", "100%"],
									x2: ["0%", "95%"],
									y1: ["0%", "100%"],
									y2: ["0%", `${93 + Math.random() * 8}%`],
								}}
								transition={{
									duration: Math.random() * 10 + 10,
									ease: "easeInOut",
									repeat: Infinity,
									delay: Math.random() * 10,
								}}
							>
								<stop stopColor="#18CCFC" stopOpacity="0"></stop>
								<stop stopColor="#18CCFC"></stop>
								<stop offset="32.5%" stopColor="#6344F5"></stop>
								<stop offset="100%" stopColor="#AE48FF" stopOpacity="0"></stop>
							</motion.linearGradient>
						))}

						<radialGradient
							id="paint0_radial_242_278"
							cx="0"
							cy="0"
							r="1"
							gradientUnits="userSpaceOnUse"
							gradientTransform="translate(352 34) rotate(90) scale(555 1560.62)"
						>
							<stop offset="0.0666667" stopColor="var(--neutral-300)"></stop>
							<stop offset="0.243243" stopColor="var(--neutral-300)"></stop>
							<stop offset="0.43594" stopColor="white" stopOpacity="0"></stop>
						</radialGradient>
					</defs>
				</svg>
			</div>
		);
	},
);

BackgroundBeams.displayName = "BackgroundBeams";

```

--------------------------------------------------------------------------------
/docs/content/docs/concepts/session-management.mdx:
--------------------------------------------------------------------------------

```markdown
---
title: Session Management
description: Better Auth session management.
---

Better Auth manages session using a traditional cookie-based session management. The session is stored in a cookie and is sent to the server on every request. The server then verifies the session and returns the user data if the session is valid.

## Session table

The session table stores the session data. The session table has the following fields:

- `id`: The session token. Which is also used as the session cookie.
- `userId`: The user ID of the user.
- `expiresAt`: The expiration date of the session.
- `ipAddress`: The IP address of the user.
- `userAgent`: The user agent of the user. It stores the user agent header from the request.

## Session Expiration

The session expires after 7 days by default. But whenever the session is used and the `updateAge` is reached, the session expiration is updated to the current time plus the `expiresIn` value.

You can change both the `expiresIn` and `updateAge` values by passing the `session` object to the `auth` configuration.

```ts title="auth.ts"
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    //... other config options
    session: {
        expiresIn: 60 * 60 * 24 * 7, // 7 days
        updateAge: 60 * 60 * 24 // 1 day (every 1 day the session expiration is updated)
    }
})
```

### Disable Session Refresh

You can disable session refresh so that the session is not updated regardless of the `updateAge` option.

```ts title="auth.ts"
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    //... other config options
    session: {
        disableSessionRefresh: true
    }
})
```

## Session Freshness

Some endpoints in Better Auth require the session to be **fresh**. A session is considered fresh if its `createdAt` is within the `freshAge` limit. By default, the `freshAge` is set to **1 day** (60 * 60 * 24).  

You can customize the `freshAge` value by passing a `session` object in the `auth` configuration:  

```ts title="auth.ts"
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    //... other config options
    session: {
        freshAge: 60 * 5 // 5 minutes (the session is fresh if created within the last 5 minutes)
    }
})
```

To **disable the freshness check**, set `freshAge` to `0`:  

```ts title="auth.ts"
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    //... other config options
    session: {
        freshAge: 0 // Disable freshness check
    }
})
```
## Session Management

Better Auth provides a set of functions to manage sessions.

### Get Session

The `getSession` function retrieves the current active session.

```ts client="client.ts"
import { authClient } from "@/lib/client"

const { data: session } = await authClient.getSession()
```

To learn how to customize the session response check the [Customizing Session Response](#customizing-session-response) section.

### Use Session

The `useSession` action provides a reactive way to access the current session.

```ts client="client.ts"
import { authClient } from "@/lib/client"

const { data: session } = authClient.useSession()
```

### List Sessions

The `listSessions` function returns a list of sessions that are active for the user.

```ts title="auth-client.ts"
import { authClient } from "@/lib/client"

const sessions = await authClient.listSessions()
```

### Revoke Session

When a user signs out of a device, the session is automatically ended. However, you can also end a session manually from any device the user is signed into.

To end a session, use the `revokeSession` function. Just pass the session token as a parameter.

```ts title="auth-client.ts"
await authClient.revokeSession({
    token: "session-token"
})
```

### Revoke Other Sessions

To revoke all other sessions except the current session, you can use the `revokeOtherSessions` function.

```ts title="auth-client.ts"
await authClient.revokeOtherSessions()
```

### Revoke All Sessions

To revoke all sessions, you can use the `revokeSessions` function.

```ts title="auth-client.ts"
await authClient.revokeSessions()
```

### Revoking Sessions on Password Change

You can revoke all sessions when the user changes their password by passing `revokeOtherSessions` as true on `changePassword` function.

```ts title="auth.ts"
await authClient.changePassword({
    newPassword: newPassword,
    currentPassword: currentPassword,
    revokeOtherSessions: true,
})
```

## Session Caching

### Cookie Cache

Calling your database every time `useSession` or `getSession` invoked isn’t ideal, especially if sessions don’t change frequently. Cookie caching handles this by storing session data in a short-lived, signed cookie—similar to how JWT access tokens are used with refresh tokens.

When cookie caching is enabled, the server can check session validity from the cookie itself instead of hitting the database each time. The cookie is signed to prevent tampering, and a short `maxAge` ensures that the session data gets refreshed regularly. If a session is revoked or expires, the cookie will be invalidated automatically.

To turn on cookie caching, just set `session.cookieCache` in your auth config:

```ts title="auth.ts"
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    session: {
        cookieCache: {
            enabled: true,
            maxAge: 5 * 60 // Cache duration in seconds
        }
    }
});
```

If you want to disable returning from the cookie cache when fetching the session, you can pass `disableCookieCache:true` this will force the server to fetch the session from the database and also refresh the cookie cache.

```ts title="auth-client.ts"
const session = await authClient.getSession({ query: {
    disableCookieCache: true
}})
```

or on the server

```ts title="server.ts"
await auth.api.getSession({
    query: {
        disableCookieCache: true,
    }, 
    headers: req.headers, // pass the headers
});
```


## Customizing Session Response

When you call `getSession` or `useSession`, the session data is returned as a `user` and `session` object. You can customize this response using the `customSession` plugin.

```ts title="auth.ts"
import { customSession } from "better-auth/plugins";

export const auth = betterAuth({
    plugins: [
        customSession(async ({ user, session }) => {
            const roles = findUserRoles(session.session.userId);
            return {
                roles,
                user: {
                    ...user,
                    newField: "newField",
                },
                session
            };
        }),
    ],
});
```

This will add `roles` and `user.newField` to the session response.

**Infer on the Client**

```ts title="auth-client.ts"
import { customSessionClient } from "better-auth/client/plugins";
import type { auth } from "@/lib/auth"; // Import the auth instance as a type

const authClient = createAuthClient({
    plugins: [customSessionClient<typeof auth>()],
});

const { data } = authClient.useSession();
const { data: sessionData } = await authClient.getSession();
// data.roles
// data.user.newField
```

### Caveats on Customizing Session Response

1. The passed `session` object to the callback does not infer fields added by plugins.  

However, as a workaround, you can pull up your auth options and pass it to the plugin to infer the fields.

```ts
import { betterAuth, BetterAuthOptions } from "better-auth";

const options = {
  //...config options
  plugins: [
    //...plugins 
  ]
} satisfies BetterAuthOptions;

export const auth = betterAuth({
    ...options,
    plugins: [
        ...(options.plugins ?? []),
        customSession(async ({ user, session }, ctx) => {
            // now both user and session will infer the fields added by plugins and your custom fields
            return {
                user,
                session
            }
        }, options), // pass options here  // [!code highlight]
    ]
})
```

2. When your server and client code are in separate projects or repositories, and you cannot import the `auth` instance as a type reference, type inference for custom session fields will not work on the client side.
3. Session caching, including secondary storage or cookie cache, does not include custom fields. Each time the session is fetched, your custom session function will be called.

**Mutating the list-device-sessions endpoint**
The `/multi-session/list-device-sessions` endpoint from the [multi-session](/docs/plugins/multi-session) plugin is used to list the devices that the user is signed into.

You can mutate the response of this endpoint by passing the `shouldMutateListDeviceSessionsEndpoint` option to the `customSession` plugin.

By default, we do not mutate the response of this endpoint.

```ts title="auth.ts"
import { customSession } from "better-auth/plugins";

export const auth = betterAuth({
    plugins: [
        customSession(async ({ user, session }, ctx) => {
            return {
                user,
                session
            }
        }, {}, { shouldMutateListDeviceSessionsEndpoint: true }), // [!code highlight]
    ],
});
```
```

--------------------------------------------------------------------------------
/packages/cli/test/info.test.ts:
--------------------------------------------------------------------------------

```typescript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import fs from "node:fs/promises";
import path from "node:path";
import { exec } from "node:child_process";
import { promisify } from "node:util";

const execAsync = promisify(exec);

let tmpDir = ".";

describe("info command", () => {
	beforeEach(async () => {
		const tmp = path.join(
			process.cwd(),
			"node_modules",
			".cache",
			"info_test-",
		);
		await fs.mkdir(path.join(tmp, "node_modules", ".cache"), {
			recursive: true,
		});
		tmpDir = await fs.mkdtemp(tmp);

		// Mock console methods to avoid noise in test output
		vi.spyOn(console, "log").mockImplementation(() => {});
		vi.spyOn(console, "error").mockImplementation(() => {});
	});

	afterEach(async () => {
		await fs.rm(tmpDir, { recursive: true });
		vi.restoreAllMocks();
	});

	it("should display system information without auth config", async () => {
		// Create a minimal package.json
		await fs.writeFile(
			path.join(tmpDir, "package.json"),
			JSON.stringify({
				name: "test-project",
				version: "1.0.0",
				dependencies: {
					"better-auth": "^1.0.0",
				},
			}),
		);

		const cliPath = path.join(process.cwd(), "dist", "index.js");
		const { stdout } = await execAsync(`node ${cliPath} info --json`, {
			cwd: tmpDir,
		});

		const output = JSON.parse(stdout);

		// Check system information
		expect(output.system).toHaveProperty("platform");
		expect(output.system).toHaveProperty("arch");
		expect(output.system).toHaveProperty("cpuCount");
		expect(output.system).toHaveProperty("totalMemory");

		// Check node information
		expect(output.node).toHaveProperty("version");
		expect(output.node).toHaveProperty("env");

		// Check package manager
		expect(output.packageManager).toHaveProperty("name");
		expect(output.packageManager).toHaveProperty("version");

		// Better Auth config should have an error since no auth file exists
		expect(output.betterAuth).toHaveProperty("version");
		expect(output.betterAuth.config).toBeNull();
	});

	it("should load and sanitize auth configuration", async () => {
		// Create package.json with dependencies
		await fs.writeFile(
			path.join(tmpDir, "package.json"),
			JSON.stringify({
				name: "test-project",
				version: "1.0.0",
				dependencies: {
					"better-auth": "^1.0.0",
					next: "^14.0.0",
					react: "^18.0.0",
				},
			}),
		);

		// Create auth.ts with sensitive data - using in-memory database to avoid adapter errors
		await fs.writeFile(
			path.join(tmpDir, "auth.ts"),
			`import { betterAuth } from "better-auth";

			export const auth = betterAuth({
				secret: "super-secret-key-123",
				baseURL: "https://example.com",
				emailAndPassword: {
					enabled: true,
				},
				socialProviders: {
					github: {
						clientId: "github-client-id",
						clientSecret: "github-client-secret"
					},
					google: {
						clientId: "google-client-id",
						clientSecret: "google-client-secret"
					}
				}
			})`,
		);

		const cliPath = path.join(process.cwd(), "dist", "index.js");
		const { stdout } = await execAsync(`node ${cliPath} info --json`, {
			cwd: tmpDir,
		});

		const output = JSON.parse(stdout);

		// Check that sensitive data is redacted
		expect(output.betterAuth.config).toBeDefined();
		expect(output.betterAuth.config.secret).toBe("[REDACTED]");

		// Check social providers are sanitized
		expect(output.betterAuth.config.socialProviders).toBeDefined();
		expect(output.betterAuth.config.socialProviders.github.clientId).toBe(
			"[REDACTED]",
		);
		expect(output.betterAuth.config.socialProviders.github.clientSecret).toBe(
			"[REDACTED]",
		);
		expect(output.betterAuth.config.socialProviders.google.clientId).toBe(
			"[REDACTED]",
		);
		expect(output.betterAuth.config.socialProviders.google.clientSecret).toBe(
			"[REDACTED]",
		);

		// Check non-sensitive data is preserved
		expect(output.betterAuth.config.emailAndPassword).toEqual({
			enabled: true,
		});
		expect(output.betterAuth.config.baseURL).toBe("https://example.com");
	});

	it("should detect installed frameworks", async () => {
		// Create package.json with various frameworks
		await fs.writeFile(
			path.join(tmpDir, "package.json"),
			JSON.stringify({
				name: "test-project",
				version: "1.0.0",
				dependencies: {
					"better-auth": "^1.0.0",
					next: "^14.0.0",
					react: "^18.0.0",
				},
				devDependencies: {
					"@sveltejs/kit": "^2.0.0",
					svelte: "^4.0.0",
				},
			}),
		);

		const cliPath = path.join(process.cwd(), "dist", "index.js");
		const { stdout } = await execAsync(`node ${cliPath} info --json`, {
			cwd: tmpDir,
		});

		const output = JSON.parse(stdout);

		// Check frameworks are detected
		expect(output.frameworks).toContainEqual({
			name: "next",
			version: "^14.0.0",
		});
		expect(output.frameworks).toContainEqual({
			name: "react",
			version: "^18.0.0",
		});
		expect(output.frameworks).toContainEqual({
			name: "@sveltejs/kit",
			version: "^2.0.0",
		});
		expect(output.frameworks).toContainEqual({
			name: "svelte",
			version: "^4.0.0",
		});
	});

	it("should detect database clients", async () => {
		// Create package.json with database clients
		await fs.writeFile(
			path.join(tmpDir, "package.json"),
			JSON.stringify({
				name: "test-project",
				version: "1.0.0",
				dependencies: {
					"better-auth": "^1.0.0",
					"@prisma/client": "^5.0.0",
					kysely: "^0.26.0",
				},
				devDependencies: {
					"drizzle-orm": "^0.29.0",
					"better-sqlite3": "^9.0.0",
				},
			}),
		);

		const cliPath = path.join(process.cwd(), "dist", "index.js");
		const { stdout } = await execAsync(`node ${cliPath} info --json`, {
			cwd: tmpDir,
		});

		const output = JSON.parse(stdout);

		// Check database clients are detected
		expect(output.databases).toContainEqual({
			name: "@prisma/client",
			version: "^5.0.0",
		});
		expect(output.databases).toContainEqual({
			name: "kysely",
			version: "^0.26.0",
		});
		expect(output.databases).toContainEqual({
			name: "drizzle",
			version: "^0.29.0",
		});
		expect(output.databases).toContainEqual({
			name: "better-sqlite3",
			version: "^9.0.0",
		});
	});

	it("should support custom config path", async () => {
		// Create package.json
		await fs.writeFile(
			path.join(tmpDir, "package.json"),
			JSON.stringify({
				name: "test-project",
				version: "1.0.0",
				dependencies: {
					"better-auth": "^1.0.0",
				},
			}),
		);

		// Create custom directory for auth config
		const customPath = path.join(tmpDir, "config");
		await fs.mkdir(customPath, { recursive: true });

		// Create auth config in custom location
		await fs.writeFile(
			path.join(customPath, "auth.config.ts"),
			`import { betterAuth } from "better-auth";

			export const auth = betterAuth({
				secret: "my-secret",
				appName: "Custom Config App",
				emailAndPassword: {
					enabled: true,
				}
			})`,
		);

		const cliPath = path.join(process.cwd(), "dist", "index.js");
		const { stdout } = await execAsync(
			`node ${cliPath} info --config config/auth.config.ts --json`,
			{ cwd: tmpDir },
		);

		const output = JSON.parse(stdout);

		// Check that custom config was loaded
		expect(output.betterAuth.config).toBeDefined();
		expect(output.betterAuth.config.appName).toBe("Custom Config App");
		expect(output.betterAuth.config.secret).toBe("[REDACTED]");
		expect(output.betterAuth.config.emailAndPassword).toEqual({
			enabled: true,
		});
	});

	it("should sanitize plugin configurations", async () => {
		// Create package.json
		await fs.writeFile(
			path.join(tmpDir, "package.json"),
			JSON.stringify({
				name: "test-project",
				version: "1.0.0",
				dependencies: {
					"better-auth": "^1.0.0",
				},
			}),
		);

		// Create auth.ts with plugins
		await fs.writeFile(
			path.join(tmpDir, "auth.ts"),
			`import { betterAuth } from "better-auth";
			import { twoFactor, organization } from "better-auth/plugins";

			export const auth = betterAuth({
				plugins: [
					twoFactor({
						otpOptions: {
							secret: "otp-secret-key"
						}
					}),
					organization({
						apiKey: "org-api-key",
						webhookSecret: "webhook-secret"
					})
				]
			})`,
		);

		const cliPath = path.join(process.cwd(), "dist", "index.js");
		const { stdout } = await execAsync(`node ${cliPath} info --json`, {
			cwd: tmpDir,
		});

		const output = JSON.parse(stdout);

		// Check that plugin configs are sanitized
		expect(output.betterAuth.config.plugins).toBeDefined();
		expect(Array.isArray(output.betterAuth.config.plugins)).toBe(true);

		// Plugin sensitive data should be redacted
		const plugins = output.betterAuth.config.plugins;
		plugins.forEach((plugin: any) => {
			if (plugin.config) {
				// Check that sensitive keys are redacted
				const configStr = JSON.stringify(plugin.config);
				expect(configStr).toContain("[REDACTED]");
			}
		});
	});

	it("should handle missing package.json gracefully", async () => {
		// Don't create package.json
		const cliPath = path.join(process.cwd(), "dist", "index.js");
		const { stdout } = await execAsync(`node ${cliPath} info --json`, {
			cwd: tmpDir,
		});

		const output = JSON.parse(stdout);

		// Should still return system info
		expect(output.system).toBeDefined();
		expect(output.node).toBeDefined();
		expect(output.packageManager).toBeDefined();

		// Frameworks and databases should be null
		expect(output.frameworks).toBeNull();
		expect(output.databases).toBeNull();
	});
}, 20000);

```

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

```typescript
import * as z from "zod";
import { createAuthEndpoint } from "@better-auth/core/api";
import { createEmailVerificationToken } from "./email-verification";
import { setSessionCookie } from "../../cookies";
import { APIError } from "better-call";
import type { AdditionalUserFieldsInput, User } from "../../types";
import type { BetterAuthOptions } from "@better-auth/core";
import { BASE_ERROR_CODES } from "@better-auth/core/error";
import { isDevelopment } from "@better-auth/core/env";
import { runWithTransaction } from "@better-auth/core/context";
import { parseUserInput } from "../../db";

export const signUpEmail = <O extends BetterAuthOptions>() =>
	createAuthEndpoint(
		"/sign-up/email",
		{
			method: "POST",
			body: z.record(z.string(), z.any()),
			metadata: {
				$Infer: {
					body: {} as {
						name: string;
						email: string;
						password: string;
						image?: string;
						callbackURL?: string;
						rememberMe?: boolean;
					} & AdditionalUserFieldsInput<O>,
				},
				openapi: {
					description: "Sign up a user using email and password",
					requestBody: {
						content: {
							"application/json": {
								schema: {
									type: "object",
									properties: {
										name: {
											type: "string",
											description: "The name of the user",
										},
										email: {
											type: "string",
											description: "The email of the user",
										},
										password: {
											type: "string",
											description: "The password of the user",
										},
										image: {
											type: "string",
											description: "The profile image URL of the user",
										},
										callbackURL: {
											type: "string",
											description:
												"The URL to use for email verification callback",
										},
										rememberMe: {
											type: "boolean",
											description:
												"If this is false, the session will not be remembered. Default is `true`.",
										},
									},
									required: ["name", "email", "password"],
								},
							},
						},
					},
					responses: {
						"200": {
							description: "Successfully created user",
							content: {
								"application/json": {
									schema: {
										type: "object",
										properties: {
											token: {
												type: "string",
												nullable: true,
												description: "Authentication token for the session",
											},
											user: {
												type: "object",
												properties: {
													id: {
														type: "string",
														description: "The unique identifier of the user",
													},
													email: {
														type: "string",
														format: "email",
														description: "The email address of the user",
													},
													name: {
														type: "string",
														description: "The name of the user",
													},
													image: {
														type: "string",
														format: "uri",
														nullable: true,
														description: "The profile image URL of the user",
													},
													emailVerified: {
														type: "boolean",
														description: "Whether the email has been verified",
													},
													createdAt: {
														type: "string",
														format: "date-time",
														description: "When the user was created",
													},
													updatedAt: {
														type: "string",
														format: "date-time",
														description: "When the user was last updated",
													},
												},
												required: [
													"id",
													"email",
													"name",
													"emailVerified",
													"createdAt",
													"updatedAt",
												],
											},
										},
										required: ["user"], // token is optional
									},
								},
							},
						},
						"422": {
							description:
								"Unprocessable Entity. User already exists or failed to create user.",
							content: {
								"application/json": {
									schema: {
										type: "object",
										properties: {
											message: {
												type: "string",
											},
										},
									},
								},
							},
						},
					},
				},
			},
		},
		async (ctx) => {
			return runWithTransaction(ctx.context.adapter, async () => {
				if (
					!ctx.context.options.emailAndPassword?.enabled ||
					ctx.context.options.emailAndPassword?.disableSignUp
				) {
					throw new APIError("BAD_REQUEST", {
						message: "Email and password sign up is not enabled",
					});
				}
				const body = ctx.body as any as User & {
					password: string;
					callbackURL?: string;
					rememberMe?: boolean;
				} & {
					[key: string]: any;
				};
				const {
					name,
					email,
					password,
					image,
					callbackURL,
					rememberMe,
					...rest
				} = body;
				const isValidEmail = z.email().safeParse(email);

				if (!isValidEmail.success) {
					throw new APIError("BAD_REQUEST", {
						message: BASE_ERROR_CODES.INVALID_EMAIL,
					});
				}

				const minPasswordLength = ctx.context.password.config.minPasswordLength;
				if (password.length < minPasswordLength) {
					ctx.context.logger.error("Password is too short");
					throw new APIError("BAD_REQUEST", {
						message: BASE_ERROR_CODES.PASSWORD_TOO_SHORT,
					});
				}

				const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
				if (password.length > maxPasswordLength) {
					ctx.context.logger.error("Password is too long");
					throw new APIError("BAD_REQUEST", {
						message: BASE_ERROR_CODES.PASSWORD_TOO_LONG,
					});
				}
				const dbUser = await ctx.context.internalAdapter.findUserByEmail(email);
				if (dbUser?.user) {
					ctx.context.logger.info(
						`Sign-up attempt for existing email: ${email}`,
					);
					throw new APIError("UNPROCESSABLE_ENTITY", {
						message: BASE_ERROR_CODES.USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL,
					});
				}
				/**
				 * Hash the password
				 *
				 * This is done prior to creating the user
				 * to ensure that any plugin that
				 * may break the hashing should break
				 * before the user is created.
				 */
				const hash = await ctx.context.password.hash(password);
				let createdUser: User;
				try {
					const data = parseUserInput(ctx.context.options, rest, "create");
					createdUser = await ctx.context.internalAdapter.createUser({
						email: email.toLowerCase(),
						name,
						image,
						...data,
						emailVerified: false,
					});
					if (!createdUser) {
						throw new APIError("BAD_REQUEST", {
							message: BASE_ERROR_CODES.FAILED_TO_CREATE_USER,
						});
					}
				} catch (e) {
					if (isDevelopment()) {
						ctx.context.logger.error("Failed to create user", e);
					}
					if (e instanceof APIError) {
						throw e;
					}
					ctx.context.logger?.error("Failed to create user", e);
					throw new APIError("UNPROCESSABLE_ENTITY", {
						message: BASE_ERROR_CODES.FAILED_TO_CREATE_USER,
						details: e,
					});
				}
				if (!createdUser) {
					throw new APIError("UNPROCESSABLE_ENTITY", {
						message: BASE_ERROR_CODES.FAILED_TO_CREATE_USER,
					});
				}
				await ctx.context.internalAdapter.linkAccount({
					userId: createdUser.id,
					providerId: "credential",
					accountId: createdUser.id,
					password: hash,
				});
				if (
					ctx.context.options.emailVerification?.sendOnSignUp ||
					ctx.context.options.emailAndPassword.requireEmailVerification
				) {
					const token = await createEmailVerificationToken(
						ctx.context.secret,
						createdUser.email,
						undefined,
						ctx.context.options.emailVerification?.expiresIn,
					);
					const callbackURL = body.callbackURL
						? encodeURIComponent(body.callbackURL)
						: encodeURIComponent("/");
					const url = `${ctx.context.baseURL}/verify-email?token=${token}&callbackURL=${callbackURL}`;

					const args: Parameters<
						Required<
							Required<BetterAuthOptions>["emailVerification"]
						>["sendVerificationEmail"]
					> = ctx.request
						? [
								{
									user: createdUser,
									url,
									token,
								},
								ctx.request,
							]
						: [
								{
									user: createdUser,
									url,
									token,
								},
							];

					await ctx.context.options.emailVerification?.sendVerificationEmail?.(
						...args,
					);
				}

				if (
					ctx.context.options.emailAndPassword.autoSignIn === false ||
					ctx.context.options.emailAndPassword.requireEmailVerification
				) {
					return ctx.json({
						token: null,
						user: {
							id: createdUser.id,
							email: createdUser.email,
							name: createdUser.name,
							image: createdUser.image,
							emailVerified: createdUser.emailVerified,
							createdAt: createdUser.createdAt,
							updatedAt: createdUser.updatedAt,
						},
					});
				}

				const session = await ctx.context.internalAdapter.createSession(
					createdUser.id,
					rememberMe === false,
				);
				if (!session) {
					throw new APIError("BAD_REQUEST", {
						message: BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION,
					});
				}
				await setSessionCookie(
					ctx,
					{
						session,
						user: createdUser,
					},
					rememberMe === false,
				);
				return ctx.json({
					token: session.token,
					user: {
						id: createdUser.id,
						email: createdUser.email,
						name: createdUser.name,
						image: createdUser.image,
						emailVerified: createdUser.emailVerified,
						createdAt: createdUser.createdAt,
						updatedAt: createdUser.updatedAt,
					},
				});
			});
		},
	);

```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/multi-session/index.ts:
--------------------------------------------------------------------------------

```typescript
import * as z from "zod";
import { APIError, sessionMiddleware } from "../../api";
import {
	createAuthEndpoint,
	createAuthMiddleware,
} from "@better-auth/core/api";
import {
	deleteSessionCookie,
	parseCookies,
	parseSetCookieHeader,
	setSessionCookie,
} from "../../cookies";
import type { BetterAuthPlugin } from "@better-auth/core";
import { defineErrorCodes } from "@better-auth/core/utils";

interface MultiSessionConfig {
	/**
	 * The maximum number of sessions a user can have
	 * at a time
	 * @default 5
	 */
	maximumSessions?: number;
}

const ERROR_CODES = defineErrorCodes({
	INVALID_SESSION_TOKEN: "Invalid session token",
});
export const multiSession = (options?: MultiSessionConfig) => {
	const opts = {
		maximumSessions: 5,
		...options,
	};

	const isMultiSessionCookie = (key: string) => key.includes("_multi-");

	return {
		id: "multi-session",
		endpoints: {
			/**
			 * ### Endpoint
			 *
			 * GET `/multi-session/list-device-sessions`
			 *
			 * ### API Methods
			 *
			 * **server:**
			 * `auth.api.listDeviceSessions`
			 *
			 * **client:**
			 * `authClient.multiSession.listDeviceSessions`
			 *
			 * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/multi-session#api-method-multi-session-list-device-sessions)
			 */
			listDeviceSessions: createAuthEndpoint(
				"/multi-session/list-device-sessions",
				{
					method: "GET",
					requireHeaders: true,
				},
				async (ctx) => {
					const cookieHeader = ctx.headers?.get("cookie");
					if (!cookieHeader) return ctx.json([]);

					const cookies = Object.fromEntries(parseCookies(cookieHeader));

					const sessionTokens = (
						await Promise.all(
							Object.entries(cookies)
								.filter(([key]) => isMultiSessionCookie(key))
								.map(
									async ([key]) =>
										await ctx.getSignedCookie(key, ctx.context.secret),
								),
						)
					).filter((v) => v !== null);

					if (!sessionTokens.length) return ctx.json([]);
					const sessions =
						await ctx.context.internalAdapter.findSessions(sessionTokens);
					const validSessions = sessions.filter(
						(session) => session && session.session.expiresAt > new Date(),
					);
					const uniqueUserSessions = validSessions.reduce(
						(acc, session) => {
							if (!acc.find((s) => s.user.id === session.user.id)) {
								acc.push(session);
							}
							return acc;
						},
						[] as typeof validSessions,
					);
					return ctx.json(uniqueUserSessions);
				},
			),
			/**
			 * ### Endpoint
			 *
			 * POST `/multi-session/set-active`
			 *
			 * ### API Methods
			 *
			 * **server:**
			 * `auth.api.setActiveSession`
			 *
			 * **client:**
			 * `authClient.multiSession.setActive`
			 *
			 * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/multi-session#api-method-multi-session-set-active)
			 */
			setActiveSession: createAuthEndpoint(
				"/multi-session/set-active",
				{
					method: "POST",
					body: z.object({
						sessionToken: z.string().meta({
							description: "The session token to set as active",
						}),
					}),
					requireHeaders: true,
					use: [sessionMiddleware],
					metadata: {
						openapi: {
							description: "Set the active session",
							responses: {
								200: {
									description: "Success",
									content: {
										"application/json": {
											schema: {
												type: "object",
												properties: {
													session: {
														$ref: "#/components/schemas/Session",
													},
												},
											},
										},
									},
								},
							},
						},
					},
				},
				async (ctx) => {
					const sessionToken = ctx.body.sessionToken;
					const multiSessionCookieName = `${
						ctx.context.authCookies.sessionToken.name
					}_multi-${sessionToken.toLowerCase()}`;
					const sessionCookie = await ctx.getSignedCookie(
						multiSessionCookieName,
						ctx.context.secret,
					);
					if (!sessionCookie) {
						throw new APIError("UNAUTHORIZED", {
							message: ERROR_CODES.INVALID_SESSION_TOKEN,
						});
					}
					const session =
						await ctx.context.internalAdapter.findSession(sessionToken);
					if (!session || session.session.expiresAt < new Date()) {
						ctx.setCookie(multiSessionCookieName, "", {
							...ctx.context.authCookies.sessionToken.options,
							maxAge: 0,
						});
						throw new APIError("UNAUTHORIZED", {
							message: ERROR_CODES.INVALID_SESSION_TOKEN,
						});
					}
					await setSessionCookie(ctx, session);
					return ctx.json(session);
				},
			),
			/**
			 * ### Endpoint
			 *
			 * POST `/multi-session/revoke`
			 *
			 * ### API Methods
			 *
			 * **server:**
			 * `auth.api.revokeDeviceSession`
			 *
			 * **client:**
			 * `authClient.multiSession.revoke`
			 *
			 * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/multi-session#api-method-multi-session-revoke)
			 */
			revokeDeviceSession: createAuthEndpoint(
				"/multi-session/revoke",
				{
					method: "POST",
					body: z.object({
						sessionToken: z.string().meta({
							description: "The session token to revoke",
						}),
					}),
					requireHeaders: true,
					use: [sessionMiddleware],
					metadata: {
						openapi: {
							description: "Revoke a device session",
							responses: {
								200: {
									description: "Success",
									content: {
										"application/json": {
											schema: {
												type: "object",
												properties: {
													status: {
														type: "boolean",
													},
												},
											},
										},
									},
								},
							},
						},
					},
				},
				async (ctx) => {
					const sessionToken = ctx.body.sessionToken;
					const multiSessionCookieName = `${
						ctx.context.authCookies.sessionToken.name
					}_multi-${sessionToken.toLowerCase()}`;
					const sessionCookie = await ctx.getSignedCookie(
						multiSessionCookieName,
						ctx.context.secret,
					);
					if (!sessionCookie) {
						throw new APIError("UNAUTHORIZED", {
							message: ERROR_CODES.INVALID_SESSION_TOKEN,
						});
					}

					await ctx.context.internalAdapter.deleteSession(sessionToken);
					ctx.setCookie(multiSessionCookieName, "", {
						...ctx.context.authCookies.sessionToken.options,
						maxAge: 0,
					});
					const isActive = ctx.context.session?.session.token === sessionToken;
					if (!isActive) return ctx.json({ status: true });

					const cookieHeader = ctx.headers?.get("cookie");
					if (cookieHeader) {
						const cookies = Object.fromEntries(parseCookies(cookieHeader));

						const sessionTokens = (
							await Promise.all(
								Object.entries(cookies)
									.filter(([key]) => isMultiSessionCookie(key))
									.map(
										async ([key]) =>
											await ctx.getSignedCookie(key, ctx.context.secret),
									),
							)
						).filter((v): v is string => v !== undefined);
						const internalAdapter = ctx.context.internalAdapter;

						if (sessionTokens.length > 0) {
							const sessions =
								await internalAdapter.findSessions(sessionTokens);
							const validSessions = sessions.filter(
								(session) => session && session.session.expiresAt > new Date(),
							);

							if (validSessions.length > 0) {
								const nextSession = validSessions[0]!;
								await setSessionCookie(ctx, nextSession);
							} else {
								deleteSessionCookie(ctx);
							}
						} else {
							deleteSessionCookie(ctx);
						}
					} else {
						deleteSessionCookie(ctx);
					}
					return ctx.json({
						status: true,
					});
				},
			),
		},
		hooks: {
			after: [
				{
					matcher: () => true,
					handler: createAuthMiddleware(async (ctx) => {
						const cookieString = ctx.context.responseHeaders?.get("set-cookie");
						if (!cookieString) return;
						const setCookies = parseSetCookieHeader(cookieString);
						const sessionCookieConfig = ctx.context.authCookies.sessionToken;
						const sessionToken = ctx.context.newSession?.session.token;
						if (!sessionToken) return;
						const cookies = parseCookies(ctx.headers?.get("cookie") || "");

						const cookieName = `${
							sessionCookieConfig.name
						}_multi-${sessionToken.toLowerCase()}`;

						if (setCookies.get(cookieName) || cookies.get(cookieName)) return;

						const currentMultiSessions =
							Object.keys(Object.fromEntries(cookies)).filter(
								isMultiSessionCookie,
							).length + (cookieString.includes("session_token") ? 1 : 0);

						if (currentMultiSessions >= opts.maximumSessions) {
							return;
						}

						await ctx.setSignedCookie(
							cookieName,
							sessionToken,
							ctx.context.secret,
							sessionCookieConfig.options,
						);
					}),
				},
				{
					matcher: (context) => context.path === "/sign-out",
					handler: createAuthMiddleware(async (ctx) => {
						const cookieHeader = ctx.headers?.get("cookie");
						if (!cookieHeader) return;
						const cookies = Object.fromEntries(parseCookies(cookieHeader));
						const ids = Object.keys(cookies)
							.map((key) => {
								if (isMultiSessionCookie(key)) {
									ctx.setCookie(
										key.toLowerCase().replace("__secure-", "__Secure-"),
										"",
										{
											...ctx.context.authCookies.sessionToken.options,
											maxAge: 0,
										},
									);
									const token = cookies[key]!.split(".")[0]!;
									return token;
								}
								return null;
							})
							.filter((v): v is string => v !== null);
						await ctx.context.internalAdapter.deleteSessions(ids);
					}),
				},
			],
		},
		$ERROR_CODES: ERROR_CODES,
	} satisfies BetterAuthPlugin;
};

```
Page 20/51FirstPrevNextLast