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

# Directory Structure

```
├── .gitattributes
├── .github
│   ├── CODEOWNERS
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   └── feature_request.yml
│   ├── renovate.json5
│   └── workflows
│       ├── ci.yml
│       ├── e2e.yml
│       ├── preview.yml
│       └── release.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .vscode
│   └── settings.json
├── banner-dark.png
├── banner.png
├── biome.json
├── bump.config.ts
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── demo
│   ├── expo-example
│   │   ├── .env.example
│   │   ├── .gitignore
│   │   ├── app.config.ts
│   │   ├── assets
│   │   │   ├── bg-image.jpeg
│   │   │   ├── fonts
│   │   │   │   └── SpaceMono-Regular.ttf
│   │   │   ├── icon.png
│   │   │   └── images
│   │   │       ├── adaptive-icon.png
│   │   │       ├── favicon.png
│   │   │       ├── logo.png
│   │   │       ├── partial-react-logo.png
│   │   │       ├── react-logo.png
│   │   │       ├── [email protected]
│   │   │       ├── [email protected]
│   │   │       └── splash.png
│   │   ├── babel.config.js
│   │   ├── components.json
│   │   ├── expo-env.d.ts
│   │   ├── index.ts
│   │   ├── metro.config.js
│   │   ├── nativewind-env.d.ts
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── app
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── api
│   │   │   │   │   └── auth
│   │   │   │   │       └── [...route]+api.ts
│   │   │   │   ├── dashboard.tsx
│   │   │   │   ├── forget-password.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   └── sign-up.tsx
│   │   │   ├── components
│   │   │   │   ├── icons
│   │   │   │   │   └── google.tsx
│   │   │   │   └── ui
│   │   │   │       ├── avatar.tsx
│   │   │   │       ├── button.tsx
│   │   │   │       ├── card.tsx
│   │   │   │       ├── dialog.tsx
│   │   │   │       ├── input.tsx
│   │   │   │       ├── separator.tsx
│   │   │   │       └── text.tsx
│   │   │   ├── global.css
│   │   │   └── lib
│   │   │       ├── auth-client.ts
│   │   │       ├── auth.ts
│   │   │       ├── icons
│   │   │       │   ├── iconWithClassName.ts
│   │   │       │   └── X.tsx
│   │   │       └── utils.ts
│   │   ├── tailwind.config.js
│   │   └── tsconfig.json
│   └── nextjs
│       ├── .env.example
│       ├── .gitignore
│       ├── app
│       │   ├── (auth)
│       │   │   ├── forget-password
│       │   │   │   └── page.tsx
│       │   │   ├── reset-password
│       │   │   │   └── page.tsx
│       │   │   ├── sign-in
│       │   │   │   ├── loading.tsx
│       │   │   │   └── page.tsx
│       │   │   └── two-factor
│       │   │       ├── otp
│       │   │       │   └── page.tsx
│       │   │       └── page.tsx
│       │   ├── accept-invitation
│       │   │   └── [id]
│       │   │       ├── invitation-error.tsx
│       │   │       └── page.tsx
│       │   ├── admin
│       │   │   └── page.tsx
│       │   ├── api
│       │   │   └── auth
│       │   │       └── [...all]
│       │   │           └── route.ts
│       │   ├── apps
│       │   │   └── register
│       │   │       └── page.tsx
│       │   ├── client-test
│       │   │   └── page.tsx
│       │   ├── dashboard
│       │   │   ├── change-plan.tsx
│       │   │   ├── client.tsx
│       │   │   ├── organization-card.tsx
│       │   │   ├── page.tsx
│       │   │   ├── upgrade-button.tsx
│       │   │   └── user-card.tsx
│       │   ├── device
│       │   │   ├── approve
│       │   │   │   └── page.tsx
│       │   │   ├── denied
│       │   │   │   └── page.tsx
│       │   │   ├── layout.tsx
│       │   │   ├── page.tsx
│       │   │   └── success
│       │   │       └── page.tsx
│       │   ├── favicon.ico
│       │   ├── features.tsx
│       │   ├── fonts
│       │   │   ├── GeistMonoVF.woff
│       │   │   └── GeistVF.woff
│       │   ├── globals.css
│       │   ├── layout.tsx
│       │   ├── oauth
│       │   │   └── authorize
│       │   │       ├── concet-buttons.tsx
│       │   │       └── page.tsx
│       │   ├── page.tsx
│       │   └── pricing
│       │       └── page.tsx
│       ├── components
│       │   ├── account-switch.tsx
│       │   ├── blocks
│       │   │   └── pricing.tsx
│       │   ├── logo.tsx
│       │   ├── one-tap.tsx
│       │   ├── sign-in-btn.tsx
│       │   ├── sign-in.tsx
│       │   ├── sign-up.tsx
│       │   ├── theme-provider.tsx
│       │   ├── theme-toggle.tsx
│       │   ├── tier-labels.tsx
│       │   ├── ui
│       │   │   ├── accordion.tsx
│       │   │   ├── alert-dialog.tsx
│       │   │   ├── alert.tsx
│       │   │   ├── aspect-ratio.tsx
│       │   │   ├── avatar.tsx
│       │   │   ├── badge.tsx
│       │   │   ├── breadcrumb.tsx
│       │   │   ├── button.tsx
│       │   │   ├── calendar.tsx
│       │   │   ├── card.tsx
│       │   │   ├── carousel.tsx
│       │   │   ├── chart.tsx
│       │   │   ├── checkbox.tsx
│       │   │   ├── collapsible.tsx
│       │   │   ├── command.tsx
│       │   │   ├── context-menu.tsx
│       │   │   ├── copy-button.tsx
│       │   │   ├── dialog.tsx
│       │   │   ├── drawer.tsx
│       │   │   ├── dropdown-menu.tsx
│       │   │   ├── form.tsx
│       │   │   ├── hover-card.tsx
│       │   │   ├── input-otp.tsx
│       │   │   ├── input.tsx
│       │   │   ├── label.tsx
│       │   │   ├── menubar.tsx
│       │   │   ├── navigation-menu.tsx
│       │   │   ├── pagination.tsx
│       │   │   ├── password-input.tsx
│       │   │   ├── popover.tsx
│       │   │   ├── progress.tsx
│       │   │   ├── radio-group.tsx
│       │   │   ├── resizable.tsx
│       │   │   ├── scroll-area.tsx
│       │   │   ├── select.tsx
│       │   │   ├── separator.tsx
│       │   │   ├── sheet.tsx
│       │   │   ├── skeleton.tsx
│       │   │   ├── slider.tsx
│       │   │   ├── sonner.tsx
│       │   │   ├── switch.tsx
│       │   │   ├── table.tsx
│       │   │   ├── tabs.tsx
│       │   │   ├── tabs2.tsx
│       │   │   ├── textarea.tsx
│       │   │   ├── toast.tsx
│       │   │   ├── toaster.tsx
│       │   │   ├── toggle-group.tsx
│       │   │   ├── toggle.tsx
│       │   │   └── tooltip.tsx
│       │   └── wrapper.tsx
│       ├── components.json
│       ├── hooks
│       │   └── use-toast.ts
│       ├── lib
│       │   ├── auth-client.ts
│       │   ├── auth-types.ts
│       │   ├── auth.ts
│       │   ├── email
│       │   │   ├── invitation.tsx
│       │   │   ├── resend.ts
│       │   │   └── reset-password.tsx
│       │   ├── metadata.ts
│       │   ├── shared.ts
│       │   └── utils.ts
│       ├── next.config.ts
│       ├── package.json
│       ├── postcss.config.mjs
│       ├── proxy.ts
│       ├── public
│       │   ├── __og.png
│       │   ├── _og.png
│       │   ├── favicon
│       │   │   ├── android-chrome-192x192.png
│       │   │   ├── android-chrome-512x512.png
│       │   │   ├── apple-touch-icon.png
│       │   │   ├── favicon-16x16.png
│       │   │   ├── favicon-32x32.png
│       │   │   ├── favicon.ico
│       │   │   ├── light
│       │   │   │   ├── android-chrome-192x192.png
│       │   │   │   ├── android-chrome-512x512.png
│       │   │   │   ├── apple-touch-icon.png
│       │   │   │   ├── favicon-16x16.png
│       │   │   │   ├── favicon-32x32.png
│       │   │   │   ├── favicon.ico
│       │   │   │   └── site.webmanifest
│       │   │   └── site.webmanifest
│       │   ├── logo.svg
│       │   └── og.png
│       ├── README.md
│       ├── tailwind.config.ts
│       ├── tsconfig.json
│       └── turbo.json
├── docker-compose.yml
├── docs
│   ├── .env.example
│   ├── .gitignore
│   ├── app
│   │   ├── api
│   │   │   ├── ai-chat
│   │   │   │   └── route.ts
│   │   │   ├── analytics
│   │   │   │   ├── conversation
│   │   │   │   │   └── route.ts
│   │   │   │   ├── event
│   │   │   │   │   └── route.ts
│   │   │   │   └── feedback
│   │   │   │       └── route.ts
│   │   │   ├── chat
│   │   │   │   └── route.ts
│   │   │   ├── og
│   │   │   │   └── route.tsx
│   │   │   ├── og-release
│   │   │   │   └── route.tsx
│   │   │   ├── search
│   │   │   │   └── route.ts
│   │   │   └── support
│   │   │       └── route.ts
│   │   ├── blog
│   │   │   ├── _components
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── blog-list.tsx
│   │   │   │   ├── changelog-layout.tsx
│   │   │   │   ├── default-changelog.tsx
│   │   │   │   ├── fmt-dates.tsx
│   │   │   │   ├── icons.tsx
│   │   │   │   ├── stat-field.tsx
│   │   │   │   └── support.tsx
│   │   │   ├── [[...slug]]
│   │   │   │   └── page.tsx
│   │   │   └── layout.tsx
│   │   ├── changelogs
│   │   │   ├── _components
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── changelog-layout.tsx
│   │   │   │   ├── default-changelog.tsx
│   │   │   │   ├── fmt-dates.tsx
│   │   │   │   ├── grid-pattern.tsx
│   │   │   │   ├── icons.tsx
│   │   │   │   └── stat-field.tsx
│   │   │   ├── [[...slug]]
│   │   │   │   └── page.tsx
│   │   │   └── layout.tsx
│   │   ├── community
│   │   │   ├── _components
│   │   │   │   ├── header.tsx
│   │   │   │   └── stats.tsx
│   │   │   └── page.tsx
│   │   ├── docs
│   │   │   ├── [[...slug]]
│   │   │   │   ├── page.client.tsx
│   │   │   │   └── page.tsx
│   │   │   ├── layout.tsx
│   │   │   └── lib
│   │   │       └── get-llm-text.ts
│   │   ├── global.css
│   │   ├── layout.config.tsx
│   │   ├── layout.tsx
│   │   ├── llms.txt
│   │   │   ├── [...slug]
│   │   │   │   └── route.ts
│   │   │   └── route.ts
│   │   ├── not-found.tsx
│   │   ├── page.tsx
│   │   ├── reference
│   │   │   └── route.ts
│   │   ├── sitemap.xml
│   │   ├── static.json
│   │   │   └── route.ts
│   │   └── v1
│   │       ├── _components
│   │       │   └── v1-text.tsx
│   │       ├── bg-line.tsx
│   │       └── page.tsx
│   ├── assets
│   │   ├── Geist.ttf
│   │   └── GeistMono.ttf
│   ├── components
│   │   ├── ai-chat-modal.tsx
│   │   ├── anchor-scroll-fix.tsx
│   │   ├── api-method-tabs.tsx
│   │   ├── api-method.tsx
│   │   ├── banner.tsx
│   │   ├── blocks
│   │   │   └── features.tsx
│   │   ├── builder
│   │   │   ├── beam.tsx
│   │   │   ├── code-tabs
│   │   │   │   ├── code-editor.tsx
│   │   │   │   ├── code-tabs.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   ├── tab-bar.tsx
│   │   │   │   └── theme.ts
│   │   │   ├── index.tsx
│   │   │   ├── sign-in.tsx
│   │   │   ├── sign-up.tsx
│   │   │   ├── social-provider.tsx
│   │   │   ├── store.ts
│   │   │   └── tabs.tsx
│   │   ├── display-techstack.tsx
│   │   ├── divider-text.tsx
│   │   ├── docs
│   │   │   ├── docs.client.tsx
│   │   │   ├── docs.tsx
│   │   │   ├── layout
│   │   │   │   ├── nav.tsx
│   │   │   │   ├── theme-toggle.tsx
│   │   │   │   ├── toc-thumb.tsx
│   │   │   │   └── toc.tsx
│   │   │   ├── page.client.tsx
│   │   │   ├── page.tsx
│   │   │   ├── shared.tsx
│   │   │   └── ui
│   │   │       ├── button.tsx
│   │   │       ├── collapsible.tsx
│   │   │       ├── popover.tsx
│   │   │       └── scroll-area.tsx
│   │   ├── endpoint.tsx
│   │   ├── features.tsx
│   │   ├── floating-ai-search.tsx
│   │   ├── fork-button.tsx
│   │   ├── generate-apple-jwt.tsx
│   │   ├── generate-secret.tsx
│   │   ├── github-stat.tsx
│   │   ├── icons.tsx
│   │   ├── landing
│   │   │   ├── gradient-bg.tsx
│   │   │   ├── grid-pattern.tsx
│   │   │   ├── hero.tsx
│   │   │   ├── section-svg.tsx
│   │   │   ├── section.tsx
│   │   │   ├── spotlight.tsx
│   │   │   └── testimonials.tsx
│   │   ├── logo-context-menu.tsx
│   │   ├── logo.tsx
│   │   ├── markdown-renderer.tsx
│   │   ├── markdown.tsx
│   │   ├── mdx
│   │   │   ├── add-to-cursor.tsx
│   │   │   └── database-tables.tsx
│   │   ├── message-feedback.tsx
│   │   ├── mobile-search-icon.tsx
│   │   ├── nav-bar.tsx
│   │   ├── nav-link.tsx
│   │   ├── nav-mobile.tsx
│   │   ├── promo-card.tsx
│   │   ├── resource-card.tsx
│   │   ├── resource-grid.tsx
│   │   ├── resource-section.tsx
│   │   ├── ripple.tsx
│   │   ├── search-dialog.tsx
│   │   ├── side-bar.tsx
│   │   ├── sidebar-content.tsx
│   │   ├── techstack-icons.tsx
│   │   ├── theme-provider.tsx
│   │   ├── theme-toggler.tsx
│   │   └── ui
│   │       ├── accordion.tsx
│   │       ├── alert-dialog.tsx
│   │       ├── alert.tsx
│   │       ├── aside-link.tsx
│   │       ├── aspect-ratio.tsx
│   │       ├── avatar.tsx
│   │       ├── background-beams.tsx
│   │       ├── background-boxes.tsx
│   │       ├── badge.tsx
│   │       ├── breadcrumb.tsx
│   │       ├── button.tsx
│   │       ├── calendar.tsx
│   │       ├── callout.tsx
│   │       ├── card.tsx
│   │       ├── carousel.tsx
│   │       ├── chart.tsx
│   │       ├── checkbox.tsx
│   │       ├── code-block.tsx
│   │       ├── collapsible.tsx
│   │       ├── command.tsx
│   │       ├── context-menu.tsx
│   │       ├── dialog.tsx
│   │       ├── drawer.tsx
│   │       ├── dropdown-menu.tsx
│   │       ├── dynamic-code-block.tsx
│   │       ├── fade-in.tsx
│   │       ├── form.tsx
│   │       ├── hover-card.tsx
│   │       ├── input-otp.tsx
│   │       ├── input.tsx
│   │       ├── label.tsx
│   │       ├── menubar.tsx
│   │       ├── navigation-menu.tsx
│   │       ├── pagination.tsx
│   │       ├── popover.tsx
│   │       ├── progress.tsx
│   │       ├── radio-group.tsx
│   │       ├── resizable.tsx
│   │       ├── scroll-area.tsx
│   │       ├── select.tsx
│   │       ├── separator.tsx
│   │       ├── sheet.tsx
│   │       ├── sidebar.tsx
│   │       ├── skeleton.tsx
│   │       ├── slider.tsx
│   │       ├── sonner.tsx
│   │       ├── sparkles.tsx
│   │       ├── switch.tsx
│   │       ├── table.tsx
│   │       ├── tabs.tsx
│   │       ├── textarea.tsx
│   │       ├── toggle-group.tsx
│   │       ├── toggle.tsx
│   │       ├── tooltip-docs.tsx
│   │       ├── tooltip.tsx
│   │       └── use-copy-button.tsx
│   ├── components.json
│   ├── content
│   │   ├── blogs
│   │   │   ├── 0-supabase-auth-to-planetscale-migration.mdx
│   │   │   ├── 1-3.mdx
│   │   │   ├── authjs-joins-better-auth.mdx
│   │   │   └── seed-round.mdx
│   │   ├── changelogs
│   │   │   ├── 1-2.mdx
│   │   │   └── 1.0.mdx
│   │   └── docs
│   │       ├── adapters
│   │       │   ├── community-adapters.mdx
│   │       │   ├── drizzle.mdx
│   │       │   ├── mongo.mdx
│   │       │   ├── mssql.mdx
│   │       │   ├── mysql.mdx
│   │       │   ├── other-relational-databases.mdx
│   │       │   ├── postgresql.mdx
│   │       │   ├── prisma.mdx
│   │       │   └── sqlite.mdx
│   │       ├── authentication
│   │       │   ├── apple.mdx
│   │       │   ├── atlassian.mdx
│   │       │   ├── cognito.mdx
│   │       │   ├── discord.mdx
│   │       │   ├── dropbox.mdx
│   │       │   ├── email-password.mdx
│   │       │   ├── facebook.mdx
│   │       │   ├── figma.mdx
│   │       │   ├── github.mdx
│   │       │   ├── gitlab.mdx
│   │       │   ├── google.mdx
│   │       │   ├── huggingface.mdx
│   │       │   ├── kakao.mdx
│   │       │   ├── kick.mdx
│   │       │   ├── line.mdx
│   │       │   ├── linear.mdx
│   │       │   ├── linkedin.mdx
│   │       │   ├── microsoft.mdx
│   │       │   ├── naver.mdx
│   │       │   ├── notion.mdx
│   │       │   ├── other-social-providers.mdx
│   │       │   ├── paypal.mdx
│   │       │   ├── reddit.mdx
│   │       │   ├── roblox.mdx
│   │       │   ├── salesforce.mdx
│   │       │   ├── slack.mdx
│   │       │   ├── spotify.mdx
│   │       │   ├── tiktok.mdx
│   │       │   ├── twitch.mdx
│   │       │   ├── twitter.mdx
│   │       │   ├── vk.mdx
│   │       │   └── zoom.mdx
│   │       ├── basic-usage.mdx
│   │       ├── comparison.mdx
│   │       ├── concepts
│   │       │   ├── api.mdx
│   │       │   ├── cli.mdx
│   │       │   ├── client.mdx
│   │       │   ├── cookies.mdx
│   │       │   ├── database.mdx
│   │       │   ├── email.mdx
│   │       │   ├── hooks.mdx
│   │       │   ├── oauth.mdx
│   │       │   ├── plugins.mdx
│   │       │   ├── rate-limit.mdx
│   │       │   ├── session-management.mdx
│   │       │   ├── typescript.mdx
│   │       │   └── users-accounts.mdx
│   │       ├── examples
│   │       │   ├── astro.mdx
│   │       │   ├── next-js.mdx
│   │       │   ├── nuxt.mdx
│   │       │   ├── remix.mdx
│   │       │   └── svelte-kit.mdx
│   │       ├── guides
│   │       │   ├── auth0-migration-guide.mdx
│   │       │   ├── browser-extension-guide.mdx
│   │       │   ├── clerk-migration-guide.mdx
│   │       │   ├── create-a-db-adapter.mdx
│   │       │   ├── next-auth-migration-guide.mdx
│   │       │   ├── optimizing-for-performance.mdx
│   │       │   ├── saml-sso-with-okta.mdx
│   │       │   ├── supabase-migration-guide.mdx
│   │       │   └── your-first-plugin.mdx
│   │       ├── installation.mdx
│   │       ├── integrations
│   │       │   ├── astro.mdx
│   │       │   ├── convex.mdx
│   │       │   ├── elysia.mdx
│   │       │   ├── expo.mdx
│   │       │   ├── express.mdx
│   │       │   ├── fastify.mdx
│   │       │   ├── hono.mdx
│   │       │   ├── lynx.mdx
│   │       │   ├── nestjs.mdx
│   │       │   ├── next.mdx
│   │       │   ├── nitro.mdx
│   │       │   ├── nuxt.mdx
│   │       │   ├── remix.mdx
│   │       │   ├── solid-start.mdx
│   │       │   ├── svelte-kit.mdx
│   │       │   ├── tanstack.mdx
│   │       │   └── waku.mdx
│   │       ├── introduction.mdx
│   │       ├── meta.json
│   │       ├── plugins
│   │       │   ├── 2fa.mdx
│   │       │   ├── admin.mdx
│   │       │   ├── anonymous.mdx
│   │       │   ├── api-key.mdx
│   │       │   ├── autumn.mdx
│   │       │   ├── bearer.mdx
│   │       │   ├── captcha.mdx
│   │       │   ├── community-plugins.mdx
│   │       │   ├── device-authorization.mdx
│   │       │   ├── dodopayments.mdx
│   │       │   ├── dub.mdx
│   │       │   ├── email-otp.mdx
│   │       │   ├── generic-oauth.mdx
│   │       │   ├── have-i-been-pwned.mdx
│   │       │   ├── jwt.mdx
│   │       │   ├── last-login-method.mdx
│   │       │   ├── magic-link.mdx
│   │       │   ├── mcp.mdx
│   │       │   ├── multi-session.mdx
│   │       │   ├── oauth-proxy.mdx
│   │       │   ├── oidc-provider.mdx
│   │       │   ├── one-tap.mdx
│   │       │   ├── one-time-token.mdx
│   │       │   ├── open-api.mdx
│   │       │   ├── organization.mdx
│   │       │   ├── passkey.mdx
│   │       │   ├── phone-number.mdx
│   │       │   ├── polar.mdx
│   │       │   ├── siwe.mdx
│   │       │   ├── sso.mdx
│   │       │   ├── stripe.mdx
│   │       │   └── username.mdx
│   │       └── reference
│   │           ├── contributing.mdx
│   │           ├── faq.mdx
│   │           ├── options.mdx
│   │           ├── resources.mdx
│   │           ├── security.mdx
│   │           └── telemetry.mdx
│   ├── hooks
│   │   └── use-mobile.ts
│   ├── ignore-build.sh
│   ├── lib
│   │   ├── blog.ts
│   │   ├── chat
│   │   │   └── inkeep-qa-schema.ts
│   │   ├── constants.ts
│   │   ├── export-search-indexes.ts
│   │   ├── inkeep-analytics.ts
│   │   ├── is-active.ts
│   │   ├── metadata.ts
│   │   ├── source.ts
│   │   └── utils.ts
│   ├── next.config.js
│   ├── package.json
│   ├── postcss.config.js
│   ├── proxy.ts
│   ├── public
│   │   ├── avatars
│   │   │   └── beka.jpg
│   │   ├── blogs
│   │   │   ├── authjs-joins.png
│   │   │   ├── seed-round.png
│   │   │   └── supabase-ps.png
│   │   ├── branding
│   │   │   ├── better-auth-brand-assets.zip
│   │   │   ├── better-auth-logo-dark.png
│   │   │   ├── better-auth-logo-dark.svg
│   │   │   ├── better-auth-logo-light.png
│   │   │   ├── better-auth-logo-light.svg
│   │   │   ├── better-auth-logo-wordmark-dark.png
│   │   │   ├── better-auth-logo-wordmark-dark.svg
│   │   │   ├── better-auth-logo-wordmark-light.png
│   │   │   └── better-auth-logo-wordmark-light.svg
│   │   ├── extension-id.png
│   │   ├── favicon
│   │   │   ├── android-chrome-192x192.png
│   │   │   ├── android-chrome-512x512.png
│   │   │   ├── apple-touch-icon.png
│   │   │   ├── favicon-16x16.png
│   │   │   ├── favicon-32x32.png
│   │   │   ├── favicon.ico
│   │   │   ├── light
│   │   │   │   ├── android-chrome-192x192.png
│   │   │   │   ├── android-chrome-512x512.png
│   │   │   │   ├── apple-touch-icon.png
│   │   │   │   ├── favicon-16x16.png
│   │   │   │   ├── favicon-32x32.png
│   │   │   │   ├── favicon.ico
│   │   │   │   └── site.webmanifest
│   │   │   └── site.webmanifest
│   │   ├── images
│   │   │   └── blogs
│   │   │       └── better auth (1).png
│   │   ├── logo.png
│   │   ├── logo.svg
│   │   ├── LogoDark.webp
│   │   ├── LogoLight.webp
│   │   ├── og.png
│   │   ├── open-api-reference.png
│   │   ├── people-say
│   │   │   ├── code-with-antonio.jpg
│   │   │   ├── dagmawi-babi.png
│   │   │   ├── dax.png
│   │   │   ├── dev-ed.png
│   │   │   ├── egoist.png
│   │   │   ├── guillermo-rauch.png
│   │   │   ├── jonathan-wilke.png
│   │   │   ├── josh-tried-coding.jpg
│   │   │   ├── kitze.jpg
│   │   │   ├── lazar-nikolov.png
│   │   │   ├── nizzy.png
│   │   │   ├── omar-mcadam.png
│   │   │   ├── ryan-vogel.jpg
│   │   │   ├── saltyatom.jpg
│   │   │   ├── sebastien-chopin.png
│   │   │   ├── shreyas-mididoddi.png
│   │   │   ├── tech-nerd.png
│   │   │   ├── theo.png
│   │   │   ├── vybhav-bhargav.png
│   │   │   └── xavier-pladevall.jpg
│   │   ├── plus.svg
│   │   ├── release-og
│   │   │   ├── 1-2.png
│   │   │   ├── 1-3.png
│   │   │   └── changelog-og.png
│   │   └── v1-og.png
│   ├── README.md
│   ├── scripts
│   │   ├── endpoint-to-doc
│   │   │   ├── index.ts
│   │   │   ├── input.ts
│   │   │   ├── output.mdx
│   │   │   └── readme.md
│   │   └── sync-orama.ts
│   ├── source.config.ts
│   ├── tailwind.config.js
│   ├── tsconfig.json
│   └── turbo.json
├── e2e
│   ├── integration
│   │   ├── package.json
│   │   ├── playwright.config.ts
│   │   ├── solid-vinxi
│   │   │   ├── .gitignore
│   │   │   ├── app.config.ts
│   │   │   ├── e2e
│   │   │   │   ├── test.spec.ts
│   │   │   │   └── utils.ts
│   │   │   ├── package.json
│   │   │   ├── public
│   │   │   │   └── favicon.ico
│   │   │   ├── src
│   │   │   │   ├── app.tsx
│   │   │   │   ├── entry-client.tsx
│   │   │   │   ├── entry-server.tsx
│   │   │   │   ├── global.d.ts
│   │   │   │   ├── lib
│   │   │   │   │   ├── auth-client.ts
│   │   │   │   │   └── auth.ts
│   │   │   │   └── routes
│   │   │   │       ├── [...404].tsx
│   │   │   │       ├── api
│   │   │   │       │   └── auth
│   │   │   │       │       └── [...all].ts
│   │   │   │       └── index.tsx
│   │   │   └── tsconfig.json
│   │   ├── test-utils
│   │   │   ├── package.json
│   │   │   └── src
│   │   │       └── playwright.ts
│   │   └── vanilla-node
│   │       ├── e2e
│   │       │   ├── app.ts
│   │       │   ├── domain.spec.ts
│   │       │   ├── postgres-js.spec.ts
│   │       │   ├── test.spec.ts
│   │       │   └── utils.ts
│   │       ├── index.html
│   │       ├── package.json
│   │       ├── src
│   │       │   ├── main.ts
│   │       │   └── vite-env.d.ts
│   │       ├── tsconfig.json
│   │       └── vite.config.ts
│   └── smoke
│       ├── package.json
│       ├── test
│       │   ├── bun.spec.ts
│       │   ├── cloudflare.spec.ts
│       │   ├── deno.spec.ts
│       │   ├── fixtures
│       │   │   ├── bun-simple.ts
│       │   │   ├── cloudflare
│       │   │   │   ├── .gitignore
│       │   │   │   ├── drizzle
│       │   │   │   │   ├── 0000_clean_vector.sql
│       │   │   │   │   └── meta
│       │   │   │   │       ├── _journal.json
│       │   │   │   │       └── 0000_snapshot.json
│       │   │   │   ├── drizzle.config.ts
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── auth-schema.ts
│       │   │   │   │   ├── db.ts
│       │   │   │   │   └── index.ts
│       │   │   │   ├── test
│       │   │   │   │   ├── apply-migrations.ts
│       │   │   │   │   ├── env.d.ts
│       │   │   │   │   └── index.test.ts
│       │   │   │   ├── tsconfig.json
│       │   │   │   ├── vitest.config.ts
│       │   │   │   ├── worker-configuration.d.ts
│       │   │   │   └── wrangler.json
│       │   │   ├── deno-simple.ts
│       │   │   ├── tsconfig-decelration
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   └── 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-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
│   ├── 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/components/ui/input-otp.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | "use client";
 2 | 
 3 | import * as React from "react";
 4 | import { OTPInput, OTPInputContext } from "input-otp";
 5 | import { MinusIcon } from "lucide-react";
 6 | 
 7 | import { cn } from "@/lib/utils";
 8 | 
 9 | function InputOTP({
10 | 	className,
11 | 	containerClassName,
12 | 	...props
13 | }: React.ComponentProps<typeof OTPInput> & {
14 | 	containerClassName?: string;
15 | }) {
16 | 	return (
17 | 		<OTPInput
18 | 			data-slot="input-otp"
19 | 			containerClassName={cn(
20 | 				"flex items-center gap-2 has-disabled:opacity-50",
21 | 				containerClassName,
22 | 			)}
23 | 			className={cn("disabled:cursor-not-allowed", className)}
24 | 			{...props}
25 | 		/>
26 | 	);
27 | }
28 | 
29 | function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
30 | 	return (
31 | 		<div
32 | 			data-slot="input-otp-group"
33 | 			className={cn("flex items-center", className)}
34 | 			{...props}
35 | 		/>
36 | 	);
37 | }
38 | 
39 | function InputOTPSlot({
40 | 	index,
41 | 	className,
42 | 	...props
43 | }: React.ComponentProps<"div"> & {
44 | 	index: number;
45 | }) {
46 | 	const inputOTPContext = React.useContext(OTPInputContext);
47 | 	const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
48 | 
49 | 	return (
50 | 		<div
51 | 			data-slot="input-otp-slot"
52 | 			data-active={isActive}
53 | 			className={cn(
54 | 				"border-input data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]",
55 | 				className,
56 | 			)}
57 | 			{...props}
58 | 		>
59 | 			{char}
60 | 			{hasFakeCaret && (
61 | 				<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
62 | 					<div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
63 | 				</div>
64 | 			)}
65 | 		</div>
66 | 	);
67 | }
68 | 
69 | function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
70 | 	return (
71 | 		<div data-slot="input-otp-separator" role="separator" {...props}>
72 | 			<MinusIcon />
73 | 		</div>
74 | 	);
75 | }
76 | 
77 | export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };
78 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/organization/has-permission.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { defaultRoles } from "./access";
 2 | import type { Role } from "../access";
 3 | import * as z from "zod";
 4 | import { APIError } from "../../api";
 5 | import type { OrganizationRole } from "./schema";
 6 | import {
 7 | 	cacheAllRoles,
 8 | 	hasPermissionFn,
 9 | 	type HasPermissionBaseInput,
10 | } from "./permission";
11 | import type { GenericEndpointContext } from "@better-auth/core";
12 | 
13 | export const hasPermission = async (
14 | 	input: {
15 | 		organizationId: string;
16 | 		/**
17 | 		 * If true, will use the in-memory cache of the roles.
18 | 		 * Keep in mind to use this in a stateless mindset, the purpose of this is to avoid unnecessary database calls when running multiple
19 | 		 * hasPermission calls in a row.
20 | 		 *
21 | 		 * @default false
22 | 		 */
23 | 		useMemoryCache?: boolean;
24 | 	} & HasPermissionBaseInput,
25 | 	ctx: GenericEndpointContext,
26 | ) => {
27 | 	let acRoles: {
28 | 		[x: string]: Role<any> | undefined;
29 | 	} = { ...(input.options.roles || defaultRoles) };
30 | 
31 | 	if (
32 | 		ctx &&
33 | 		input.organizationId &&
34 | 		input.options.dynamicAccessControl?.enabled &&
35 | 		input.options.ac &&
36 | 		!input.useMemoryCache
37 | 	) {
38 | 		// Load roles from database
39 | 		const roles = await ctx.context.adapter.findMany<
40 | 			OrganizationRole & { permission: string }
41 | 		>({
42 | 			model: "organizationRole",
43 | 			where: [
44 | 				{
45 | 					field: "organizationId",
46 | 					value: input.organizationId,
47 | 				},
48 | 			],
49 | 		});
50 | 
51 | 		for (const { role, permission: permissionsString } of roles) {
52 | 			// If it's for an existing role, skip as we shouldn't override hard-coded roles.
53 | 			if (role in acRoles) continue;
54 | 
55 | 			const result = z
56 | 				.record(z.string(), z.array(z.string()))
57 | 				.safeParse(JSON.parse(permissionsString));
58 | 
59 | 			if (!result.success) {
60 | 				ctx.context.logger.error(
61 | 					"[hasPermission] Invalid permissions for role " + role,
62 | 					{
63 | 						permissions: JSON.parse(permissionsString),
64 | 					},
65 | 				);
66 | 				throw new APIError("INTERNAL_SERVER_ERROR", {
67 | 					message: "Invalid permissions for role " + role,
68 | 				});
69 | 			}
70 | 
71 | 			acRoles[role] = input.options.ac.newRole(result.data);
72 | 		}
73 | 	}
74 | 
75 | 	if (input.useMemoryCache) {
76 | 		acRoles = cacheAllRoles.get(input.organizationId) || acRoles;
77 | 	}
78 | 	cacheAllRoles.set(input.organizationId, acRoles);
79 | 
80 | 	return hasPermissionFn(input, acRoles);
81 | };
82 | 
```

--------------------------------------------------------------------------------
/docs/app/api/ai-chat/route.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { NextResponse } from "next/server";
 2 | export const maxDuration = 300;
 3 | export async function POST(request: Request) {
 4 | 	try {
 5 | 		const body = await request.json();
 6 | 		const gurubasePayload = {
 7 | 			question: body.question,
 8 | 			stream: body.stream,
 9 | 			external_user_id: body.external_user_id,
10 | 			session_id: body.session_id,
11 | 			fetch_existing: body.fetch_existing || false,
12 | 		};
13 | 		const response = await fetch(
14 | 			`https://api.gurubase.io/api/v1/${process.env.GURUBASE_SLUG}/answer/`,
15 | 			{
16 | 				method: "POST",
17 | 				headers: {
18 | 					"Content-Type": "application/json",
19 | 					"x-api-key": `${process.env.GURUBASE_API_KEY}`,
20 | 				},
21 | 				body: JSON.stringify(gurubasePayload),
22 | 			},
23 | 		);
24 | 		if (!response.ok) {
25 | 			const errorText = await response.text();
26 | 			console.error("Gurubase API error:", response.status, errorText);
27 | 			if (response.status === 400) {
28 | 				return NextResponse.json(
29 | 					{
30 | 						error:
31 | 							"I'm sorry, I couldn't process that question. Please try asking something else about Better-Auth.",
32 | 					},
33 | 					{ status: 200 },
34 | 				);
35 | 			}
36 | 
37 | 			return NextResponse.json(
38 | 				{ error: `External API error: ${response.status} ${errorText}` },
39 | 				{ status: response.status },
40 | 			);
41 | 		}
42 | 		const isStreaming = gurubasePayload.stream === true;
43 | 		if (isStreaming) {
44 | 			const stream = new ReadableStream({
45 | 				start(controller) {
46 | 					const reader = response.body?.getReader();
47 | 					if (!reader) {
48 | 						controller.close();
49 | 						return;
50 | 					}
51 | 
52 | 					function pump(): Promise<void> {
53 | 						return reader!.read().then(({ done, value }) => {
54 | 							if (done) {
55 | 								controller.close();
56 | 								return;
57 | 							}
58 | 							controller.enqueue(value);
59 | 							return pump();
60 | 						});
61 | 					}
62 | 
63 | 					return pump();
64 | 				},
65 | 			});
66 | 
67 | 			return new NextResponse(stream, {
68 | 				headers: {
69 | 					"Content-Type": "text/plain; charset=utf-8",
70 | 					"Cache-Control": "no-cache",
71 | 					Connection: "keep-alive",
72 | 				},
73 | 			});
74 | 		} else {
75 | 			const data = await response.json();
76 | 			return NextResponse.json(data);
77 | 		}
78 | 	} catch (error) {
79 | 		return NextResponse.json(
80 | 			{
81 | 				error: `Proxy error: ${error instanceof Error ? error.message : "Unknown error"}`,
82 | 			},
83 | 			{ status: 500 },
84 | 		);
85 | 	}
86 | }
87 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/adapters/mysql.mdx:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: MySQL
 3 | description: Integrate Better Auth with MySQL.
 4 | ---
 5 | 
 6 | MySQL is a popular open-source relational database management system (RDBMS) that is widely used for building web applications and other types of software. It provides a flexible and scalable database solution that allows for efficient storage and retrieval of data.
 7 | Read more here: [MySQL](https://www.mysql.com/).
 8 | 
 9 | ## Example Usage
10 | 
11 | Make sure you have MySQL installed and configured.
12 | Then, you can connect it straight into Better Auth.
13 | 
14 | ```ts title="auth.ts"
15 | import { betterAuth } from "better-auth";
16 | import { createPool } from "mysql2/promise";
17 | 
18 | export const auth = betterAuth({
19 |   database: createPool({
20 |     host: "localhost",
21 |     user: "root",
22 |     password: "password",
23 |     database: "database",
24 |     timezone: "Z", // Important to ensure consistent timezone values
25 |   }),
26 | });
27 | ```
28 | 
29 | <Callout>
30 |   For more information, read Kysely's documentation to the
31 |   [MySQLDialect](https://kysely-org.github.io/kysely-apidoc/classes/MysqlDialect.html).
32 | </Callout>
33 | 
34 | ## Schema generation & migration
35 | 
36 | The [Better Auth CLI](/docs/concepts/cli) allows you to generate or migrate
37 | your database schema based on your Better Auth configuration and plugins.
38 | 
39 | <table>
40 |   <thead>
41 |     <tr className="border-b">
42 |       <th>
43 |         <p className="font-bold text-[16px] mb-1">MySQL Schema Generation</p>
44 |       </th>
45 |       <th>
46 |         <p className="font-bold text-[16px] mb-1">MySQL Schema Migration</p>
47 |       </th>
48 |     </tr>
49 |   </thead>
50 |   <tbody>
51 |     <tr className="h-10">
52 |       <td>✅ Supported</td>
53 |       <td>✅ Supported</td>
54 |     </tr>
55 |   </tbody>
56 | </table>
57 | 
58 | ```bash title="Schema Generation"
59 | npx @better-auth/cli@latest generate
60 | ```
61 | 
62 | ```bash title="Schema Migration"
63 | npx @better-auth/cli@latest migrate
64 | ```
65 | 
66 | ## Additional Information
67 | 
68 | MySQL is supported under the hood via the [Kysely](https://kysely.dev/) adapter, any database supported by Kysely would also be supported. (<Link href="/docs/adapters/other-relational-databases">Read more here</Link>)
69 | 
70 | If you're looking for performance improvements or tips, take a look at our guide to <Link href="/docs/guides/optimizing-for-performance">performance optimizations</Link>.
71 | 
```

--------------------------------------------------------------------------------
/packages/cli/test/__snapshots__/auth-schema-pg-enum.txt:
--------------------------------------------------------------------------------

```
 1 | import { pgTable, text, timestamp, boolean, pgEnum } from "drizzle-orm/pg-core";
 2 | 
 3 | export const user = pgTable("user", {
 4 |   id: text("id").primaryKey(),
 5 |   name: text("name").notNull(),
 6 |   email: text("email").notNull().unique(),
 7 |   emailVerified: boolean("email_verified").default(false).notNull(),
 8 |   image: text("image"),
 9 |   createdAt: timestamp("created_at").defaultNow().notNull(),
10 |   updatedAt: timestamp("updated_at")
11 |     .defaultNow()
12 |     .$onUpdate(() => /* @__PURE__ */ new Date())
13 |     .notNull(),
14 |   role: pgEnum("role", ["admin", "user", "guest"]).notNull(),
15 | });
16 | 
17 | export const session = pgTable("session", {
18 |   id: text("id").primaryKey(),
19 |   expiresAt: timestamp("expires_at").notNull(),
20 |   token: text("token").notNull().unique(),
21 |   createdAt: timestamp("created_at").defaultNow().notNull(),
22 |   updatedAt: timestamp("updated_at")
23 |     .$onUpdate(() => /* @__PURE__ */ new Date())
24 |     .notNull(),
25 |   ipAddress: text("ip_address"),
26 |   userAgent: text("user_agent"),
27 |   userId: text("user_id")
28 |     .notNull()
29 |     .references(() => user.id, { onDelete: "cascade" }),
30 | });
31 | 
32 | export const account = pgTable("account", {
33 |   id: text("id").primaryKey(),
34 |   accountId: text("account_id").notNull(),
35 |   providerId: text("provider_id").notNull(),
36 |   userId: text("user_id")
37 |     .notNull()
38 |     .references(() => user.id, { onDelete: "cascade" }),
39 |   accessToken: text("access_token"),
40 |   refreshToken: text("refresh_token"),
41 |   idToken: text("id_token"),
42 |   accessTokenExpiresAt: timestamp("access_token_expires_at"),
43 |   refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
44 |   scope: text("scope"),
45 |   password: text("password"),
46 |   createdAt: timestamp("created_at").defaultNow().notNull(),
47 |   updatedAt: timestamp("updated_at")
48 |     .$onUpdate(() => /* @__PURE__ */ new Date())
49 |     .notNull(),
50 | });
51 | 
52 | export const verification = pgTable("verification", {
53 |   id: text("id").primaryKey(),
54 |   identifier: text("identifier").notNull(),
55 |   value: text("value").notNull(),
56 |   expiresAt: timestamp("expires_at").notNull(),
57 |   createdAt: timestamp("created_at").defaultNow().notNull(),
58 |   updatedAt: timestamp("updated_at")
59 |     .defaultNow()
60 |     .$onUpdate(() => /* @__PURE__ */ new Date())
61 |     .notNull(),
62 | });
63 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/lib/email/reset-password.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 | 	Body,
 3 | 	Button,
 4 | 	Container,
 5 | 	Head,
 6 | 	Heading,
 7 | 	Hr,
 8 | 	Html,
 9 | 	Link,
10 | 	Preview,
11 | 	Text,
12 | 	Tailwind,
13 | 	Section,
14 | } from "@react-email/components";
15 | 
16 | interface BetterAuthResetPasswordEmailProps {
17 | 	username?: string;
18 | 	resetLink?: string;
19 | }
20 | 
21 | export const ResetPasswordEmail = ({
22 | 	username,
23 | 	resetLink,
24 | }: BetterAuthResetPasswordEmailProps) => {
25 | 	const previewText = `Reset your Better Auth password`;
26 | 	return (
27 | 		<Html>
28 | 			<Head />
29 | 			<Preview>{previewText}</Preview>
30 | 			<Tailwind>
31 | 				<Body className="bg-white my-auto mx-auto font-sans px-2">
32 | 					<Container className="border border-solid border-[#eaeaea] rounded my-[40px] mx-auto p-[20px] max-w-[465px]">
33 | 						<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
34 | 							Reset your <strong>Better Auth</strong> password
35 | 						</Heading>
36 | 						<Text className="text-black text-[14px] leading-[24px]">
37 | 							Hello {username},
38 | 						</Text>
39 | 						<Text className="text-black text-[14px] leading-[24px]">
40 | 							We received a request to reset your password for your Better Auth
41 | 							account. If you didn't make this request, you can safely ignore
42 | 							this email.
43 | 						</Text>
44 | 						<Section className="text-center mt-[32px] mb-[32px]">
45 | 							<Button
46 | 								className="bg-[#000000] rounded text-white text-[12px] font-semibold no-underline text-center px-5 py-3"
47 | 								href={resetLink}
48 | 							>
49 | 								Reset Password
50 | 							</Button>
51 | 						</Section>
52 | 						<Text className="text-black text-[14px] leading-[24px]">
53 | 							Or copy and paste this URL into your browser:{" "}
54 | 							<Link href={resetLink} className="text-blue-600 no-underline">
55 | 								{resetLink}
56 | 							</Link>
57 | 						</Text>
58 | 						<Hr className="border border-solid border-[#eaeaea] my-[26px] mx-0 w-full" />
59 | 						<Text className="text-[#666666] text-[12px] leading-[24px]">
60 | 							If you didn't request a password reset, please ignore this email
61 | 							or contact support if you have concerns.
62 | 						</Text>
63 | 					</Container>
64 | 				</Body>
65 | 			</Tailwind>
66 | 		</Html>
67 | 	);
68 | };
69 | 
70 | export function reactResetPasswordEmail(
71 | 	props: BetterAuthResetPasswordEmailProps,
72 | ) {
73 | 	console.log(props);
74 | 	return <ResetPasswordEmail {...props} />;
75 | }
76 | 
```

--------------------------------------------------------------------------------
/docs/content/changelogs/1.0.mdx:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: v1.0
 3 | description: Built-in CLI for managing your project.
 4 | date: 2021-10-01
 5 | ---
 6 | 
 7 | Version update
 8 | 
 9 | Better Auth comes with a built-in CLI to help you manage the database schema needed for both core functionality and plugins.
10 | 
11 | ## Generate
12 | 
13 | The `generate` command creates the schema required by Better Auth. If you're using a database adapter like Prisma or Drizzle, this command will generate the right schema for your ORM. If you're using the built-in Kysely adapter, it will generate an SQL file you can run directly on your database.
14 | 
15 | ```bash title="Terminal"
16 | npx @better-auth/cli@latest generate
17 | ```
18 | 
19 | ### Options
20 | 
21 | - `--output` - Where to save the generated schema. For Prisma, it will be saved in prisma/schema.prisma. For Drizzle, it goes to schema.ts in your project root. For Kysely, it’s an SQL file saved as schema.sql in your project root.
22 | - `--config` - The path to your Better Auth config file. By default, the CLI will search for a better-auth.ts file in **./**, **./utils**, **./lib**, or any of these directories under `src` directory.
23 | - `--y` - Skip the confirmation prompt and generate the schema directly.
24 | 
25 | ## Migrate
26 | 
27 | The migrate command applies the Better Auth schema directly to your database. This is available if you’re using the built-in Kysely adapter.
28 | 
29 | ```bash title="Terminal"
30 | npx @better-auth/cli@latest migrate
31 | ```
32 | 
33 | ### Options
34 | 
35 | - `--config` - The path to your Better Auth config file. By default, the CLI will search for a better-auth.ts file in **./**, **./utils**, **./lib**, or any of these directories under `src` directory.
36 | - `--y` - Skip the confirmation prompt and apply the schema directly.
37 | 
38 | ## Common Issues
39 | 
40 | **Error: Cannot find module X**
41 | 
42 | If you see this error, it means the CLI can’t resolve imported modules in your Better Auth config file. We're working on a fix for many of these issues, but in the meantime, you can try the following:
43 | 
44 | - Remove any import aliases in your config file and use relative paths instead. After running the CLI, you can revert to using aliases.
45 | 
46 | ## Secret
47 | 
48 | The CLI also provides a way to generate a secret key for your Better Auth instance.
49 | 
50 | ```bash title="Terminal"
51 | npx @better-auth/cli@latest secret
52 | ```
53 | 
```

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

```typescript
 1 | import { describe, expectTypeOf } from "vitest";
 2 | import { getTestInstance } from "../test-utils/test-instance";
 3 | import { organization, twoFactor } from "../plugins";
 4 | 
 5 | describe("general types", async (it) => {
 6 | 	it("should infer base session", async () => {
 7 | 		const { auth } = await getTestInstance();
 8 | 		type Session = typeof auth.$Infer.Session;
 9 | 		expectTypeOf<Session>().toEqualTypeOf<{
10 | 			session: {
11 | 				id: string;
12 | 				createdAt: Date;
13 | 				updatedAt: Date;
14 | 				userId: string;
15 | 				expiresAt: Date;
16 | 				token: string;
17 | 				ipAddress?: string | null | undefined;
18 | 				userAgent?: string | null | undefined;
19 | 			};
20 | 			user: {
21 | 				id: string;
22 | 				createdAt: Date;
23 | 				updatedAt: Date;
24 | 				email: string;
25 | 				emailVerified: boolean;
26 | 				name: string;
27 | 				image?: string | null | undefined;
28 | 			};
29 | 		}>();
30 | 	});
31 | 
32 | 	it("should infer additional fields from plugins", async () => {
33 | 		const { auth } = await getTestInstance({
34 | 			plugins: [twoFactor(), organization()],
35 | 		});
36 | 		expectTypeOf<typeof auth.$Infer.Session.user>().toEqualTypeOf<{
37 | 			id: string;
38 | 			email: string;
39 | 			emailVerified: boolean;
40 | 			name: string;
41 | 			image?: string | undefined | null;
42 | 			createdAt: Date;
43 | 			updatedAt: Date;
44 | 			twoFactorEnabled: boolean | undefined | null;
45 | 		}>();
46 | 
47 | 		expectTypeOf<typeof auth.$Infer.Session.session>().toEqualTypeOf<{
48 | 			id: string;
49 | 			userId: string;
50 | 			expiresAt: Date;
51 | 			createdAt: Date;
52 | 			updatedAt: Date;
53 | 			token: string;
54 | 			ipAddress?: string | undefined | null;
55 | 			userAgent?: string | undefined | null;
56 | 			activeOrganizationId?: string | undefined | null;
57 | 		}>();
58 | 	});
59 | 
60 | 	it("should infer the same types for empty plugins and no plugins", async () => {
61 | 		const { auth: authWithEmptyPlugins } = await getTestInstance({
62 | 			plugins: [],
63 | 			secret: "test-secret",
64 | 			emailAndPassword: {
65 | 				enabled: true,
66 | 			},
67 | 		});
68 | 
69 | 		const { auth: authWithoutPlugins } = await getTestInstance({
70 | 			secret: "test-secret",
71 | 			emailAndPassword: {
72 | 				enabled: true,
73 | 			},
74 | 		});
75 | 
76 | 		type SessionWithEmptyPlugins = typeof authWithEmptyPlugins.$Infer;
77 | 		type SessionWithoutPlugins = typeof authWithoutPlugins.$Infer;
78 | 
79 | 		expectTypeOf<SessionWithEmptyPlugins>().toEqualTypeOf<SessionWithoutPlugins>();
80 | 	});
81 | });
82 | 
```

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

```markdown
 1 | ---
 2 | title: TanStack Start Integration
 3 | description: Integrate Better Auth with TanStack Start.
 4 | ---
 5 | 
 6 | This integration guide is assuming you are using TanStack Start.
 7 | 
 8 | Before you start, make sure you have a Better Auth instance configured. If you haven't done that yet, check out the [installation](/docs/installation).
 9 | 
10 | ### Mount the handler
11 | 
12 | We need to mount the handler to a TanStack API endpoint/Server Route.
13 | Create a new file: `/src/routes/api/auth/$.ts`
14 | 
15 | ```ts title="src/routes/api/auth/$.ts"
16 | import { auth } from '@/lib/auth'
17 | import { createFileRoute } from '@tanstack/react-router'
18 | 
19 | export const Route = createFileRoute('/api/auth/$')({
20 |   server: {
21 |     handlers: {
22 |       GET: ({ request }) => {
23 |         return auth.handler(request)
24 |       },
25 |       POST: ({ request }) => {
26 |         return auth.handler(request)
27 |       },
28 |     },
29 |   },
30 | })
31 | ```
32 | 
33 | If you haven't created your server route handler yet, you can do so by creating a file: `/src/server.ts`
34 | 
35 | ```ts title="src/server.ts"
36 | import {
37 |   createStartHandler,
38 |   defaultStreamHandler,
39 | } from '@tanstack/react-start/server'
40 | import { createRouter } from './router'
41 | 
42 | export default createStartHandler({
43 |   createRouter,
44 | })(defaultStreamHandler)
45 | ```
46 | 
47 | ### Usage tips
48 | 
49 | - We recommend using the client SDK or `authClient` to handle authentication, rather than server actions with `auth.api`.
50 | - When you call functions that need to set cookies (like `signInEmail` or `signUpEmail`), you'll need to handle cookie setting for TanStack Start. Better Auth provides a `reactStartCookies` plugin to automatically handle this for you.
51 | 
52 | ```ts title="src/lib/auth.ts"
53 | import { betterAuth } from "better-auth";
54 | import { reactStartCookies } from "better-auth/react-start";
55 | 
56 | export const auth = betterAuth({
57 |     //...your config
58 |     plugins: [reactStartCookies()] // make sure this is the last plugin in the array
59 | })
60 | ```
61 | 
62 | Now, when you call functions that set cookies, they will be automatically set using TanStack Start's cookie handling system.
63 | 
64 | ```ts
65 | import { auth } from "@/lib/auth"
66 | 
67 | const signIn = async () => {
68 |     await auth.api.signInEmail({
69 |         body: {
70 |             email: "[email protected]",
71 |             password: "password",
72 |         }
73 |     })
74 | }
75 | ```
76 | 
```

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

```typescript
 1 | "use client";
 2 | 
 3 | import { useState } from "react";
 4 | import { Highlight } from "prism-react-renderer";
 5 | import { Check, Copy } from "lucide-react";
 6 | import { Button } from "@/components/ui/button";
 7 | import theme from "./theme";
 8 | 
 9 | interface CodeEditorProps {
10 | 	code: string;
11 | 	language: string;
12 | }
13 | 
14 | export function CodeEditor({ code, language }: CodeEditorProps) {
15 | 	const [isCopied, setIsCopied] = useState(false);
16 | 
17 | 	const copyToClipboard = async () => {
18 | 		try {
19 | 			await navigator.clipboard.writeText(code);
20 | 			setIsCopied(true);
21 | 			setTimeout(() => setIsCopied(false), 2000);
22 | 		} catch (err) {
23 | 			console.error("Failed to copy text: ", err);
24 | 		}
25 | 	};
26 | 
27 | 	return (
28 | 		<div className="relative">
29 | 			<div className="dark:bg-bg-white rounded-md overflow-hidden">
30 | 				<Highlight theme={theme} code={code} language={language}>
31 | 					{({ className, style, tokens, getLineProps, getTokenProps }) => (
32 | 						<div className="overflow-auto max-h-[400px]">
33 | 							<pre
34 | 								className={`${className} relative text-sm p-4 w-full min-w-fit rounded-md`}
35 | 								style={style}
36 | 							>
37 | 								{tokens.map((line, i) => {
38 | 									const lineProps = getLineProps({ line, key: i });
39 | 									return (
40 | 										<div
41 | 											key={i}
42 | 											className={lineProps.className}
43 | 											style={lineProps.style}
44 | 										>
45 | 											<span className="inline-block w-8 pr-2 text-right mr-4 text-gray-500 select-none sticky left-0 bg-black">
46 | 												{i + 1}
47 | 											</span>
48 | 											{line.map((token, key) => {
49 | 												const tokenProps = getTokenProps({ token, key });
50 | 												return (
51 | 													<span
52 | 														key={key}
53 | 														className={tokenProps.className}
54 | 														style={tokenProps.style}
55 | 													>
56 | 														{tokenProps.children}
57 | 													</span>
58 | 												);
59 | 											})}
60 | 										</div>
61 | 									);
62 | 								})}
63 | 							</pre>
64 | 						</div>
65 | 					)}
66 | 				</Highlight>
67 | 				<Button
68 | 					variant="outline"
69 | 					size="icon"
70 | 					className="absolute top-2 right-2"
71 | 					onClick={copyToClipboard}
72 | 					aria-label="Copy code"
73 | 				>
74 | 					{isCopied ? (
75 | 						<Check className="h-4 w-4" />
76 | 					) : (
77 | 						<Copy className="h-4 w-4" />
78 | 					)}
79 | 				</Button>
80 | 			</div>
81 | 		</div>
82 | 	);
83 | }
84 | 
```

--------------------------------------------------------------------------------
/docs/app/api/chat/route.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ProvideLinksToolSchema } from "@/lib/chat/inkeep-qa-schema";
 2 | import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
 3 | import { convertToModelMessages, streamText } from "ai";
 4 | import {
 5 | 	logConversationToAnalytics,
 6 | 	type InkeepMessage,
 7 | } from "@/lib/inkeep-analytics";
 8 | 
 9 | export const runtime = "edge";
10 | 
11 | const openai = createOpenAICompatible({
12 | 	name: "inkeep",
13 | 	apiKey: process.env.INKEEP_API_KEY,
14 | 	baseURL: "https://api.inkeep.com/v1",
15 | });
16 | 
17 | export async function POST(req: Request) {
18 | 	const reqJson = await req.json();
19 | 
20 | 	const result = streamText({
21 | 		model: openai("inkeep-qa-sonnet-4"),
22 | 		tools: {
23 | 			provideLinks: {
24 | 				inputSchema: ProvideLinksToolSchema,
25 | 			},
26 | 		},
27 | 		messages: convertToModelMessages(reqJson.messages, {
28 | 			ignoreIncompleteToolCalls: true,
29 | 		}),
30 | 		toolChoice: "auto",
31 | 		onFinish: async (event) => {
32 | 			try {
33 | 				const extractMessageContent = (msg: any): string => {
34 | 					if (typeof msg.content === "string") {
35 | 						return msg.content;
36 | 					}
37 | 
38 | 					if (msg.parts && Array.isArray(msg.parts)) {
39 | 						return msg.parts
40 | 							.filter((part: any) => part.type === "text")
41 | 							.map((part: any) => part.text)
42 | 							.join("");
43 | 					}
44 | 
45 | 					if (msg.text) {
46 | 						return msg.text;
47 | 					}
48 | 
49 | 					return "";
50 | 				};
51 | 
52 | 				const assistantMessageId =
53 | 					event.response.id ||
54 | 					`assistant_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
55 | 
56 | 				const inkeepMessages: InkeepMessage[] = [
57 | 					...reqJson.messages
58 | 						.map((msg: any) => ({
59 | 							id:
60 | 								msg.id ||
61 | 								`msg_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
62 | 							role: msg.role,
63 | 							content: extractMessageContent(msg),
64 | 						}))
65 | 						.filter((msg: any) => msg.content.trim() !== ""),
66 | 					{
67 | 						id: assistantMessageId,
68 | 						role: "assistant" as const,
69 | 						content: event.text,
70 | 					},
71 | 				];
72 | 
73 | 				await logConversationToAnalytics({
74 | 					type: "openai",
75 | 					messages: inkeepMessages,
76 | 					properties: {
77 | 						source: "better-auth-docs",
78 | 						timestamp: new Date().toISOString(),
79 | 						model: "inkeep-qa-sonnet-4",
80 | 					},
81 | 				});
82 | 			} catch (error) {
83 | 				// Don't fail the request if analytics logging fails
84 | 			}
85 | 		},
86 | 	});
87 | 
88 | 	return result.toUIMessageStreamResponse();
89 | }
90 | 
```

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

```typescript
  1 | import * as React from "react";
  2 | import { Slot } from "@radix-ui/react-slot";
  3 | import { ChevronRight, MoreHorizontal } from "lucide-react";
  4 | 
  5 | import { cn } from "@/lib/utils";
  6 | 
  7 | function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
  8 | 	return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
  9 | }
 10 | 
 11 | function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
 12 | 	return (
 13 | 		<ol
 14 | 			data-slot="breadcrumb-list"
 15 | 			className={cn(
 16 | 				"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
 17 | 				className,
 18 | 			)}
 19 | 			{...props}
 20 | 		/>
 21 | 	);
 22 | }
 23 | 
 24 | function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
 25 | 	return (
 26 | 		<li
 27 | 			data-slot="breadcrumb-item"
 28 | 			className={cn("inline-flex items-center gap-1.5", className)}
 29 | 			{...props}
 30 | 		/>
 31 | 	);
 32 | }
 33 | 
 34 | function BreadcrumbLink({
 35 | 	asChild,
 36 | 	className,
 37 | 	...props
 38 | }: React.ComponentProps<"a"> & {
 39 | 	asChild?: boolean;
 40 | }) {
 41 | 	const Comp = asChild ? Slot : "a";
 42 | 
 43 | 	return (
 44 | 		<Comp
 45 | 			data-slot="breadcrumb-link"
 46 | 			className={cn("hover:text-foreground transition-colors", className)}
 47 | 			{...props}
 48 | 		/>
 49 | 	);
 50 | }
 51 | 
 52 | function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
 53 | 	return (
 54 | 		<span
 55 | 			data-slot="breadcrumb-page"
 56 | 			role="link"
 57 | 			aria-disabled="true"
 58 | 			aria-current="page"
 59 | 			className={cn("text-foreground font-normal", className)}
 60 | 			{...props}
 61 | 		/>
 62 | 	);
 63 | }
 64 | 
 65 | function BreadcrumbSeparator({
 66 | 	children,
 67 | 	className,
 68 | 	...props
 69 | }: React.ComponentProps<"li">) {
 70 | 	return (
 71 | 		<li
 72 | 			data-slot="breadcrumb-separator"
 73 | 			role="presentation"
 74 | 			aria-hidden="true"
 75 | 			className={cn("[&>svg]:size-3.5", className)}
 76 | 			{...props}
 77 | 		>
 78 | 			{children ?? <ChevronRight />}
 79 | 		</li>
 80 | 	);
 81 | }
 82 | 
 83 | function BreadcrumbEllipsis({
 84 | 	className,
 85 | 	...props
 86 | }: React.ComponentProps<"span">) {
 87 | 	return (
 88 | 		<span
 89 | 			data-slot="breadcrumb-ellipsis"
 90 | 			role="presentation"
 91 | 			aria-hidden="true"
 92 | 			className={cn("flex size-9 items-center justify-center", className)}
 93 | 			{...props}
 94 | 		>
 95 | 			<MoreHorizontal className="size-4" />
 96 | 			<span className="sr-only">More</span>
 97 | 		</span>
 98 | 	);
 99 | }
100 | 
101 | export {
102 | 	Breadcrumb,
103 | 	BreadcrumbList,
104 | 	BreadcrumbItem,
105 | 	BreadcrumbLink,
106 | 	BreadcrumbPage,
107 | 	BreadcrumbSeparator,
108 | 	BreadcrumbEllipsis,
109 | };
110 | 
```

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

```markdown
 1 | ---
 2 | title: Elysia Integration
 3 | description: Integrate Better Auth with Elysia.
 4 | ---
 5 | 
 6 | This integration guide is assuming you are using Elysia with bun server.
 7 | 
 8 | Before you start, make sure you have a Better Auth instance configured. If you haven't done that yet, check out the [installation](/docs/installation).
 9 | 
10 | ### Mount the handler
11 | 
12 | We need to mount the handler to Elysia endpoint.
13 | 
14 | ```ts
15 | import { Elysia } from "elysia";
16 | import { auth } from "./auth";
17 | 
18 | const app = new Elysia().mount(auth.handler).listen(3000);
19 | 
20 | console.log(
21 |   `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
22 | );
23 | ```
24 | 
25 | ### CORS
26 | 
27 | To configure cors, you can use the `cors` plugin from `@elysiajs/cors`.
28 | 
29 | ```ts
30 | import { Elysia } from "elysia";
31 | import { cors } from "@elysiajs/cors";
32 | 
33 | import { auth } from "./auth";
34 | 
35 | const app = new Elysia()
36 |   .use(
37 |     cors({
38 |       origin: "http://localhost:3001",
39 |       methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
40 |       credentials: true,
41 |       allowedHeaders: ["Content-Type", "Authorization"],
42 |     }),
43 |   )
44 |   .mount(auth.handler)
45 |   .listen(3000);
46 | 
47 | console.log(
48 |   `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
49 | );
50 | ```
51 | 
52 | ### Macro
53 | 
54 | You can use [macro](https://elysiajs.com/patterns/macro.html#macro) with [resolve](https://elysiajs.com/essential/handler.html#resolve) to provide session and user information before pass to view.
55 | 
56 | ```ts
57 | import { Elysia } from "elysia";
58 | import { auth } from "./auth";
59 | 
60 | // user middleware (compute user and session and pass to routes)
61 | const betterAuth = new Elysia({ name: "better-auth" })
62 |   .mount(auth.handler)
63 |   .macro({
64 |     auth: {
65 |       async resolve({ status, request: { headers } }) {
66 |         const session = await auth.api.getSession({
67 |           headers,
68 |         });
69 | 
70 |         if (!session) return status(401);
71 | 
72 |         return {
73 |           user: session.user,
74 |           session: session.session,
75 |         };
76 |       },
77 |     },
78 |   });
79 | 
80 | const app = new Elysia()
81 |   .use(betterAuth)
82 |   .get("/user", ({ user }) => user, {
83 |     auth: true,
84 |   })
85 |   .listen(3000);
86 | 
87 | console.log(
88 |   `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
89 | );
90 | ```
91 | 
92 | This will allow you to access the `user` and `session` object in all of your routes.
93 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/app/features.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | "use client";
 2 | import React from "react";
 3 | 
 4 | import { AnimatePresence, motion } from "framer-motion";
 5 | import { Logo } from "@/components/logo";
 6 | 
 7 | export function Features() {
 8 | 	return (
 9 | 		<>
10 | 			<div className="flex flex-col lg:flex-row bg-white dark:bg-black w-full gap-4 mx-auto px-8">
11 | 				<Card title="Better Auth" icon={<Logo className=" w-44" />}></Card>
12 | 			</div>
13 | 		</>
14 | 	);
15 | }
16 | 
17 | const Card = ({
18 | 	title,
19 | 	icon,
20 | 	children,
21 | }: {
22 | 	title: string;
23 | 	icon: React.ReactNode;
24 | 	children?: React.ReactNode;
25 | }) => {
26 | 	const [hovered, setHovered] = React.useState(false);
27 | 	return (
28 | 		<div
29 | 			onMouseEnter={() => setHovered(true)}
30 | 			onMouseLeave={() => setHovered(false)}
31 | 			className="border border-black/20 group/canvas-card flex items-center justify-center dark:border-white/20  max-w-sm w-full mx-auto p-4 relative h-72"
32 | 		>
33 | 			<Icon className="absolute h-6 w-6 -top-3 -left-3 dark:text-white text-black" />
34 | 			<Icon className="absolute h-6 w-6 -bottom-3 -left-3 dark:text-white text-black" />
35 | 			<Icon className="absolute h-6 w-6 -top-3 -right-3 dark:text-white text-black" />
36 | 			<Icon className="absolute h-6 w-6 -bottom-3 -right-3 dark:text-white text-black" />
37 | 
38 | 			<AnimatePresence>
39 | 				{hovered && (
40 | 					<motion.div
41 | 						initial={{ opacity: 0 }}
42 | 						animate={{ opacity: 1 }}
43 | 						className="h-full w-full absolute inset-0"
44 | 					>
45 | 						{children}
46 | 					</motion.div>
47 | 				)}
48 | 			</AnimatePresence>
49 | 
50 | 			<div className="relative z-20">
51 | 				<div className="text-center group-hover/canvas-card:-translate-y-4 group-hover/canvas-card:opacity-0 transition duration-200 w-full  mx-auto flex items-center justify-center">
52 | 					{icon}
53 | 				</div>
54 | 				<h2 className="dark:text-white text-xl opacity-0 group-hover/canvas-card:opacity-100 relative z-10 text-black mt-4  font-bold group-hover/canvas-card:text-white group-hover/canvas-card:-translate-y-2 transition duration-200">
55 | 					{title}
56 | 				</h2>
57 | 			</div>
58 | 		</div>
59 | 	);
60 | };
61 | 
62 | export const Icon = ({ className, ...rest }: any) => {
63 | 	return (
64 | 		<svg
65 | 			xmlns="http://www.w3.org/2000/svg"
66 | 			fill="none"
67 | 			viewBox="0 0 24 24"
68 | 			strokeWidth="1.5"
69 | 			stroke="currentColor"
70 | 			className={className}
71 | 			{...rest}
72 | 		>
73 | 			<path strokeLinecap="round" strokeLinejoin="round" d="M12 6v12m6-6H6" />
74 | 		</svg>
75 | 	);
76 | };
77 | 
```

--------------------------------------------------------------------------------
/docs/app/community/page.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import CommunityHeader from "./_components/header";
 2 | import Stats from "./_components/stats";
 3 | import Section from "@/components/landing/section";
 4 | type NpmPackageResp = {
 5 | 	downloads: number;
 6 | 	start: string;
 7 | 	end: string;
 8 | 	package: string;
 9 | };
10 | async function getNPMPackageDownloads() {
11 | 	const res = await fetch(
12 | 		`https://api.npmjs.org/downloads/point/last-year/better-auth`,
13 | 		{
14 | 			next: { revalidate: 60 },
15 | 		},
16 | 	);
17 | 
18 | 	const npmStat: NpmPackageResp = await res.json();
19 | 	return npmStat;
20 | }
21 | async function getGitHubStars() {
22 | 	try {
23 | 		const response = await fetch(
24 | 			"https://api.github.com/repos/better-auth/better-auth",
25 | 			{
26 | 				next: {
27 | 					revalidate: 60,
28 | 				},
29 | 			},
30 | 		);
31 | 		const json = await response.json();
32 | 		const stars = Number(json.stargazers_count);
33 | 		return stars;
34 | 	} catch {
35 | 		return 0;
36 | 	}
37 | }
38 | export default async function CommunityPage() {
39 | 	const npmDownloads = await getNPMPackageDownloads();
40 | 	const githubStars = await getGitHubStars();
41 | 
42 | 	return (
43 | 		<Section
44 | 			id="hero"
45 | 			className="relative md:px-[3.4rem] md:pl-[3.9rem] md:max-w-7xl md:mx-auto overflow-hidden"
46 | 			crosses={false}
47 | 			crossesOffset=""
48 | 			customPaddings
49 | 		>
50 | 			<div className="min-h-screen w-full bg-transparent">
51 | 				<div className="overflow-hidden flex flex-col w-full bg-transparent/10 relative">
52 | 					<div className="h-[38vh]">
53 | 						<CommunityHeader />
54 | 					</div>
55 | 					<div className="relative py-0">
56 | 						<div className="absolute inset-0 z-0">
57 | 							<div className="grid grid-cols-12 h-full">
58 | 								{Array(12)
59 | 									.fill(null)
60 | 									.map((_, i) => (
61 | 										<div
62 | 											key={i}
63 | 											className="border-l border-dashed border-stone-100 dark:border-white/10 h-full"
64 | 										/>
65 | 									))}
66 | 							</div>
67 | 							<div className="grid grid-rows-12 w-full absolute top-0">
68 | 								{Array(12)
69 | 									.fill(null)
70 | 									.map((_, i) => (
71 | 										<div
72 | 											key={i}
73 | 											className="border-t border-dashed border-stone-100 dark:border-stone-900/60 w-full"
74 | 										/>
75 | 									))}
76 | 							</div>
77 | 						</div>
78 | 					</div>
79 | 					<div className="w-full md:mx-auto overflow-hidden">
80 | 						<Stats
81 | 							npmDownloads={npmDownloads.downloads}
82 | 							githubStars={githubStars}
83 | 						/>
84 | 					</div>
85 | 				</div>
86 | 			</div>
87 | 		</Section>
88 | 	);
89 | }
90 | 
```

--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Release
 2 | 
 3 | on:
 4 |   push:
 5 |     tags:
 6 |       - 'v*'
 7 | 
 8 | permissions:
 9 |   contents: write
10 | 
11 | jobs:
12 |   release:
13 |     runs-on: ubuntu-latest
14 |     steps:
15 |       - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
16 |         with:
17 |           fetch-depth: 0
18 | 
19 |       - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
20 |         with:
21 |           node-version: 22.x
22 | 
23 |       - run: npx changelogithub
24 |         env:
25 |           GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
26 | 
27 |       - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
28 | 
29 |       - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
30 |         with:
31 |           node-version: 22.x
32 |           registry-url: 'https://registry.npmjs.org'
33 | 
34 |       - run: pnpm install
35 | 
36 |       - name: Build
37 |         run: pnpm build
38 | 
39 |       - name: Determine npm tag
40 |         id: determine_npm_tag
41 |         shell: bash
42 |         run: |
43 |           TAG="${GITHUB_REF#refs/tags/}"
44 |           if [[ "$TAG" =~ -(next|canary|beta|rc) ]]; then
45 |             # Extract pre-release tag (e.g., beta, rc)
46 |             NPM_TAG=${BASH_REMATCH[1]}
47 |           else
48 |             # Check if the commit is on the main branch or a version branch
49 |             git fetch origin main
50 |             CURRENT_BRANCH=$(git branch -r --contains "$GITHUB_SHA" | grep -E 'origin/(main|v[0-9]+\.[0-9]+\.x-latest)' | head -1 | sed 's/.*origin\///')
51 | 
52 |             if [[ "$CURRENT_BRANCH" == "main" ]]; then
53 |               NPM_TAG="latest"
54 |             elif [[ "$CURRENT_BRANCH" =~ ^v[0-9]+\.[0-9]+\.x-latest$ ]]; then
55 |               # For version branches like v1.3.x-latest, v1.4.x-latest, use "latest" tag
56 |               NPM_TAG="latest"
57 |             else
58 |               echo "The tagged commit is not on the main branch or a version branch (v*.*.x-latest)."
59 |               echo "::error ::Releases with the 'latest' npm tag must be on the main branch or a version branch."
60 |               exit 1
61 |             fi
62 |           fi
63 |           echo "npm_tag=$NPM_TAG" >> $GITHUB_OUTPUT
64 |           echo "Using npm tag: $NPM_TAG"
65 | 
66 |       - name: Publish to npm
67 |         run: pnpm -r publish --access public --no-git-checks --tag ${{ steps.determine_npm_tag.outputs.npm_tag }}
68 |         env:
69 |           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```

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

```typescript
 1 | import Link from "next/link";
 2 | import { Button } from "./ui/button";
 3 | import { auth } from "@/lib/auth";
 4 | import { headers } from "next/headers";
 5 | 
 6 | export async function SignInButton() {
 7 | 	const session = await auth.api.getSession({
 8 | 		headers: await headers(),
 9 | 	});
10 | 
11 | 	return (
12 | 		<Link
13 | 			href={session?.session ? "/dashboard" : "/sign-in"}
14 | 			className="flex justify-center"
15 | 		>
16 | 			<Button className="gap-2  justify-between" variant="default">
17 | 				{!session?.session ? (
18 | 					<svg
19 | 						xmlns="http://www.w3.org/2000/svg"
20 | 						width="1.2em"
21 | 						height="1.2em"
22 | 						viewBox="0 0 24 24"
23 | 					>
24 | 						<path
25 | 							fill="currentColor"
26 | 							d="M5 3H3v4h2V5h14v14H5v-2H3v4h18V3zm12 8h-2V9h-2V7h-2v2h2v2H3v2h10v2h-2v2h2v-2h2v-2h2z"
27 | 						></path>
28 | 					</svg>
29 | 				) : (
30 | 					<svg
31 | 						xmlns="http://www.w3.org/2000/svg"
32 | 						width="1.2em"
33 | 						height="1.2em"
34 | 						viewBox="0 0 24 24"
35 | 					>
36 | 						<path fill="currentColor" d="M2 3h20v18H2zm18 16V7H4v12z"></path>
37 | 					</svg>
38 | 				)}
39 | 				<span>{session?.session ? "Dashboard" : "Sign In"}</span>
40 | 			</Button>
41 | 		</Link>
42 | 	);
43 | }
44 | 
45 | function checkOptimisticSession(headers: Headers) {
46 | 	const guessIsSignIn =
47 | 		headers.get("cookie")?.includes("better-auth.session") ||
48 | 		headers.get("cookie")?.includes("__Secure-better-auth.session-token");
49 | 	return !!guessIsSignIn;
50 | }
51 | 
52 | export async function SignInFallback() {
53 | 	//to avoid flash of unauthenticated state
54 | 	const guessIsSignIn = checkOptimisticSession(await headers());
55 | 	return (
56 | 		<Link
57 | 			href={guessIsSignIn ? "/dashboard" : "/sign-in"}
58 | 			className="flex justify-center"
59 | 		>
60 | 			<Button className="gap-2  justify-between" variant="default">
61 | 				{!guessIsSignIn ? (
62 | 					<svg
63 | 						xmlns="http://www.w3.org/2000/svg"
64 | 						width="1.2em"
65 | 						height="1.2em"
66 | 						viewBox="0 0 24 24"
67 | 					>
68 | 						<path
69 | 							fill="currentColor"
70 | 							d="M5 3H3v4h2V5h14v14H5v-2H3v4h18V3zm12 8h-2V9h-2V7h-2v2h2v2H3v2h10v2h-2v2h2v-2h2v-2h2z"
71 | 						></path>
72 | 					</svg>
73 | 				) : (
74 | 					<svg
75 | 						xmlns="http://www.w3.org/2000/svg"
76 | 						width="1.2em"
77 | 						height="1.2em"
78 | 						viewBox="0 0 24 24"
79 | 					>
80 | 						<path fill="currentColor" d="M2 3h20v18H2zm18 16V7H4v12z"></path>
81 | 					</svg>
82 | 				)}
83 | 				<span>{guessIsSignIn ? "Dashboard" : "Sign In"}</span>
84 | 			</Button>
85 | 		</Link>
86 | 	);
87 | }
88 | 
```

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

```markdown
 1 | ---
 2 | title: Zoom
 3 | description: Zoom provider setup and usage.
 4 | ---
 5 | 
 6 | <Steps>
 7 |   <Step> 
 8 |     ### Create a Zoom App from Marketplace
 9 |     1. Visit [Zoom Marketplace](https://marketplace.zoom.us).
10 | 
11 |     1. Hover on the `Develop` button and select `Build App`
12 | 
13 |     1. Select `General App` and click `Create`
14 | 
15 |   </Step>
16 | 
17 |   <Step> 
18 |     ### Configure your Zoom App
19 |     
20 |     Ensure that you are in the `Basic Information` of your app settings.
21 | 
22 |     1. Under `Select how the app is managed`, choose `User-managed`
23 | 
24 |     1. Under `App Credentials`, copy your `Client ID` and `Client Secret` and store them in a safe location
25 | 
26 |     1. Under `OAuth Information` -> `OAuth Redirect URL`, add your Callback URL. For example,
27 | 
28 |        ```
29 |        http://localhost:3000/api/auth/callback/zoom
30 |        ```
31 | 
32 |        <Callout>
33 |        For production, you should set it to the URL of your application. If you change the base
34 |        path of the auth routes, you should update the redirect URL accordingly.
35 |        </Callout>
36 | 
37 |     Skip to the `Scopes` section, then
38 |     1. Click the `Add Scopes` button
39 |     1. Search for `user:read:user` (View a user) and select it
40 |     1. Add any other scopes your applications needs and click `Done`
41 | 
42 |   </Step>
43 | 
44 |   <Step>
45 |     ### Configure the provider
46 |     To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
47 | 
48 |     ```ts title="auth.ts"
49 |     import { betterAuth } from "better-auth"
50 | 
51 |     export const auth = betterAuth({
52 |       socialProviders: {
53 |         zoom: { // [!code highlight]
54 |           clientId: process.env.ZOOM_CLIENT_ID as string, // [!code highlight]
55 |           clientSecret: process.env.ZOOM_CLIENT_SECRET as string, // [!code highlight]
56 |         }, // [!code highlight]
57 |       },
58 |     })
59 |     ```
60 | 
61 |   </Step>
62 | 
63 |   <Step>
64 |     ### Sign In with Zoom 
65 |     To sign in with Zoom, you can use the `signIn.social` function provided by the client.
66 |     You will need to specify `zoom` as the provider.
67 | 
68 |     ```ts title="auth-client.ts"
69 |     import { createAuthClient } from "better-auth/client"
70 |     const authClient =  createAuthClient()
71 | 
72 |     const signIn = async () => {
73 |       const data = await authClient.signIn.social({
74 |         provider: "zoom"
75 |       })
76 |     }
77 |     ```
78 | 
79 |   </Step>
80 | </Steps>
81 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/api-key/rate-limit.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ERROR_CODES } from ".";
 2 | import type { PredefinedApiKeyOptions } from "./routes";
 3 | import type { ApiKey } from "./types";
 4 | 
 5 | interface RateLimitResult {
 6 | 	success: boolean;
 7 | 	message: string | null;
 8 | 	tryAgainIn: number | null;
 9 | 	update: Partial<ApiKey> | null;
10 | }
11 | 
12 | /**
13 |  * Determines if a request is allowed based on rate limiting parameters.
14 |  *
15 |  * @returns An object indicating whether the request is allowed and, if not,
16 |  *          a message and updated ApiKey data.
17 |  */
18 | export function isRateLimited(
19 | 	/**
20 | 	 * The ApiKey object containing rate limiting information
21 | 	 */
22 | 	apiKey: ApiKey,
23 | 	opts: PredefinedApiKeyOptions,
24 | ): RateLimitResult {
25 | 	const now = new Date();
26 | 	const lastRequest = apiKey.lastRequest;
27 | 	const rateLimitTimeWindow = apiKey.rateLimitTimeWindow;
28 | 	const rateLimitMax = apiKey.rateLimitMax;
29 | 	let requestCount = apiKey.requestCount;
30 | 
31 | 	if (opts.rateLimit.enabled === false)
32 | 		return {
33 | 			success: true,
34 | 			message: null,
35 | 			update: { lastRequest: now },
36 | 			tryAgainIn: null,
37 | 		};
38 | 
39 | 	if (apiKey.rateLimitEnabled === false)
40 | 		return {
41 | 			success: true,
42 | 			message: null,
43 | 			update: { lastRequest: now },
44 | 			tryAgainIn: null,
45 | 		};
46 | 
47 | 	if (rateLimitTimeWindow === null || rateLimitMax === null) {
48 | 		// Rate limiting is disabled.
49 | 		return {
50 | 			success: true,
51 | 			message: null,
52 | 			update: null,
53 | 			tryAgainIn: null,
54 | 		};
55 | 	}
56 | 
57 | 	if (lastRequest === null) {
58 | 		// No previous requests, so allow the first one.
59 | 		return {
60 | 			success: true,
61 | 			message: null,
62 | 			update: { lastRequest: now, requestCount: 1 },
63 | 			tryAgainIn: null,
64 | 		};
65 | 	}
66 | 
67 | 	const timeSinceLastRequest = now.getTime() - new Date(lastRequest).getTime();
68 | 
69 | 	if (timeSinceLastRequest > rateLimitTimeWindow) {
70 | 		// Time window has passed, reset the request count.
71 | 		return {
72 | 			success: true,
73 | 			message: null,
74 | 			update: { lastRequest: now, requestCount: 1 },
75 | 			tryAgainIn: null,
76 | 		};
77 | 	}
78 | 
79 | 	if (requestCount >= rateLimitMax) {
80 | 		// Rate limit exceeded.
81 | 		return {
82 | 			success: false,
83 | 			message: ERROR_CODES.RATE_LIMIT_EXCEEDED,
84 | 			update: null,
85 | 			tryAgainIn: Math.ceil(rateLimitTimeWindow - timeSinceLastRequest),
86 | 		};
87 | 	}
88 | 
89 | 	// Request is allowed.
90 | 	requestCount++;
91 | 	return {
92 | 		success: true,
93 | 		message: null,
94 | 		tryAgainIn: null,
95 | 		update: { lastRequest: now, requestCount: requestCount },
96 | 	};
97 | }
98 | 
```

--------------------------------------------------------------------------------
/packages/core/src/oauth2/create-authorization-url.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import type { ProviderOptions } from "./index";
 2 | import { generateCodeChallenge } from "./utils";
 3 | 
 4 | export async function createAuthorizationURL({
 5 | 	id,
 6 | 	options,
 7 | 	authorizationEndpoint,
 8 | 	state,
 9 | 	codeVerifier,
10 | 	scopes,
11 | 	claims,
12 | 	redirectURI,
13 | 	duration,
14 | 	prompt,
15 | 	accessType,
16 | 	responseType,
17 | 	display,
18 | 	loginHint,
19 | 	hd,
20 | 	responseMode,
21 | 	additionalParams,
22 | 	scopeJoiner,
23 | }: {
24 | 	id: string;
25 | 	options: ProviderOptions;
26 | 	redirectURI: string;
27 | 	authorizationEndpoint: string;
28 | 	state: string;
29 | 	codeVerifier?: string;
30 | 	scopes: string[];
31 | 	claims?: string[];
32 | 	duration?: string;
33 | 	prompt?: string;
34 | 	accessType?: string;
35 | 	responseType?: string;
36 | 	display?: string;
37 | 	loginHint?: string;
38 | 	hd?: string;
39 | 	responseMode?: string;
40 | 	additionalParams?: Record<string, string>;
41 | 	scopeJoiner?: string;
42 | }) {
43 | 	const url = new URL(authorizationEndpoint);
44 | 	url.searchParams.set("response_type", responseType || "code");
45 | 	const primaryClientId = Array.isArray(options.clientId)
46 | 		? options.clientId[0]
47 | 		: options.clientId;
48 | 	url.searchParams.set("client_id", primaryClientId);
49 | 	url.searchParams.set("state", state);
50 | 	url.searchParams.set("scope", scopes.join(scopeJoiner || " "));
51 | 	url.searchParams.set("redirect_uri", options.redirectURI || redirectURI);
52 | 	duration && url.searchParams.set("duration", duration);
53 | 	display && url.searchParams.set("display", display);
54 | 	loginHint && url.searchParams.set("login_hint", loginHint);
55 | 	prompt && url.searchParams.set("prompt", prompt);
56 | 	hd && url.searchParams.set("hd", hd);
57 | 	accessType && url.searchParams.set("access_type", accessType);
58 | 	responseMode && url.searchParams.set("response_mode", responseMode);
59 | 	if (codeVerifier) {
60 | 		const codeChallenge = await generateCodeChallenge(codeVerifier);
61 | 		url.searchParams.set("code_challenge_method", "S256");
62 | 		url.searchParams.set("code_challenge", codeChallenge);
63 | 	}
64 | 	if (claims) {
65 | 		const claimsObj = claims.reduce(
66 | 			(acc, claim) => {
67 | 				acc[claim] = null;
68 | 				return acc;
69 | 			},
70 | 			{} as Record<string, null>,
71 | 		);
72 | 		url.searchParams.set(
73 | 			"claims",
74 | 			JSON.stringify({
75 | 				id_token: { email: null, email_verified: null, ...claimsObj },
76 | 			}),
77 | 		);
78 | 	}
79 | 	if (additionalParams) {
80 | 		Object.entries(additionalParams).forEach(([key, value]) => {
81 | 			url.searchParams.set(key, value);
82 | 		});
83 | 	}
84 | 	return url;
85 | }
86 | 
```

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

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | 
  5 | import { cn } from "@/lib/utils";
  6 | 
  7 | function Table({ className, ...props }: React.ComponentProps<"table">) {
  8 | 	return (
  9 | 		<div
 10 | 			data-slot="table-container"
 11 | 			className="relative w-full overflow-x-auto"
 12 | 		>
 13 | 			<table
 14 | 				data-slot="table"
 15 | 				className={cn("w-full caption-bottom text-sm", className)}
 16 | 				{...props}
 17 | 			/>
 18 | 		</div>
 19 | 	);
 20 | }
 21 | 
 22 | function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
 23 | 	return (
 24 | 		<thead
 25 | 			data-slot="table-header"
 26 | 			className={cn("[&_tr]:border-b", className)}
 27 | 			{...props}
 28 | 		/>
 29 | 	);
 30 | }
 31 | 
 32 | function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
 33 | 	return (
 34 | 		<tbody
 35 | 			data-slot="table-body"
 36 | 			className={cn("[&_tr:last-child]:border-0", className)}
 37 | 			{...props}
 38 | 		/>
 39 | 	);
 40 | }
 41 | 
 42 | function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
 43 | 	return (
 44 | 		<tfoot
 45 | 			data-slot="table-footer"
 46 | 			className={cn(
 47 | 				"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
 48 | 				className,
 49 | 			)}
 50 | 			{...props}
 51 | 		/>
 52 | 	);
 53 | }
 54 | 
 55 | function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
 56 | 	return (
 57 | 		<tr
 58 | 			data-slot="table-row"
 59 | 			className={cn(
 60 | 				"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
 61 | 				className,
 62 | 			)}
 63 | 			{...props}
 64 | 		/>
 65 | 	);
 66 | }
 67 | 
 68 | function TableHead({ className, ...props }: React.ComponentProps<"th">) {
 69 | 	return (
 70 | 		<th
 71 | 			data-slot="table-head"
 72 | 			className={cn(
 73 | 				"text-muted-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
 74 | 				className,
 75 | 			)}
 76 | 			{...props}
 77 | 		/>
 78 | 	);
 79 | }
 80 | 
 81 | function TableCell({ className, ...props }: React.ComponentProps<"td">) {
 82 | 	return (
 83 | 		<td
 84 | 			data-slot="table-cell"
 85 | 			className={cn(
 86 | 				"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
 87 | 				className,
 88 | 			)}
 89 | 			{...props}
 90 | 		/>
 91 | 	);
 92 | }
 93 | 
 94 | function TableCaption({
 95 | 	className,
 96 | 	...props
 97 | }: React.ComponentProps<"caption">) {
 98 | 	return (
 99 | 		<caption
100 | 			data-slot="table-caption"
101 | 			className={cn("text-muted-foreground mt-4 text-sm", className)}
102 | 			{...props}
103 | 		/>
104 | 	);
105 | }
106 | 
107 | export {
108 | 	Table,
109 | 	TableHeader,
110 | 	TableBody,
111 | 	TableFooter,
112 | 	TableHead,
113 | 	TableRow,
114 | 	TableCell,
115 | 	TableCaption,
116 | };
117 | 
```

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

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | 
  5 | import { cn } from "@/lib/utils";
  6 | 
  7 | function Table({ className, ...props }: React.ComponentProps<"table">) {
  8 | 	return (
  9 | 		<div
 10 | 			data-slot="table-container"
 11 | 			className="relative w-full overflow-x-auto"
 12 | 		>
 13 | 			<table
 14 | 				data-slot="table"
 15 | 				className={cn("w-full caption-bottom text-sm", className)}
 16 | 				{...props}
 17 | 			/>
 18 | 		</div>
 19 | 	);
 20 | }
 21 | 
 22 | function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
 23 | 	return (
 24 | 		<thead
 25 | 			data-slot="table-header"
 26 | 			className={cn("[&_tr]:border-b", className)}
 27 | 			{...props}
 28 | 		/>
 29 | 	);
 30 | }
 31 | 
 32 | function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
 33 | 	return (
 34 | 		<tbody
 35 | 			data-slot="table-body"
 36 | 			className={cn("[&_tr:last-child]:border-0", className)}
 37 | 			{...props}
 38 | 		/>
 39 | 	);
 40 | }
 41 | 
 42 | function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
 43 | 	return (
 44 | 		<tfoot
 45 | 			data-slot="table-footer"
 46 | 			className={cn(
 47 | 				"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
 48 | 				className,
 49 | 			)}
 50 | 			{...props}
 51 | 		/>
 52 | 	);
 53 | }
 54 | 
 55 | function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
 56 | 	return (
 57 | 		<tr
 58 | 			data-slot="table-row"
 59 | 			className={cn(
 60 | 				"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
 61 | 				className,
 62 | 			)}
 63 | 			{...props}
 64 | 		/>
 65 | 	);
 66 | }
 67 | 
 68 | function TableHead({ className, ...props }: React.ComponentProps<"th">) {
 69 | 	return (
 70 | 		<th
 71 | 			data-slot="table-head"
 72 | 			className={cn(
 73 | 				"text-muted-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
 74 | 				className,
 75 | 			)}
 76 | 			{...props}
 77 | 		/>
 78 | 	);
 79 | }
 80 | 
 81 | function TableCell({ className, ...props }: React.ComponentProps<"td">) {
 82 | 	return (
 83 | 		<td
 84 | 			data-slot="table-cell"
 85 | 			className={cn(
 86 | 				"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
 87 | 				className,
 88 | 			)}
 89 | 			{...props}
 90 | 		/>
 91 | 	);
 92 | }
 93 | 
 94 | function TableCaption({
 95 | 	className,
 96 | 	...props
 97 | }: React.ComponentProps<"caption">) {
 98 | 	return (
 99 | 		<caption
100 | 			data-slot="table-caption"
101 | 			className={cn("text-muted-foreground mt-4 text-sm", className)}
102 | 			{...props}
103 | 		/>
104 | 	);
105 | }
106 | 
107 | export {
108 | 	Table,
109 | 	TableHeader,
110 | 	TableBody,
111 | 	TableFooter,
112 | 	TableHead,
113 | 	TableRow,
114 | 	TableCell,
115 | 	TableCaption,
116 | };
117 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/access/access.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { BetterAuthError } from "@better-auth/core/error";
 2 | import type { Statements, Subset } from "./types";
 3 | 
 4 | export type AuthorizeResponse =
 5 | 	| { success: false; error: string }
 6 | 	| { success: true; error?: never };
 7 | 
 8 | export function role<TStatements extends Statements>(statements: TStatements) {
 9 | 	return {
10 | 		authorize<K extends keyof TStatements>(
11 | 			request: {
12 | 				[key in K]?:
13 | 					| TStatements[key]
14 | 					| {
15 | 							actions: TStatements[key];
16 | 							connector: "OR" | "AND";
17 | 					  };
18 | 			},
19 | 			connector: "OR" | "AND" = "AND",
20 | 		): AuthorizeResponse {
21 | 			let success = false;
22 | 			for (const [requestedResource, requestedActions] of Object.entries(
23 | 				request,
24 | 			)) {
25 | 				const allowedActions = statements[requestedResource];
26 | 				if (!allowedActions) {
27 | 					return {
28 | 						success: false,
29 | 						error: `You are not allowed to access resource: ${requestedResource}`,
30 | 					};
31 | 				}
32 | 				if (Array.isArray(requestedActions)) {
33 | 					success = (requestedActions as string[]).every((requestedAction) =>
34 | 						allowedActions.includes(requestedAction),
35 | 					);
36 | 				} else {
37 | 					if (typeof requestedActions === "object") {
38 | 						const actions = requestedActions as {
39 | 							actions: string[];
40 | 							connector: "OR" | "AND";
41 | 						};
42 | 						if (actions.connector === "OR") {
43 | 							success = actions.actions.some((requestedAction) =>
44 | 								allowedActions.includes(requestedAction),
45 | 							);
46 | 						} else {
47 | 							success = actions.actions.every((requestedAction) =>
48 | 								allowedActions.includes(requestedAction),
49 | 							);
50 | 						}
51 | 					} else {
52 | 						throw new BetterAuthError("Invalid access control request");
53 | 					}
54 | 				}
55 | 				if (success && connector === "OR") {
56 | 					return { success };
57 | 				}
58 | 				if (!success && connector === "AND") {
59 | 					return {
60 | 						success: false,
61 | 						error: `unauthorized to access resource "${requestedResource}"`,
62 | 					};
63 | 				}
64 | 			}
65 | 			if (success) {
66 | 				return {
67 | 					success,
68 | 				};
69 | 			}
70 | 			return {
71 | 				success: false,
72 | 				error: "Not authorized",
73 | 			};
74 | 		},
75 | 		statements,
76 | 	};
77 | }
78 | 
79 | export function createAccessControl<const TStatements extends Statements>(
80 | 	s: TStatements,
81 | ) {
82 | 	return {
83 | 		newRole<K extends keyof TStatements>(statements: Subset<K, TStatements>) {
84 | 			return role<Subset<K, TStatements>>(statements);
85 | 		},
86 | 		statements: s,
87 | 	};
88 | }
89 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/adapters/drizzle-adapter/test/adapter.drizzle.mysql.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { drizzleAdapter } from "../drizzle-adapter";
 2 | import { testAdapter } from "../../test-adapter";
 3 | import {
 4 | 	authFlowTestSuite,
 5 | 	normalTestSuite,
 6 | 	numberIdTestSuite,
 7 | 	performanceTestSuite,
 8 | 	transactionsTestSuite,
 9 | } from "../../tests";
10 | import { drizzle } from "drizzle-orm/mysql2";
11 | import { generateDrizzleSchema, resetGenerationCount } from "./generate-schema";
12 | import { createPool } from "mysql2/promise";
13 | import { assert } from "vitest";
14 | import { execSync } from "child_process";
15 | 
16 | const mysqlDB = createPool({
17 | 	uri: "mysql://user:password@localhost:3306",
18 | 	timezone: "Z",
19 | });
20 | 
21 | const { execute } = await testAdapter({
22 | 	adapter: async (options) => {
23 | 		const { schema } = await generateDrizzleSchema(mysqlDB, options, "mysql");
24 | 		return drizzleAdapter(drizzle(mysqlDB), {
25 | 			debugLogs: { isRunningAdapterTests: true },
26 | 			schema,
27 | 			provider: "mysql",
28 | 		});
29 | 	},
30 | 	async runMigrations(betterAuthOptions) {
31 | 		await mysqlDB.query("DROP DATABASE IF EXISTS better_auth");
32 | 		await mysqlDB.query("CREATE DATABASE better_auth");
33 | 		await mysqlDB.query("USE better_auth");
34 | 
35 | 		const { fileName } = await generateDrizzleSchema(
36 | 			mysqlDB,
37 | 			betterAuthOptions,
38 | 			"mysql",
39 | 		);
40 | 
41 | 		const command = `npx drizzle-kit push --dialect=mysql --schema=${fileName}.ts --url=mysql://user:password@localhost:3306/better_auth`;
42 | 		console.log(`Running: ${command}`);
43 | 		console.log(`Options:`, betterAuthOptions);
44 | 		try {
45 | 			// wait for the above console.log to be printed
46 | 			await new Promise((resolve) => setTimeout(resolve, 10));
47 | 			execSync(command, {
48 | 				cwd: import.meta.dirname,
49 | 				stdio: "inherit",
50 | 			});
51 | 		} catch (error) {
52 | 			console.error("Failed to push drizzle schema (mysql):", error);
53 | 			throw error;
54 | 		}
55 | 
56 | 		// ensure migrations were run successfully
57 | 		const [tables_result] = (await mysqlDB.query("SHOW TABLES")) as unknown as [
58 | 			{ Tables_in_better_auth: string }[],
59 | 		];
60 | 		const tables = tables_result.map((table) => table.Tables_in_better_auth);
61 | 		assert(tables.length > 0, "No tables found");
62 | 	},
63 | 	prefixTests: "mysql",
64 | 	tests: [
65 | 		normalTestSuite(),
66 | 		transactionsTestSuite({ disableTests: { ALL: true } }),
67 | 		authFlowTestSuite(),
68 | 		numberIdTestSuite(),
69 | 		performanceTestSuite({ dialect: "mysql" }),
70 | 	],
71 | 	async onFinish() {
72 | 		await mysqlDB.end();
73 | 		resetGenerationCount();
74 | 	},
75 | });
76 | 
77 | execute();
78 | 
```

--------------------------------------------------------------------------------
/packages/core/src/social-providers/spotify.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { betterFetch } from "@better-fetch/fetch";
 2 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
 3 | import {
 4 | 	createAuthorizationURL,
 5 | 	validateAuthorizationCode,
 6 | 	refreshAccessToken,
 7 | } from "../oauth2";
 8 | 
 9 | export interface SpotifyProfile {
10 | 	id: string;
11 | 	display_name: string;
12 | 	email: string;
13 | 	images: {
14 | 		url: string;
15 | 	}[];
16 | }
17 | 
18 | export interface SpotifyOptions extends ProviderOptions<SpotifyProfile> {
19 | 	clientId: string;
20 | }
21 | 
22 | export const spotify = (options: SpotifyOptions) => {
23 | 	return {
24 | 		id: "spotify",
25 | 		name: "Spotify",
26 | 		createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
27 | 			const _scopes = options.disableDefaultScope ? [] : ["user-read-email"];
28 | 			options.scope && _scopes.push(...options.scope);
29 | 			scopes && _scopes.push(...scopes);
30 | 			return createAuthorizationURL({
31 | 				id: "spotify",
32 | 				options,
33 | 				authorizationEndpoint: "https://accounts.spotify.com/authorize",
34 | 				scopes: _scopes,
35 | 				state,
36 | 				codeVerifier,
37 | 				redirectURI,
38 | 			});
39 | 		},
40 | 		validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
41 | 			return validateAuthorizationCode({
42 | 				code,
43 | 				codeVerifier,
44 | 				redirectURI,
45 | 				options,
46 | 				tokenEndpoint: "https://accounts.spotify.com/api/token",
47 | 			});
48 | 		},
49 | 		refreshAccessToken: options.refreshAccessToken
50 | 			? options.refreshAccessToken
51 | 			: async (refreshToken) => {
52 | 					return refreshAccessToken({
53 | 						refreshToken,
54 | 						options: {
55 | 							clientId: options.clientId,
56 | 							clientKey: options.clientKey,
57 | 							clientSecret: options.clientSecret,
58 | 						},
59 | 						tokenEndpoint: "https://accounts.spotify.com/api/token",
60 | 					});
61 | 				},
62 | 		async getUserInfo(token) {
63 | 			if (options.getUserInfo) {
64 | 				return options.getUserInfo(token);
65 | 			}
66 | 			const { data: profile, error } = await betterFetch<SpotifyProfile>(
67 | 				"https://api.spotify.com/v1/me",
68 | 				{
69 | 					method: "GET",
70 | 					headers: {
71 | 						Authorization: `Bearer ${token.accessToken}`,
72 | 					},
73 | 				},
74 | 			);
75 | 			if (error) {
76 | 				return null;
77 | 			}
78 | 			const userMap = await options.mapProfileToUser?.(profile);
79 | 			return {
80 | 				user: {
81 | 					id: profile.id,
82 | 					name: profile.display_name,
83 | 					email: profile.email,
84 | 					image: profile.images[0]?.url,
85 | 					emailVerified: false,
86 | 					...userMap,
87 | 				},
88 | 				data: profile,
89 | 			};
90 | 		},
91 | 		options,
92 | 	} satisfies OAuthProvider<SpotifyProfile>;
93 | };
94 | 
```

--------------------------------------------------------------------------------
/docs/app/page.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import Section from "@/components/landing/section";
 2 | import Hero from "@/components/landing/hero";
 3 | import Features from "@/components/features";
 4 | import Link from "next/link";
 5 | 
 6 | async function getGitHubStars() {
 7 | 	try {
 8 | 		const response = await fetch(
 9 | 			"https://api.github.com/repos/better-auth/better-auth",
10 | 			{
11 | 				next: {
12 | 					revalidate: 60,
13 | 				},
14 | 			},
15 | 		);
16 | 		if (!response?.ok) {
17 | 			return null;
18 | 		}
19 | 		const json = await response.json();
20 | 		const stars = parseInt(json.stargazers_count).toLocaleString();
21 | 		return stars;
22 | 	} catch {
23 | 		return null;
24 | 	}
25 | }
26 | 
27 | export default async function HomePage() {
28 | 	const stars = await getGitHubStars();
29 | 	return (
30 | 		<main className="h-min mx-auto overflow-x-hidden">
31 | 			<div className="w-full bg-gradient-to-br from-zinc-50 to-zinc-100 dark:from-zinc-950 dark:via-black dark:to-zinc-950 border-b border-dashed border-zinc-200 dark:border-zinc-800">
32 | 				<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
33 | 					<div className="w-full h-full">
34 | 						<div className="flex flex-col md:flex-row items-center justify-center h-12">
35 | 							<span className="font-medium flex gap-2 text-sm text-zinc-700 dark:text-zinc-300">
36 | 								<span className=" text-zinc-900 dark:text-white/90 hover:text-zinc-950 text-xs md:text-sm dark:hover:text-zinc-100 transition-colors">
37 | 									Introducing{" "}
38 | 									<span className="font-semibold">
39 | 										Better Auth Infrastructure
40 | 									</span>
41 | 								</span>
42 | 								<span className=" text-zinc-400 hidden md:block">|</span>
43 | 								<Link
44 | 									href="https://better-auth.build"
45 | 									className="font-semibold text-blue-600 dark:text-blue-400 hover:text-blue-700 hidden dark:hover:text-blue-300 transition-colors md:block"
46 | 								>
47 | 									Join the waitlist →
48 | 								</Link>
49 | 							</span>
50 | 							<Link
51 | 								href="https://better-auth.build"
52 | 								className="font-semibold text-blue-600 dark:text-blue-400 hover:text-blue-700 text-xs dark:hover:text-blue-300 transition-colors md:hidden"
53 | 							>
54 | 								Join the waitlist →
55 | 							</Link>
56 | 						</div>
57 | 					</div>
58 | 				</div>
59 | 			</div>
60 | 			<Section
61 | 				className="mb-1 overflow-y-clip"
62 | 				crosses
63 | 				crossesOffset="lg:translate-y-[5.25rem]"
64 | 				customPaddings
65 | 				id="hero"
66 | 			>
67 | 				<Hero />
68 | 				<Features stars={stars} />
69 | 				<hr className="h-px bg-gray-200" />
70 | 			</Section>
71 | 		</main>
72 | 	);
73 | }
74 | 
```

--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------

```yaml
  1 | version: '3.8'
  2 | 
  3 | services:
  4 |   mongodb:
  5 |     image: mongo:latest
  6 |     container_name: mongodb
  7 |     ports:
  8 |       - "27017:27017"
  9 |     volumes:
 10 |       - mongodb_data:/data/db
 11 |   
 12 |   # drizzle
 13 |   postgres:
 14 |     image: postgres:latest
 15 |     container_name: postgres
 16 |     environment:
 17 |       POSTGRES_USER: user
 18 |       POSTGRES_PASSWORD: password
 19 |       POSTGRES_DB: better_auth
 20 |     ports:
 21 |       - "5432:5432"
 22 |     volumes:
 23 |       - postgres_data:/var/lib/postgresql/data
 24 | 
 25 |   postgres-kysely:
 26 |     image: postgres:latest
 27 |     container_name: postgres-kysely
 28 |     environment:
 29 |       POSTGRES_USER: user
 30 |       POSTGRES_PASSWORD: password
 31 |       POSTGRES_DB: better_auth
 32 |     ports:
 33 |       - "5433:5432"
 34 |     volumes:
 35 |       - postgres-kysely_data:/var/lib/postgresql/data
 36 | 
 37 |   postgres-prisma:
 38 |     image: postgres:latest
 39 |     container_name: postgres-prisma
 40 |     environment:
 41 |       POSTGRES_USER: user
 42 |       POSTGRES_PASSWORD: password
 43 |       POSTGRES_DB: better_auth
 44 |     ports:
 45 |       - "5434:5432"
 46 |     volumes:
 47 |       - postgres-prisma_data:/var/lib/postgresql/data
 48 | 
 49 |   # Drizzle tests
 50 |   mysql:
 51 |     image: mysql:latest
 52 |     container_name: mysql
 53 |     environment:
 54 |       MYSQL_ROOT_PASSWORD: root_password
 55 |       MYSQL_DATABASE: better_auth
 56 |       MYSQL_USER: user
 57 |       MYSQL_PASSWORD: password
 58 |     ports:
 59 |       - "3306:3306"
 60 |     volumes:
 61 |       - mysql_data:/var/lib/mysql
 62 | 
 63 | 
 64 |   mysql-kysely:
 65 |     image: mysql:latest
 66 |     container_name: mysql-kysely
 67 |     environment:
 68 |       MYSQL_ROOT_PASSWORD: root_password
 69 |       MYSQL_DATABASE: better_auth
 70 |       MYSQL_USER: user
 71 |       MYSQL_PASSWORD: password
 72 |     ports:
 73 |       - "3307:3306"
 74 |     volumes:
 75 |       - mysql-kysely_data:/var/lib/mysql
 76 | 
 77 |   mysql-prisma:
 78 |     image: mysql:latest
 79 |     container_name: mysql-prisma
 80 |     environment:
 81 |       MYSQL_ROOT_PASSWORD: root_password
 82 |       MYSQL_DATABASE: better_auth
 83 |       MYSQL_USER: user
 84 |       MYSQL_PASSWORD: password
 85 |     ports:
 86 |       - "3308:3306"
 87 |     volumes:
 88 |       - mysql-prisma_data:/var/lib/mysql
 89 | 
 90 | 
 91 |   mssql:
 92 |     image: mcr.microsoft.com/mssql/server:latest
 93 |     container_name: mssql
 94 |     environment:
 95 |       SA_PASSWORD: "Password123!"
 96 |       ACCEPT_EULA: "Y"
 97 |     ports:
 98 |       - "1433:1433"
 99 |     volumes:
100 |       - mssql_data:/var/opt/mssql
101 | 
102 | volumes:
103 |   mongodb_data:
104 |   postgres_data:
105 |   postgres-kysely_data:
106 |   postgres-prisma_data:
107 |   mysql_data:
108 |   mssql_data:
109 |   mysql-kysely_data:
110 |   mysql-prisma_data: 
```

--------------------------------------------------------------------------------
/packages/telemetry/src/utils/package-json.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import type { PackageJson } from "type-fest";
 2 | let packageJSONCache: PackageJson | undefined;
 3 | 
 4 | async function readRootPackageJson() {
 5 | 	if (packageJSONCache) return packageJSONCache;
 6 | 	try {
 7 | 		const cwd =
 8 | 			typeof process !== "undefined" && typeof process.cwd === "function"
 9 | 				? process.cwd()
10 | 				: "";
11 | 		if (!cwd) return undefined;
12 | 		// Lazily import Node built-ins only when available (Node/Bun/Deno) and
13 | 		// avoid static analyzer/bundler resolution by obfuscating module names
14 | 		const importRuntime = (m: string) =>
15 | 			(Function("mm", "return import(mm)") as any)(m);
16 | 		const [{ default: fs }, { default: path }] = await Promise.all([
17 | 			importRuntime("fs/promises"),
18 | 			importRuntime("path"),
19 | 		]);
20 | 		const raw = await fs.readFile(path.join(cwd, "package.json"), "utf-8");
21 | 		packageJSONCache = JSON.parse(raw);
22 | 		return packageJSONCache as PackageJson;
23 | 	} catch {}
24 | 	return undefined;
25 | }
26 | 
27 | export async function getPackageVersion(pkg: string) {
28 | 	if (packageJSONCache) {
29 | 		return (packageJSONCache.dependencies?.[pkg] ||
30 | 			packageJSONCache.devDependencies?.[pkg] ||
31 | 			packageJSONCache.peerDependencies?.[pkg]) as string | undefined;
32 | 	}
33 | 
34 | 	try {
35 | 		const cwd =
36 | 			typeof process !== "undefined" && typeof process.cwd === "function"
37 | 				? process.cwd()
38 | 				: "";
39 | 		if (!cwd) throw new Error("no-cwd");
40 | 		const importRuntime = (m: string) =>
41 | 			(Function("mm", "return import(mm)") as any)(m);
42 | 		const [{ default: fs }, { default: path }] = await Promise.all([
43 | 			importRuntime("fs/promises"),
44 | 			importRuntime("path"),
45 | 		]);
46 | 		const pkgJsonPath = path.join(cwd, "node_modules", pkg, "package.json");
47 | 		const raw = await fs.readFile(pkgJsonPath, "utf-8");
48 | 		const json = JSON.parse(raw);
49 | 		const resolved =
50 | 			(json.version as string) ||
51 | 			(await getVersionFromLocalPackageJson(pkg)) ||
52 | 			undefined;
53 | 		return resolved;
54 | 	} catch {}
55 | 
56 | 	const fromRoot = await getVersionFromLocalPackageJson(pkg);
57 | 	return fromRoot;
58 | }
59 | 
60 | async function getVersionFromLocalPackageJson(pkg: string) {
61 | 	const json = await readRootPackageJson();
62 | 	if (!json) return undefined;
63 | 	const allDeps = {
64 | 		...json.dependencies,
65 | 		...json.devDependencies,
66 | 		...json.peerDependencies,
67 | 	} as Record<string, string | undefined>;
68 | 	return allDeps[pkg];
69 | }
70 | 
71 | export async function getNameFromLocalPackageJson() {
72 | 	const json = await readRootPackageJson();
73 | 	return json?.name as string | undefined;
74 | }
75 | 
```

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

```typescript
 1 | import { getClientConfig } from "./config";
 2 | import { capitalizeFirstLetter } from "../utils/misc";
 3 | import type {
 4 | 	InferActions,
 5 | 	InferClientAPI,
 6 | 	InferErrorCodes,
 7 | 	IsSignal,
 8 | } from "./types";
 9 | import type {
10 | 	BetterAuthClientPlugin,
11 | 	BetterAuthClientOptions,
12 | } from "@better-auth/core";
13 | import { createDynamicPathProxy } from "./proxy";
14 | import type { PrettifyDeep, UnionToIntersection } from "../types/helper";
15 | import type { Atom } from "nanostores";
16 | import type {
17 | 	BetterFetchError,
18 | 	BetterFetchResponse,
19 | } from "@better-fetch/fetch";
20 | import type { BASE_ERROR_CODES } from "@better-auth/core/error";
21 | 
22 | type InferResolvedHooks<O extends BetterAuthClientOptions> = O extends {
23 | 	plugins: Array<infer Plugin>;
24 | }
25 | 	? UnionToIntersection<
26 | 			Plugin extends BetterAuthClientPlugin
27 | 				? Plugin["getAtoms"] extends (fetch: any) => infer Atoms
28 | 					? Atoms extends Record<string, any>
29 | 						? {
30 | 								[key in keyof Atoms as IsSignal<key> extends true
31 | 									? never
32 | 									: key extends string
33 | 										? `use${Capitalize<key>}`
34 | 										: never]: Atoms[key];
35 | 							}
36 | 						: {}
37 | 					: {}
38 | 				: {}
39 | 		>
40 | 	: {};
41 | 
42 | export function createAuthClient<Option extends BetterAuthClientOptions>(
43 | 	options?: Option,
44 | ) {
45 | 	const {
46 | 		pluginPathMethods,
47 | 		pluginsActions,
48 | 		pluginsAtoms,
49 | 		$fetch,
50 | 		atomListeners,
51 | 		$store,
52 | 	} = getClientConfig(options);
53 | 	let resolvedHooks: Record<string, any> = {};
54 | 	for (const [key, value] of Object.entries(pluginsAtoms)) {
55 | 		resolvedHooks[`use${capitalizeFirstLetter(key)}`] = value;
56 | 	}
57 | 	const routes = {
58 | 		...pluginsActions,
59 | 		...resolvedHooks,
60 | 		$fetch,
61 | 		$store,
62 | 	};
63 | 	const proxy = createDynamicPathProxy(
64 | 		routes,
65 | 		$fetch,
66 | 		pluginPathMethods,
67 | 		pluginsAtoms,
68 | 		atomListeners,
69 | 	);
70 | 	type ClientAPI = InferClientAPI<Option>;
71 | 	type Session = ClientAPI extends {
72 | 		getSession: () => Promise<infer Res>;
73 | 	}
74 | 		? Res extends BetterFetchResponse<infer S>
75 | 			? S
76 | 			: Res extends Record<string, any>
77 | 				? Res
78 | 				: never
79 | 		: never;
80 | 	return proxy as UnionToIntersection<InferResolvedHooks<Option>> &
81 | 		ClientAPI &
82 | 		InferActions<Option> & {
83 | 			useSession: Atom<{
84 | 				data: Session;
85 | 				error: BetterFetchError | null;
86 | 				isPending: boolean;
87 | 			}>;
88 | 			$fetch: typeof $fetch;
89 | 			$store: typeof $store;
90 | 			$Infer: {
91 | 				Session: NonNullable<Session>;
92 | 			};
93 | 			$ERROR_CODES: PrettifyDeep<
94 | 				InferErrorCodes<Option> & typeof BASE_ERROR_CODES
95 | 			>;
96 | 		};
97 | }
98 | 
```

--------------------------------------------------------------------------------
/packages/core/src/oauth2/client-credentials-token.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import { base64Url } from "@better-auth/utils/base64";
  3 | import type { OAuth2Tokens, ProviderOptions } from "./oauth-provider";
  4 | 
  5 | export function createClientCredentialsTokenRequest({
  6 | 	options,
  7 | 	scope,
  8 | 	authentication,
  9 | 	resource,
 10 | }: {
 11 | 	options: ProviderOptions & { clientSecret: string };
 12 | 	scope?: string;
 13 | 	authentication?: "basic" | "post";
 14 | 	resource?: string | string[];
 15 | }) {
 16 | 	const body = new URLSearchParams();
 17 | 	const headers: Record<string, any> = {
 18 | 		"content-type": "application/x-www-form-urlencoded",
 19 | 		accept: "application/json",
 20 | 	};
 21 | 
 22 | 	body.set("grant_type", "client_credentials");
 23 | 	scope && body.set("scope", scope);
 24 | 	if (resource) {
 25 | 		if (typeof resource === "string") {
 26 | 			body.append("resource", resource);
 27 | 		} else {
 28 | 			for (const _resource of resource) {
 29 | 				body.append("resource", _resource);
 30 | 			}
 31 | 		}
 32 | 	}
 33 | 	if (authentication === "basic") {
 34 | 		const primaryClientId = Array.isArray(options.clientId)
 35 | 			? options.clientId[0]
 36 | 			: options.clientId;
 37 | 		const encodedCredentials = base64Url.encode(
 38 | 			`${primaryClientId}:${options.clientSecret}`,
 39 | 		);
 40 | 		headers["authorization"] = `Basic ${encodedCredentials}`;
 41 | 	} else {
 42 | 		const primaryClientId = Array.isArray(options.clientId)
 43 | 			? options.clientId[0]
 44 | 			: options.clientId;
 45 | 		body.set("client_id", primaryClientId);
 46 | 		body.set("client_secret", options.clientSecret);
 47 | 	}
 48 | 
 49 | 	return {
 50 | 		body,
 51 | 		headers,
 52 | 	};
 53 | }
 54 | 
 55 | export async function clientCredentialsToken({
 56 | 	options,
 57 | 	tokenEndpoint,
 58 | 	scope,
 59 | 	authentication,
 60 | 	resource,
 61 | }: {
 62 | 	options: ProviderOptions & { clientSecret: string };
 63 | 	tokenEndpoint: string;
 64 | 	scope: string;
 65 | 	authentication?: "basic" | "post";
 66 | 	resource?: string | string[];
 67 | }): Promise<OAuth2Tokens> {
 68 | 	const { body, headers } = createClientCredentialsTokenRequest({
 69 | 		options,
 70 | 		scope,
 71 | 		authentication,
 72 | 		resource,
 73 | 	});
 74 | 
 75 | 	const { data, error } = await betterFetch<{
 76 | 		access_token: string;
 77 | 		expires_in?: number;
 78 | 		token_type?: string;
 79 | 		scope?: string;
 80 | 	}>(tokenEndpoint, {
 81 | 		method: "POST",
 82 | 		body,
 83 | 		headers,
 84 | 	});
 85 | 	if (error) {
 86 | 		throw error;
 87 | 	}
 88 | 	const tokens: OAuth2Tokens = {
 89 | 		accessToken: data.access_token,
 90 | 		tokenType: data.token_type,
 91 | 		scopes: data.scope?.split(" "),
 92 | 	};
 93 | 
 94 | 	if (data.expires_in) {
 95 | 		const now = new Date();
 96 | 		tokens.accessTokenExpiresAt = new Date(
 97 | 			now.getTime() + data.expires_in * 1000,
 98 | 		);
 99 | 	}
100 | 
101 | 	return tokens;
102 | }
103 | 
```

--------------------------------------------------------------------------------
/docs/app/blog/_components/icons.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | export function BookIcon(props: React.ComponentPropsWithoutRef<"svg">) {
 2 | 	return (
 3 | 		<svg viewBox="0 0 16 16" aria-hidden="true" fill="currentColor" {...props}>
 4 | 			<path d="M7 3.41a1 1 0 0 0-.668-.943L2.275 1.039a.987.987 0 0 0-.877.166c-.25.192-.398.493-.398.812V12.2c0 .454.296.853.725.977l3.948 1.365A1 1 0 0 0 7 13.596V3.41ZM9 13.596a1 1 0 0 0 1.327.946l3.948-1.365c.429-.124.725-.523.725-.977V2.017c0-.32-.147-.62-.398-.812a.987.987 0 0 0-.877-.166L9.668 2.467A1 1 0 0 0 9 3.41v10.186Z" />
 5 | 		</svg>
 6 | 	);
 7 | }
 8 | 
 9 | export function GitHubIcon(props: React.ComponentPropsWithoutRef<"svg">) {
10 | 	return (
11 | 		<svg viewBox="0 0 16 16" aria-hidden="true" fill="currentColor" {...props}>
12 | 			<path d="M8 .198a8 8 0 0 0-8 8 7.999 7.999 0 0 0 5.47 7.59c.4.076.547-.172.547-.384 0-.19-.007-.694-.01-1.36-2.226.482-2.695-1.074-2.695-1.074-.364-.923-.89-1.17-.89-1.17-.725-.496.056-.486.056-.486.803.056 1.225.824 1.225.824.714 1.224 1.873.87 2.33.666.072-.518.278-.87.507-1.07-1.777-.2-3.644-.888-3.644-3.954 0-.873.31-1.586.823-2.146-.09-.202-.36-1.016.07-2.118 0 0 .67-.214 2.2.82a7.67 7.67 0 0 1 2-.27 7.67 7.67 0 0 1 2 .27c1.52-1.034 2.19-.82 2.19-.82.43 1.102.16 1.916.08 2.118.51.56.82 1.273.82 2.146 0 3.074-1.87 3.75-3.65 3.947.28.24.54.73.54 1.48 0 1.07-.01 1.93-.01 2.19 0 .21.14.46.55.38A7.972 7.972 0 0 0 16 8.199a8 8 0 0 0-8-8Z" />
13 | 		</svg>
14 | 	);
15 | }
16 | 
17 | export function FeedIcon(props: React.ComponentPropsWithoutRef<"svg">) {
18 | 	return (
19 | 		<svg viewBox="0 0 16 16" aria-hidden="true" fill="currentColor" {...props}>
20 | 			<path
21 | 				fillRule="evenodd"
22 | 				clipRule="evenodd"
23 | 				d="M2.5 3a.5.5 0 0 1 .5-.5h.5c5.523 0 10 4.477 10 10v.5a.5.5 0 0 1-.5.5h-.5a.5.5 0 0 1-.5-.5v-.5A8.5 8.5 0 0 0 3.5 4H3a.5.5 0 0 1-.5-.5V3Zm0 4.5A.5.5 0 0 1 3 7h.5A5.5 5.5 0 0 1 9 12.5v.5a.5.5 0 0 1-.5.5H8a.5.5 0 0 1-.5-.5v-.5a4 4 0 0 0-4-4H3a.5.5 0 0 1-.5-.5v-.5Zm0 5a1 1 0 1 1 2 0 1 1 0 0 1-2 0Z"
24 | 			/>
25 | 		</svg>
26 | 	);
27 | }
28 | 
29 | export function XIcon(props: React.ComponentPropsWithoutRef<"svg">) {
30 | 	return (
31 | 		<svg viewBox="0 0 16 16" aria-hidden="true" fill="currentColor" {...props}>
32 | 			<path d="M9.51762 6.77491L15.3459 0H13.9648L8.90409 5.88256L4.86212 0H0.200195L6.31244 8.89547L0.200195 16H1.58139L6.92562 9.78782L11.1942 16H15.8562L9.51728 6.77491H9.51762ZM7.62588 8.97384L7.00658 8.08805L2.07905 1.03974H4.20049L8.17706 6.72795L8.79636 7.61374L13.9654 15.0075H11.844L7.62588 8.97418V8.97384Z" />
33 | 		</svg>
34 | 	);
35 | }
36 | 
```

--------------------------------------------------------------------------------
/docs/app/changelogs/_components/icons.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | export function BookIcon(props: React.ComponentPropsWithoutRef<"svg">) {
 2 | 	return (
 3 | 		<svg viewBox="0 0 16 16" aria-hidden="true" fill="currentColor" {...props}>
 4 | 			<path d="M7 3.41a1 1 0 0 0-.668-.943L2.275 1.039a.987.987 0 0 0-.877.166c-.25.192-.398.493-.398.812V12.2c0 .454.296.853.725.977l3.948 1.365A1 1 0 0 0 7 13.596V3.41ZM9 13.596a1 1 0 0 0 1.327.946l3.948-1.365c.429-.124.725-.523.725-.977V2.017c0-.32-.147-.62-.398-.812a.987.987 0 0 0-.877-.166L9.668 2.467A1 1 0 0 0 9 3.41v10.186Z" />
 5 | 		</svg>
 6 | 	);
 7 | }
 8 | 
 9 | export function GitHubIcon(props: React.ComponentPropsWithoutRef<"svg">) {
10 | 	return (
11 | 		<svg viewBox="0 0 16 16" aria-hidden="true" fill="currentColor" {...props}>
12 | 			<path d="M8 .198a8 8 0 0 0-8 8 7.999 7.999 0 0 0 5.47 7.59c.4.076.547-.172.547-.384 0-.19-.007-.694-.01-1.36-2.226.482-2.695-1.074-2.695-1.074-.364-.923-.89-1.17-.89-1.17-.725-.496.056-.486.056-.486.803.056 1.225.824 1.225.824.714 1.224 1.873.87 2.33.666.072-.518.278-.87.507-1.07-1.777-.2-3.644-.888-3.644-3.954 0-.873.31-1.586.823-2.146-.09-.202-.36-1.016.07-2.118 0 0 .67-.214 2.2.82a7.67 7.67 0 0 1 2-.27 7.67 7.67 0 0 1 2 .27c1.52-1.034 2.19-.82 2.19-.82.43 1.102.16 1.916.08 2.118.51.56.82 1.273.82 2.146 0 3.074-1.87 3.75-3.65 3.947.28.24.54.73.54 1.48 0 1.07-.01 1.93-.01 2.19 0 .21.14.46.55.38A7.972 7.972 0 0 0 16 8.199a8 8 0 0 0-8-8Z" />
13 | 		</svg>
14 | 	);
15 | }
16 | 
17 | export function FeedIcon(props: React.ComponentPropsWithoutRef<"svg">) {
18 | 	return (
19 | 		<svg viewBox="0 0 16 16" aria-hidden="true" fill="currentColor" {...props}>
20 | 			<path
21 | 				fillRule="evenodd"
22 | 				clipRule="evenodd"
23 | 				d="M2.5 3a.5.5 0 0 1 .5-.5h.5c5.523 0 10 4.477 10 10v.5a.5.5 0 0 1-.5.5h-.5a.5.5 0 0 1-.5-.5v-.5A8.5 8.5 0 0 0 3.5 4H3a.5.5 0 0 1-.5-.5V3Zm0 4.5A.5.5 0 0 1 3 7h.5A5.5 5.5 0 0 1 9 12.5v.5a.5.5 0 0 1-.5.5H8a.5.5 0 0 1-.5-.5v-.5a4 4 0 0 0-4-4H3a.5.5 0 0 1-.5-.5v-.5Zm0 5a1 1 0 1 1 2 0 1 1 0 0 1-2 0Z"
24 | 			/>
25 | 		</svg>
26 | 	);
27 | }
28 | 
29 | export function XIcon(props: React.ComponentPropsWithoutRef<"svg">) {
30 | 	return (
31 | 		<svg viewBox="0 0 16 16" aria-hidden="true" fill="currentColor" {...props}>
32 | 			<path d="M9.51762 6.77491L15.3459 0H13.9648L8.90409 5.88256L4.86212 0H0.200195L6.31244 8.89547L0.200195 16H1.58139L6.92562 9.78782L11.1942 16H15.8562L9.51728 6.77491H9.51762ZM7.62588 8.97384L7.00658 8.08805L2.07905 1.03974H4.20049L8.17706 6.72795L8.79636 7.61374L13.9654 15.0075H11.844L7.62588 8.97418V8.97384Z" />
33 | 		</svg>
34 | 	);
35 | }
36 | 
```

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

```typescript
 1 | "use client";
 2 | 
 3 | import * as React from "react";
 4 | import { DayPicker } from "react-day-picker";
 5 | 
 6 | import { cn } from "@/lib/utils";
 7 | import { buttonVariants } from "@/components/ui/button";
 8 | 
 9 | function Calendar({
10 | 	className,
11 | 	classNames,
12 | 	showOutsideDays = true,
13 | 	...props
14 | }: React.ComponentProps<typeof DayPicker>) {
15 | 	return (
16 | 		<DayPicker
17 | 			showOutsideDays={showOutsideDays}
18 | 			className={cn("p-3", className)}
19 | 			classNames={{
20 | 				months: "flex flex-col sm:flex-row gap-2",
21 | 				month: "flex flex-col gap-4",
22 | 				caption: "flex justify-center pt-1 relative items-center w-full",
23 | 				caption_label: "text-sm font-medium",
24 | 				nav: "flex items-center gap-1",
25 | 				nav_button: cn(
26 | 					buttonVariants({ variant: "outline" }),
27 | 					"size-7 bg-transparent p-0 opacity-50 hover:opacity-100",
28 | 				),
29 | 				nav_button_previous: "absolute left-1",
30 | 				nav_button_next: "absolute right-1",
31 | 				table: "w-full border-collapse space-x-1",
32 | 				head_row: "flex",
33 | 				head_cell:
34 | 					"text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
35 | 				row: "flex w-full mt-2",
36 | 				cell: cn(
37 | 					"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-range-end)]:rounded-r-md",
38 | 					props.mode === "range"
39 | 						? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
40 | 						: "[&:has([aria-selected])]:rounded-md",
41 | 				),
42 | 				day: cn(
43 | 					buttonVariants({ variant: "ghost" }),
44 | 					"size-8 p-0 font-normal aria-selected:opacity-100",
45 | 				),
46 | 				day_range_start:
47 | 					"day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground",
48 | 				day_range_end:
49 | 					"day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground",
50 | 				day_selected:
51 | 					"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
52 | 				day_today: "bg-accent text-accent-foreground",
53 | 				day_outside:
54 | 					"day-outside text-muted-foreground aria-selected:text-muted-foreground",
55 | 				day_disabled: "text-muted-foreground opacity-50",
56 | 				day_range_middle:
57 | 					"aria-selected:bg-accent aria-selected:text-accent-foreground",
58 | 				day_hidden: "invisible",
59 | 				...classNames,
60 | 			}}
61 | 			{...props}
62 | 		/>
63 | 	);
64 | }
65 | 
66 | export { Calendar };
67 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/captcha/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import type { BetterAuthPlugin } from "@better-auth/core";
 2 | import type { CaptchaOptions } from "./types";
 3 | import { defaultEndpoints, Providers, siteVerifyMap } from "./constants";
 4 | import { EXTERNAL_ERROR_CODES, INTERNAL_ERROR_CODES } from "./error-codes";
 5 | import { middlewareResponse } from "../../utils/middleware-response";
 6 | import * as verifyHandlers from "./verify-handlers";
 7 | 
 8 | export const captcha = (options: CaptchaOptions) =>
 9 | 	({
10 | 		id: "captcha",
11 | 		onRequest: async (request, ctx) => {
12 | 			try {
13 | 				const endpoints = options.endpoints?.length
14 | 					? options.endpoints
15 | 					: defaultEndpoints;
16 | 
17 | 				if (!endpoints.some((endpoint) => request.url.includes(endpoint)))
18 | 					return undefined;
19 | 
20 | 				if (!options.secretKey) {
21 | 					throw new Error(INTERNAL_ERROR_CODES.MISSING_SECRET_KEY);
22 | 				}
23 | 
24 | 				const captchaResponse = request.headers.get("x-captcha-response");
25 | 				const remoteUserIP =
26 | 					request.headers.get("x-captcha-user-remote-ip") ?? undefined;
27 | 
28 | 				if (!captchaResponse) {
29 | 					return middlewareResponse({
30 | 						message: EXTERNAL_ERROR_CODES.MISSING_RESPONSE,
31 | 						status: 400,
32 | 					});
33 | 				}
34 | 
35 | 				const siteVerifyURL =
36 | 					options.siteVerifyURLOverride || siteVerifyMap[options.provider];
37 | 
38 | 				const handlerParams = {
39 | 					siteVerifyURL,
40 | 					captchaResponse,
41 | 					secretKey: options.secretKey,
42 | 					remoteIP: remoteUserIP,
43 | 				};
44 | 
45 | 				if (options.provider === Providers.CLOUDFLARE_TURNSTILE) {
46 | 					return await verifyHandlers.cloudflareTurnstile(handlerParams);
47 | 				}
48 | 
49 | 				if (options.provider === Providers.GOOGLE_RECAPTCHA) {
50 | 					return await verifyHandlers.googleRecaptcha({
51 | 						...handlerParams,
52 | 						minScore: options.minScore,
53 | 					});
54 | 				}
55 | 
56 | 				if (options.provider === Providers.HCAPTCHA) {
57 | 					return await verifyHandlers.hCaptcha({
58 | 						...handlerParams,
59 | 						siteKey: options.siteKey,
60 | 					});
61 | 				}
62 | 
63 | 				if (options.provider === Providers.CAPTCHAFOX) {
64 | 					return await verifyHandlers.captchaFox({
65 | 						...handlerParams,
66 | 						siteKey: options.siteKey,
67 | 					});
68 | 				}
69 | 			} catch (_error) {
70 | 				const errorMessage =
71 | 					_error instanceof Error ? _error.message : undefined;
72 | 
73 | 				ctx.logger.error(errorMessage ?? "Unknown error", {
74 | 					endpoint: request.url,
75 | 					message: _error,
76 | 				});
77 | 
78 | 				return middlewareResponse({
79 | 					message: EXTERNAL_ERROR_CODES.UNKNOWN_ERROR,
80 | 					status: 500,
81 | 				});
82 | 			}
83 | 		},
84 | 	}) satisfies BetterAuthPlugin;
85 | 
```

--------------------------------------------------------------------------------
/docs/components/api-method-tabs.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | "use client";
 2 | 
 3 | import * as React from "react";
 4 | 
 5 | import { cn } from "@/lib/utils";
 6 | 
 7 | const provider = React.createContext<{
 8 | 	current: string | null;
 9 | 	setCurrent: (value: string | null) => void;
10 | }>({
11 | 	current: null,
12 | 	setCurrent: () => {},
13 | });
14 | 
15 | function ApiMethodTabs({
16 | 	className,
17 | 	...props
18 | }: React.ComponentProps<"div"> & { defaultValue: string | null }) {
19 | 	const [current, setCurrent] = React.useState<string | null>(
20 | 		props.defaultValue || null,
21 | 	);
22 | 	return (
23 | 		<provider.Provider value={{ current, setCurrent }}>
24 | 			<div
25 | 				data-slot="tabs"
26 | 				className={cn("flex flex-col gap-2", className)}
27 | 				{...props}
28 | 			/>
29 | 		</provider.Provider>
30 | 	);
31 | }
32 | 
33 | const useApiMethodTabs = () => {
34 | 	return React.useContext(provider);
35 | };
36 | 
37 | function ApiMethodTabsList({
38 | 	className,
39 | 	...props
40 | }: React.ComponentProps<"div">) {
41 | 	return (
42 | 		<div
43 | 			data-slot="tabs-list"
44 | 			className={cn(
45 | 				"inline-flex justify-center items-center p-1 h-9 rounded-lg bg-muted text-muted-foreground w-fit",
46 | 				className,
47 | 			)}
48 | 			{...props}
49 | 		/>
50 | 	);
51 | }
52 | 
53 | function ApiMethodTabsTrigger({
54 | 	className,
55 | 	...props
56 | }: React.ComponentProps<"button"> & { value: string }) {
57 | 	const { setCurrent, current } = useApiMethodTabs();
58 | 	return (
59 | 		<button
60 | 			data-slot="tabs-trigger"
61 | 			className={cn(
62 | 				"data-[state=active]:bg-background data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring inline-flex flex-1 items-center justify-center gap-1.5 rounded-md px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
63 | 				className,
64 | 			)}
65 | 			data-state={props.value === current ? "active" : "inactive"}
66 | 			onClick={() => {
67 | 				setCurrent(props.value);
68 | 			}}
69 | 			{...props}
70 | 		/>
71 | 	);
72 | }
73 | 
74 | function ApiMethodTabsContent({
75 | 	className,
76 | 	...props
77 | }: React.ComponentProps<"div"> & { value: string }) {
78 | 	const { current } = useApiMethodTabs();
79 | 	return (
80 | 		<div
81 | 			data-slot="tabs-content"
82 | 			className={cn(
83 | 				"flex-1 outline-none",
84 | 				className,
85 | 				props.value === current && "block",
86 | 				props.value !== current && "hidden",
87 | 			)}
88 | 			{...props}
89 | 		/>
90 | 	);
91 | }
92 | 
93 | export {
94 | 	ApiMethodTabs,
95 | 	ApiMethodTabsList,
96 | 	ApiMethodTabsTrigger,
97 | 	ApiMethodTabsContent,
98 | };
99 | 
```

--------------------------------------------------------------------------------
/docs/app/layout.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import { Navbar } from "@/components/nav-bar";
 2 | import "./global.css";
 3 | import { RootProvider } from "fumadocs-ui/provider";
 4 | import type { ReactNode } from "react";
 5 | import { NavbarProvider } from "@/components/nav-mobile";
 6 | import { GeistMono } from "geist/font/mono";
 7 | import { GeistSans } from "geist/font/sans";
 8 | import { baseUrl, createMetadata } from "@/lib/metadata";
 9 | import { Analytics } from "@vercel/analytics/react";
10 | import { ThemeProvider } from "@/components/theme-provider";
11 | import { Toaster } from "@/components/ui/sonner";
12 | import { CustomSearchDialog } from "@/components/search-dialog";
13 | import { AnchorScroll } from "@/components/anchor-scroll-fix";
14 | 
15 | export const metadata = createMetadata({
16 | 	title: {
17 | 		template: "%s | Better Auth",
18 | 		default: "Better Auth",
19 | 	},
20 | 	description: "The most comprehensive authentication library for TypeScript.",
21 | 	metadataBase: baseUrl,
22 | });
23 | 
24 | export default function Layout({ children }: { children: ReactNode }) {
25 | 	return (
26 | 		<html lang="en" suppressHydrationWarning>
27 | 			<head>
28 | 				<link rel="icon" href="/favicon/favicon.ico" sizes="any" />
29 | 				<script
30 | 					dangerouslySetInnerHTML={{
31 | 						__html: `
32 |                     try {
33 |                       if (localStorage.theme === 'dark' || ((!('theme' in localStorage) || localStorage.theme === 'system') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
34 |                         document.querySelector('meta[name="theme-color"]').setAttribute('content')
35 |                       }
36 |                     } catch (_) {}
37 |                   `,
38 | 					}}
39 | 				/>
40 | 			</head>
41 | 			<body
42 | 				className={`${GeistSans.variable} ${GeistMono.variable} bg-background font-sans relative `}
43 | 			>
44 | 				<ThemeProvider
45 | 					attribute="class"
46 | 					defaultTheme="dark"
47 | 					enableSystem
48 | 					disableTransitionOnChange
49 | 				>
50 | 					<RootProvider
51 | 						theme={{
52 | 							enableSystem: true,
53 | 							defaultTheme: "dark",
54 | 						}}
55 | 						search={{
56 | 							enabled: true,
57 | 							SearchDialog: process.env.ORAMA_PRIVATE_API_KEY
58 | 								? CustomSearchDialog
59 | 								: undefined,
60 | 						}}
61 | 					>
62 | 						<AnchorScroll />
63 | 						<NavbarProvider>
64 | 							<Navbar />
65 | 							{children}
66 | 							<Toaster
67 | 								toastOptions={{
68 | 									style: {
69 | 										borderRadius: "0px",
70 | 										fontSize: "11px",
71 | 									},
72 | 								}}
73 | 							/>
74 | 						</NavbarProvider>
75 | 					</RootProvider>
76 | 					<Analytics />
77 | 				</ThemeProvider>
78 | 			</body>
79 | 		</html>
80 | 	);
81 | }
82 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/integrations/next-js.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import type { BetterAuthPlugin } from "@better-auth/core";
 2 | import { parseSetCookieHeader } from "../cookies";
 3 | import { createAuthMiddleware } from "@better-auth/core/api";
 4 | 
 5 | export function toNextJsHandler(
 6 | 	auth:
 7 | 		| {
 8 | 				handler: (request: Request) => Promise<Response>;
 9 | 		  }
10 | 		| ((request: Request) => Promise<Response>),
11 | ) {
12 | 	const handler = async (request: Request) => {
13 | 		return "handler" in auth ? auth.handler(request) : auth(request);
14 | 	};
15 | 	return {
16 | 		GET: handler,
17 | 		POST: handler,
18 | 	};
19 | }
20 | 
21 | export const nextCookies = () => {
22 | 	return {
23 | 		id: "next-cookies",
24 | 		hooks: {
25 | 			after: [
26 | 				{
27 | 					matcher(ctx) {
28 | 						return true;
29 | 					},
30 | 					handler: createAuthMiddleware(async (ctx) => {
31 | 						const returned = ctx.context.responseHeaders;
32 | 						if ("_flag" in ctx && ctx._flag === "router") {
33 | 							return;
34 | 						}
35 | 						if (returned instanceof Headers) {
36 | 							const setCookies = returned?.get("set-cookie");
37 | 							if (!setCookies) return;
38 | 							const parsed = parseSetCookieHeader(setCookies);
39 | 							const { cookies } = await import("next/headers");
40 | 							let cookieHelper: Awaited<ReturnType<typeof cookies>>;
41 | 							try {
42 | 								cookieHelper = await cookies();
43 | 							} catch (error) {
44 | 								if (
45 | 									error instanceof Error &&
46 | 									error.message.startsWith(
47 | 										"`cookies` was called outside a request scope.",
48 | 									)
49 | 								) {
50 | 									// If error it means the `cookies` was called outside request scope.
51 | 									// NextJS docs on this: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context
52 | 									// This often gets called in a monorepo workspace (outside of NextJS),
53 | 									// so we will try to catch this suppress it, and ignore using next-cookies.
54 | 									return;
55 | 								}
56 | 								// If it's an unexpected error, throw it.
57 | 								throw error;
58 | 							}
59 | 							parsed.forEach((value, key) => {
60 | 								if (!key) return;
61 | 								const opts = {
62 | 									sameSite: value.samesite,
63 | 									secure: value.secure,
64 | 									maxAge: value["max-age"],
65 | 									httpOnly: value.httponly,
66 | 									domain: value.domain,
67 | 									path: value.path,
68 | 								} as const;
69 | 								try {
70 | 									cookieHelper.set(key, decodeURIComponent(value.value), opts);
71 | 								} catch (e) {
72 | 									// this will fail if the cookie is being set on server component
73 | 								}
74 | 							});
75 | 							return;
76 | 						}
77 | 					}),
78 | 				},
79 | 			],
80 | 		},
81 | 	} satisfies BetterAuthPlugin;
82 | };
83 | 
```
Page 7/67FirstPrevNextLast