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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/packages/better-auth/src/adapters/adapter-factory/types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type {
  2 | 	DBFieldAttribute,
  3 | 	BetterAuthDBSchema,
  4 | } from "@better-auth/core/db";
  5 | import type {
  6 | 	DBAdapterFactoryConfig,
  7 | 	CustomAdapter as CoreCustomAdapter,
  8 | } from "@better-auth/core/db/adapter";
  9 | import type {
 10 | 	AdapterSchemaCreation,
 11 | 	TransactionAdapter,
 12 | 	Where,
 13 | } from "../../types";
 14 | import type { BetterAuthOptions } from "@better-auth/core";
 15 | import type { Prettify } from "../../types/helper";
 16 | 
 17 | export type AdapterFactoryOptions = {
 18 | 	config: AdapterFactoryConfig;
 19 | 	adapter: AdapterFactoryCustomizeAdapterCreator;
 20 | };
 21 | 
 22 | /**
 23 |  * @deprecated Use `DBAdapterFactoryConfig` from `@better-auth/core/db/adapter` instead.
 24 |  */
 25 | export interface AdapterFactoryConfig
 26 | 	extends Omit<DBAdapterFactoryConfig<BetterAuthOptions>, "transaction"> {
 27 | 	/**
 28 | 	 * Execute multiple operations in a transaction.
 29 | 	 *
 30 | 	 * If the database doesn't support transactions, set this to `false` and operations will be executed sequentially.
 31 | 	 *
 32 | 	 * @default false
 33 | 	 */
 34 | 	transaction?:
 35 | 		| false
 36 | 		| (<R>(callback: (trx: TransactionAdapter) => Promise<R>) => Promise<R>);
 37 | }
 38 | 
 39 | export type AdapterFactoryCustomizeAdapterCreator = (config: {
 40 | 	options: BetterAuthOptions;
 41 | 	/**
 42 | 	 * The schema of the user's Better-Auth instance.
 43 | 	 */
 44 | 	schema: BetterAuthDBSchema;
 45 | 	/**
 46 | 	 * The debug log function.
 47 | 	 *
 48 | 	 * If the config has defined `debugLogs` as `false`, no logs will be shown.
 49 | 	 */
 50 | 	debugLog: (...args: any[]) => void;
 51 | 	/**
 52 | 	 * Get the model name which is expected to be saved in the database based on the user's schema.
 53 | 	 */
 54 | 	getModelName: (model: string) => string;
 55 | 	/**
 56 | 	 * Get the field name which is expected to be saved in the database based on the user's schema.
 57 | 	 */
 58 | 	getFieldName: ({ model, field }: { model: string; field: string }) => string;
 59 | 	/**
 60 | 	 * This function helps us get the default model name from the schema defined by devs.
 61 | 	 * Often times, the user will be using the `modelName` which could had been customized by the users.
 62 | 	 * This function helps us get the actual model name useful to match against the schema. (eg: schema[model])
 63 | 	 *
 64 | 	 * If it's still unclear what this does:
 65 | 	 *
 66 | 	 * 1. User can define a custom modelName.
 67 | 	 * 2. When using a custom modelName, doing something like `schema[model]` will not work.
 68 | 	 * 3. Using this function helps us get the actual model name based on the user's defined custom modelName.
 69 | 	 * 4. Thus allowing us to use `schema[model]`.
 70 | 	 */
 71 | 	getDefaultModelName: (model: string) => string;
 72 | 	/**
 73 | 	 * This function helps us get the default field name from the schema defined by devs.
 74 | 	 * Often times, the user will be using the `fieldName` which could had been customized by the users.
 75 | 	 * This function helps us get the actual field name useful to match against the schema. (eg: schema[model].fields[field])
 76 | 	 *
 77 | 	 * If it's still unclear what this does:
 78 | 	 *
 79 | 	 * 1. User can define a custom fieldName.
 80 | 	 * 2. When using a custom fieldName, doing something like `schema[model].fields[field]` will not work.
 81 | 	 *
 82 | 	 */
 83 | 	getDefaultFieldName: ({
 84 | 		model,
 85 | 		field,
 86 | 	}: {
 87 | 		model: string;
 88 | 		field: string;
 89 | 	}) => string;
 90 | 	/**
 91 | 	 * Get the field attributes for a given model and field.
 92 | 	 *
 93 | 	 * Note: any model name or field name is allowed, whether default to schema or not.
 94 | 	 */
 95 | 	getFieldAttributes: ({
 96 | 		model,
 97 | 		field,
 98 | 	}: {
 99 | 		model: string;
100 | 		field: string;
101 | 	}) => DBFieldAttribute;
102 | 	// The following functions are exposed primarily for the purpose of having wrapper adapters.
103 | 	transformInput: (
104 | 		data: Record<string, any>,
105 | 		defaultModelName: string,
106 | 		action: "create" | "update",
107 | 		forceAllowId?: boolean,
108 | 	) => Promise<Record<string, any>>;
109 | 	transformOutput: (
110 | 		data: Record<string, any>,
111 | 		defaultModelName: string,
112 | 		select?: string[],
113 | 	) => Promise<Record<string, any>>;
114 | 	transformWhereClause: <W extends Where[] | undefined>({
115 | 		model,
116 | 		where,
117 | 	}: {
118 | 		where: W;
119 | 		model: string;
120 | 	}) => W extends undefined ? undefined : CleanedWhere[];
121 | }) => CustomAdapter;
122 | 
123 | /**
124 |  * @deprecated Use `CustomAdapter` from `@better-auth/core/db/adapter` instead.
125 |  */
126 | export interface CustomAdapter extends Omit<CoreCustomAdapter, "createSchema"> {
127 | 	createSchema?: (props: {
128 | 		/**
129 | 		 * The file the user may have passed in to the `generate` command as the expected schema file output path.
130 | 		 */
131 | 		file?: string;
132 | 		/**
133 | 		 * The tables from the user's Better-Auth instance schema.
134 | 		 */
135 | 		tables: BetterAuthDBSchema;
136 | 	}) => Promise<AdapterSchemaCreation>;
137 | }
138 | 
139 | /**
140 |  * @deprecated Use `CleanedWhere` from `@better-auth/core/db/adapter` instead.
141 |  */
142 | export type CleanedWhere = Prettify<Required<Where>>;
143 | 
144 | export type AdapterTestDebugLogs = {
145 | 	resetDebugLogs: () => void;
146 | 	printDebugLogs: () => void;
147 | };
148 | 
149 | /**
150 |  * @deprecated Use `AdapterFactoryOptions` instead. This export will be removed in a future version.
151 |  */
152 | export type CreateAdapterOptions = AdapterFactoryOptions;
153 | 
154 | /**
155 |  * @deprecated Use `AdapterFactoryConfig` instead. This export will be removed in a future version.
156 |  */
157 | export type AdapterConfig = AdapterFactoryConfig;
158 | 
159 | /**
160 |  * @deprecated Use `AdapterFactoryCustomizeAdapterCreator` instead. This export will be removed in a future version.
161 |  */
162 | export type CreateCustomAdapter = AdapterFactoryCustomizeAdapterCreator;
163 | 
```

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

```markdown
  1 | ---
  2 | title: Resources
  3 | description: A curated collection of resources to help you learn and master Better Auth.
  4 | ---
  5 | 
  6 | import { Resource } from "@/components/resource-section";
  7 | 
  8 | A curated collection of resources to help you learn and master Better Auth. From blog posts to video tutorials, find everything you need to get started.
  9 | 
 10 | ## Video tutorials 
 11 | 
 12 | <Resource resources={
 13 |     [
 14 | 		{
 15 | 			title: "The State of Authentication",
 16 | 			description:
 17 | 				"<strong>Theo(t3.gg)</strong> explores the current landscape of authentication, discussing trends, challenges, and where the industry is heading.",
 18 | 			href: "https://www.youtube.com/watch?v=lxslnp-ZEMw",
 19 | 			tags: ["trends", "showcase", "review"],
 20 | 		},
 21 | 		{
 22 | 			title: "Last Authentication You Will Ever Need",
 23 | 			description:
 24 | 				"A comprehensive tutorial demonstrating why Better Auth could be the final authentication solution you'll need for your projects.",
 25 | 			href: "https://www.youtube.com/watch?v=hFtufpaMcLM",
 26 | 			tags: ["implementation", "showcase"],
 27 | 		},
 28 | 		{
 29 | 			title: "This Might Be My New Favourite Auth Library",
 30 | 			description:
 31 | 				"<strong>developedbyed</strong> explores the features and capabilities of Better Auth, explaining why it stands out among authentication libraries.",
 32 | 			href: "https://www.youtube.com/watch?v=Hjs3zM7o7NE",
 33 | 			tags: ["review", "showcase"],
 34 | 		},
 35 | 	 	{
 36 | 			title: "8 Reasons To Try Better Auth",
 37 | 			description:
 38 | 				"<strong>CJ</strong> presents 8 compelling reasons why Better Auth is the BEST auth framework he's ever used, demonstrating its superior features and ease of implementation.",
 39 | 			href: "https://www.youtube.com/watch?v=_OApmLmex14",
 40 | 			tags: ["review", "showcase", "implementation"],
 41 | 		},
 42 | 		{
 43 | 			title: "Better Auth is so good that I almost switched programming languages",
 44 | 			description:
 45 | 				"<strong>Dreams of Code</strong> reviews Better Auth's features that nearly made them switch languages.",
 46 | 			href: "https://www.youtube.com/watch?v=dNY4FKXwTsM",
 47 | 			tags: ["review", "showcase", "implementation"],
 48 | 		},
 49 | 		{
 50 | 			title: "Best authentication framework for next.js",
 51 | 			description:
 52 | 				"A detailed comparison of authentication frameworks for Next.js, highlighting why Better Auth might be your best choice.",
 53 | 			href: "https://www.youtube.com/watch?v=V--T0q9FrEw",
 54 | 			tags: ["nextjs", "comparison"],
 55 | 		},
 56 | 		{
 57 | 			title: "Better-Auth: A First Look",
 58 | 			description:
 59 | 				"An introductory overview and demonstration of Better Auth's core features and capabilities.",
 60 | 			href: "https://www.youtube.com/watch?v=2cQTV6NYxis",
 61 | 			tags: ["implementation", "showcase"],
 62 | 		},
 63 | 		{
 64 | 			title: "Stripe was never so easy (with better auth)",
 65 | 			description: "A tutorial on how to integrate Stripe with Better Auth.",
 66 | 			href: "https://www.youtube.com/watch?v=g-RIrzBEX6M",
 67 | 			tags: ["implementation"],
 68 | 		},
 69 | 		{
 70 | 			title: "Nextjs 15 Authentication Made EASY with Better Auth",
 71 | 			description:
 72 | 				"A practical guide showing how to seamlessly integrate Better Auth with Next.js 15 for robust authentication.",
 73 | 			href: "https://www.youtube.com/watch?v=lxslnp-ZEMw",
 74 | 			tags: ["nextjs", "implementation", "tutorial"],
 75 | 		},
 76 | 		{
 77 | 			title: "Better Auth: Headless Authentication for Your TanStack Start App",
 78 | 			description: "<strong>Jack</strong> demonstrates how to implement headless authentication in your TanStack Start application using Better Auth, providing a modern approach to auth.",
 79 | 			href: "https://www.youtube.com/watch?v=Atev8Nxpw7c", 
 80 | 			tags: ["tanstack", "implementation"],
 81 | 		},
 82 | 		{
 83 | 			title: "Goodbye Clerk, Hello Better Auth – Full Migration Guide!",
 84 | 			description: "A comprehensive guide showing how to migrate your authentication from Clerk to Better Auth, with step-by-step instructions and best practices.",
 85 | 			href: "https://www.youtube.com/watch?v=Za_QihbDSuk",
 86 | 			tags: ["migration", "clerk", "tutorial"],
 87 | 		},
 88 |     ]
 89 | } />
 90 | 
 91 | ## Blog posts
 92 | 
 93 | <Resource resources={
 94 |     [
 95 |         {
 96 | 			title: "Better Auth with Hono, Bun, TypeScript, React and Vite",
 97 | 			description:
 98 | 				"You'll learn how to implement authentication with Better Auth in a client - server architecture, where the frontend is separate from the backend.",
 99 | 			href: "https://catalins.tech/better-auth-with-hono-bun-typescript-react-vite",
100 | 			tags: ["typescript", "react", "bun", "vite"],
101 | 		},
102 | 		{
103 | 			title: "Polar.sh + BetterAuth for Organizations",
104 | 			description:
105 | 				"Polar.sh is a platform for building payment integrations. This article will show you how to use Better Auth to authenticate your users.",
106 | 			href: "https://dev.to/phumudzosly/polarsh-betterauth-for-organizations-1j1b",
107 | 			tags: ["organizations", "integration", "payments"],
108 | 		},
109 | 		{
110 | 			title: "Authenticating users in Astro with Better Auth",
111 | 			description:
112 | 				"Step by step guide on how to authenticate users in Astro with Better Auth.",
113 | 			href: "https://www.launchfa.st/blog/astro-better-auth",
114 | 			tags: ["astro", "integration", "tutorial"],
115 | 		},
116 | 		{
117 | 			title: "Building Multi-Tenant Apps With Better-Auth and ZenStack",
118 | 			description:
119 | 				"Learn how to build multi-tenant apps with Better-Auth and ZenStack.",
120 | 			href: "https://zenstack.dev/blog/better-auth",
121 | 			tags: ["multi-tenant", "zenstack", "architecture"],
122 | 		},
123 |     ]
124 | } />
```

--------------------------------------------------------------------------------
/docs/app/changelogs/_components/changelog-layout.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import Link from "next/link";
  2 | import { useId } from "react";
  3 | 
  4 | import clsx from "clsx";
  5 | import { DiscordLogoIcon } from "@radix-ui/react-icons";
  6 | 
  7 | function BookIcon(props: React.ComponentPropsWithoutRef<"svg">) {
  8 | 	return (
  9 | 		<svg viewBox="0 0 16 16" aria-hidden="true" fill="currentColor" {...props}>
 10 | 			<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" />
 11 | 		</svg>
 12 | 	);
 13 | }
 14 | 
 15 | function GitHubIcon(props: React.ComponentPropsWithoutRef<"svg">) {
 16 | 	return (
 17 | 		<svg viewBox="0 0 16 16" aria-hidden="true" fill="currentColor" {...props}>
 18 | 			<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" />
 19 | 		</svg>
 20 | 	);
 21 | }
 22 | 
 23 | function FeedIcon(props: React.ComponentPropsWithoutRef<"svg">) {
 24 | 	return (
 25 | 		<svg viewBox="0 0 16 16" aria-hidden="true" fill="currentColor" {...props}>
 26 | 			<path
 27 | 				fillRule="evenodd"
 28 | 				clipRule="evenodd"
 29 | 				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"
 30 | 			/>
 31 | 		</svg>
 32 | 	);
 33 | }
 34 | 
 35 | function XIcon(props: React.ComponentPropsWithoutRef<"svg">) {
 36 | 	return (
 37 | 		<svg viewBox="0 0 16 16" aria-hidden="true" fill="currentColor" {...props}>
 38 | 			<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" />
 39 | 		</svg>
 40 | 	);
 41 | }
 42 | 
 43 | export function Intro() {
 44 | 	return (
 45 | 		<>
 46 | 			<h1 className="mt-14  font-sans  font-semibold tracking-tighter text-5xl">
 47 | 				All of the changes made will be{" "}
 48 | 				<span className="">available here.</span>
 49 | 			</h1>
 50 | 			<p className="mt-4 text-sm text-gray-600 dark:text-gray-300">
 51 | 				Better Auth is comprehensive authentication library for TypeScript that
 52 | 				provides a wide range of features to make authentication easier and more
 53 | 				secure.
 54 | 			</p>
 55 | 			<hr className="h-px bg-gray-300 mt-5" />
 56 | 			<div className="mt-8 flex flex-wrap text-gray-600 dark:text-gray-300  justify-center gap-x-1 gap-y-3 sm:gap-x-2 lg:justify-start">
 57 | 				<IconLink
 58 | 					href="/docs"
 59 | 					icon={BookIcon}
 60 | 					className="flex-none text-gray-600 dark:text-gray-300"
 61 | 				>
 62 | 					Documentation
 63 | 				</IconLink>
 64 | 				<IconLink
 65 | 					href="https://github.com/better-auth/better-auth"
 66 | 					icon={GitHubIcon}
 67 | 					className="flex-none text-gray-600 dark:text-gray-300"
 68 | 				>
 69 | 					GitHub
 70 | 				</IconLink>
 71 | 				<IconLink
 72 | 					href="https://discord.gg/better-auth"
 73 | 					icon={DiscordLogoIcon}
 74 | 					className="flex-none text-gray-600 dark:text-gray-300"
 75 | 				>
 76 | 					Community
 77 | 				</IconLink>
 78 | 			</div>
 79 | 		</>
 80 | 	);
 81 | }
 82 | 
 83 | export function IntroFooter() {
 84 | 	return (
 85 | 		<p className="flex items-baseline gap-x-2 text-[0.8125rem]/6 text-gray-500">
 86 | 			Brought to you by{" "}
 87 | 			<IconLink href="#" icon={XIcon} compact>
 88 | 				BETTER-AUTH.
 89 | 			</IconLink>
 90 | 		</p>
 91 | 	);
 92 | }
 93 | 
 94 | export function SignUpForm() {
 95 | 	let id = useId();
 96 | 
 97 | 	return (
 98 | 		<form className="relative isolate mt-8 flex items-center pr-1">
 99 | 			<label htmlFor={id} className="sr-only">
100 | 				Email address
101 | 			</label>
102 | 
103 | 			<div className="absolute inset-0 -z-10 rounded-lg transition peer-focus:ring-4 peer-focus:ring-sky-300/15" />
104 | 			<div className="absolute inset-0 -z-10 rounded-lg bg-white/2.5 ring-1 ring-white/15 transition peer-focus:ring-sky-300" />
105 | 		</form>
106 | 	);
107 | }
108 | 
109 | export function IconLink({
110 | 	children,
111 | 	className,
112 | 	compact = false,
113 | 	icon: Icon,
114 | 	...props
115 | }: React.ComponentPropsWithoutRef<typeof Link> & {
116 | 	compact?: boolean;
117 | 	icon?: React.ComponentType<{ className?: string }>;
118 | }) {
119 | 	return (
120 | 		<Link
121 | 			{...props}
122 | 			className={clsx(
123 | 				className,
124 | 				"group relative isolate flex items-center px-2 py-0.5 text-[0.8125rem]/6 font-medium text-black/70 dark:text-white/30 transition-colors hover:text-stone-300 rounded-none",
125 | 				compact ? "gap-x-2" : "gap-x-3",
126 | 			)}
127 | 		>
128 | 			<span className="absolute inset-0 -z-10 scale-75 rounded-lg bg-white/5 opacity-0 transition group-hover:scale-100 group-hover:opacity-100" />
129 | 			{Icon && <Icon className="h-4 w-4 flex-none" />}
130 | 			<span className="self-baseline text-black/70 dark:text-white">
131 | 				{children}
132 | 			</span>
133 | 		</Link>
134 | 	);
135 | }
136 | 
```

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

```markdown
  1 | ---
  2 | title: Contributing to BetterAuth
  3 | description: A concise guide to contributing to BetterAuth
  4 | ---
  5 | 
  6 | Thank you for your interest in contributing to Better Auth! This guide is a concise guide to contributing to Better Auth.
  7 | 
  8 | ## Getting Started
  9 | 
 10 | Before diving in, here are a few important resources:
 11 | 
 12 | - Take a look at our existing <Link href="https://github.com/better-auth/better-auth/issues">issues</Link> and <Link href="https://github.com/better-auth/better-auth/pulls">pull requests</Link>
 13 | - Join our community discussions in <Link href="https://discord.gg/better-auth">Discord</Link>
 14 | 
 15 | 
 16 | ## Development Setup
 17 | 
 18 | To get started with development:
 19 | 
 20 | <Callout type="warn">
 21 |   Make sure you have <Link href="https://nodejs.org/en/download">Node.JS</Link>{" "}
 22 |   installed, preferably on LTS.
 23 | </Callout>
 24 | 
 25 | <Steps>
 26 | 
 27 |     <Step>
 28 |         ### 1. Fork the repository
 29 | 
 30 |         Visit https://github.com/better-auth/better-auth
 31 | 
 32 |         Click the "Fork" button in the top right.
 33 | 
 34 |     </Step>
 35 | 
 36 |     <Step>
 37 |         ### 2. Clone your fork
 38 | 
 39 |         ```bash
 40 |         # Replace YOUR-USERNAME with your GitHub username
 41 |         git clone https://github.com/YOUR-USERNAME/better-auth.git
 42 |         cd better-auth
 43 |         ```
 44 |     </Step>
 45 | 
 46 |     <Step>
 47 |         ### 3. Install dependencies
 48 | 
 49 |         Make sure you have <Link href="https://pnpm.io/installation">pnpm</Link> installed!
 50 | 
 51 |         ```bash
 52 |         pnpm install
 53 |         ```
 54 |     </Step>
 55 | 
 56 |     <Step>
 57 |         ### 4. Prepare ENV files
 58 | 
 59 |         Copy the example env file to create your new `.env` file.
 60 | 
 61 |         ```bash
 62 |         cp -n ./docs/.env.example ./docs/.env
 63 |         ```
 64 |     </Step>
 65 | 
 66 | </Steps>
 67 | 
 68 | ## Making changes
 69 | 
 70 | Once you have an idea of what you want to contribute, you can start making changes. Here are some steps to get started:
 71 | 
 72 | <Steps>
 73 |     <Step>
 74 |         ### 1. Create a new branch
 75 | 
 76 |         ```bash
 77 |         # Make sure you're on main
 78 |         git checkout main
 79 | 
 80 |         # Pull latest changes
 81 |         git pull upstream main
 82 | 
 83 |         # Create and switch to a new branch
 84 |         git checkout -b feature/your-feature-name
 85 |         ```
 86 |     </Step>
 87 |     <Step>
 88 |         ### 2. Start development server
 89 | 
 90 |         Start the development server:
 91 | 
 92 |         ```bash
 93 |         pnpm dev
 94 |         ```
 95 | 
 96 |         To start the docs server:
 97 | 
 98 |         ```bash
 99 |         pnpm -F docs dev
100 |         ```
101 |     </Step>
102 |     <Step>
103 |         ### 3. Make Your Changes
104 | 
105 |         * Make your changes to the codebase.
106 | 
107 |         * Write tests if needed. (Read more about testing <Link href="/docs/reference/contributing#testing">here</Link>)
108 | 
109 |         * Update documentation.  (Read more about documenting <Link href="/docs/reference/contributing#documentation">here</Link>)
110 | 
111 |     </Step>
112 | 
113 | </Steps>
114 | 
115 | 
116 | ### Issues and Bug Fixes
117 | 
118 | - Check our [GitHub issues](https://github.com/better-auth/better-auth/issues) for tasks labeled `good first issue`
119 | - When reporting bugs, include steps to reproduce and expected behavior
120 | - Comment on issues you'd like to work on to avoid duplicate efforts
121 | 
122 | ### Framework Integrations
123 | 
124 | We welcome contributions to support more frameworks:
125 | 
126 | - Focus on framework-agnostic solutions where possible
127 | - Keep integrations minimal and maintainable
128 | - All integrations currently live in the main package
129 | 
130 | ### Plugin Development
131 | 
132 | - For core plugins: Open an issue first to discuss your idea
133 | - For community plugins: Feel free to develop independently
134 | - Follow our plugin architecture guidelines
135 | 
136 | ### Documentation
137 | 
138 | - Fix typos and errors
139 | - Add examples and clarify existing content
140 | - Ensure documentation is up to date with code changes
141 | 
142 | ## Testing
143 | 
144 | We use Vitest for testing. Place test files next to the source files they test:
145 | 
146 | ```ts
147 | import { describe, it, expect } from "vitest";
148 | import { getTestInstance } from "./test-utils/test-instance";
149 | 
150 | describe("Feature", () => {
151 |     it("should work as expected", async () => {
152 |         const { client } = await getTestInstance();
153 |         // Test code here
154 |         expect(result).toBeDefined();
155 |     });
156 | });
157 | ```
158 | 
159 | ### Using the Test Instance Helper
160 | 
161 | The test instance helper now includes improved async context support for managing user sessions:
162 | 
163 | ```ts
164 | const { client, runWithUser, signInWithTestUser } = await getTestInstance();
165 | 
166 | // Run tests with a specific user context
167 | await runWithUser("[email protected]", "password", async (headers) => {
168 |     // All client calls within this block will use the user's session
169 |     const response = await client.getSession();
170 |     // headers are automatically applied
171 | });
172 | 
173 | // Or use the test user with async context
174 | const { runWithDefaultUser } = await signInWithTestUser();
175 | await runWithDefaultUser(async (headers) => {
176 |     // Code here runs with the test user's session context
177 | });
178 | ```
179 | 
180 | ### Testing Best Practices
181 | 
182 | - Write clear commit messages
183 | - Update documentation to reflect your changes
184 | - Add tests for new features
185 | - Follow our coding standards
186 | - Keep pull requests focused on a single change
187 | 
188 | ## Need Help?
189 | 
190 | Don't hesitate to ask for help! You can:
191 | 
192 | - Open an <Link href="https://github.com/better-auth/better-auth/issues">issue</Link> with questions
193 | - Join our <Link href="https://discord.gg/better-auth">community discussions</Link>
194 | - Reach out to project maintainers
195 | 
196 | Thank you for contributing to Better Auth!
```

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

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import useEmblaCarousel, {
  5 | 	type UseEmblaCarouselType,
  6 | } from "embla-carousel-react";
  7 | import { ArrowLeft, ArrowRight } from "lucide-react";
  8 | 
  9 | import { cn } from "@/lib/utils";
 10 | import { Button } from "@/components/ui/button";
 11 | 
 12 | type CarouselApi = UseEmblaCarouselType[1];
 13 | type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
 14 | type CarouselOptions = UseCarouselParameters[0];
 15 | type CarouselPlugin = UseCarouselParameters[1];
 16 | 
 17 | type CarouselProps = {
 18 | 	opts?: CarouselOptions;
 19 | 	plugins?: CarouselPlugin;
 20 | 	orientation?: "horizontal" | "vertical";
 21 | 	setApi?: (api: CarouselApi) => void;
 22 | };
 23 | 
 24 | type CarouselContextProps = {
 25 | 	carouselRef: ReturnType<typeof useEmblaCarousel>[0];
 26 | 	api: ReturnType<typeof useEmblaCarousel>[1];
 27 | 	scrollPrev: () => void;
 28 | 	scrollNext: () => void;
 29 | 	canScrollPrev: boolean;
 30 | 	canScrollNext: boolean;
 31 | } & CarouselProps;
 32 | 
 33 | const CarouselContext = React.createContext<CarouselContextProps | null>(null);
 34 | 
 35 | function useCarousel() {
 36 | 	const context = React.useContext(CarouselContext);
 37 | 
 38 | 	if (!context) {
 39 | 		throw new Error("useCarousel must be used within a <Carousel />");
 40 | 	}
 41 | 
 42 | 	return context;
 43 | }
 44 | 
 45 | function Carousel({
 46 | 	orientation = "horizontal",
 47 | 	opts,
 48 | 	setApi,
 49 | 	plugins,
 50 | 	className,
 51 | 	children,
 52 | 	...props
 53 | }: React.ComponentProps<"div"> & CarouselProps) {
 54 | 	const [carouselRef, api] = useEmblaCarousel(
 55 | 		{
 56 | 			...opts,
 57 | 			axis: orientation === "horizontal" ? "x" : "y",
 58 | 		},
 59 | 		plugins,
 60 | 	);
 61 | 	const [canScrollPrev, setCanScrollPrev] = React.useState(false);
 62 | 	const [canScrollNext, setCanScrollNext] = React.useState(false);
 63 | 
 64 | 	const onSelect = React.useCallback((api: CarouselApi) => {
 65 | 		if (!api) return;
 66 | 		setCanScrollPrev(api.canScrollPrev());
 67 | 		setCanScrollNext(api.canScrollNext());
 68 | 	}, []);
 69 | 
 70 | 	const scrollPrev = React.useCallback(() => {
 71 | 		api?.scrollPrev();
 72 | 	}, [api]);
 73 | 
 74 | 	const scrollNext = React.useCallback(() => {
 75 | 		api?.scrollNext();
 76 | 	}, [api]);
 77 | 
 78 | 	const handleKeyDown = React.useCallback(
 79 | 		(event: React.KeyboardEvent<HTMLDivElement>) => {
 80 | 			if (event.key === "ArrowLeft") {
 81 | 				event.preventDefault();
 82 | 				scrollPrev();
 83 | 			} else if (event.key === "ArrowRight") {
 84 | 				event.preventDefault();
 85 | 				scrollNext();
 86 | 			}
 87 | 		},
 88 | 		[scrollPrev, scrollNext],
 89 | 	);
 90 | 
 91 | 	React.useEffect(() => {
 92 | 		if (!api || !setApi) return;
 93 | 		setApi(api);
 94 | 	}, [api, setApi]);
 95 | 
 96 | 	React.useEffect(() => {
 97 | 		if (!api) return;
 98 | 		onSelect(api);
 99 | 		api.on("reInit", onSelect);
100 | 		api.on("select", onSelect);
101 | 
102 | 		return () => {
103 | 			api?.off("select", onSelect);
104 | 		};
105 | 	}, [api, onSelect]);
106 | 
107 | 	return (
108 | 		<CarouselContext.Provider
109 | 			value={{
110 | 				carouselRef,
111 | 				api: api,
112 | 				opts,
113 | 				orientation:
114 | 					orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
115 | 				scrollPrev,
116 | 				scrollNext,
117 | 				canScrollPrev,
118 | 				canScrollNext,
119 | 			}}
120 | 		>
121 | 			<div
122 | 				onKeyDownCapture={handleKeyDown}
123 | 				className={cn("relative", className)}
124 | 				role="region"
125 | 				aria-roledescription="carousel"
126 | 				data-slot="carousel"
127 | 				{...props}
128 | 			>
129 | 				{children}
130 | 			</div>
131 | 		</CarouselContext.Provider>
132 | 	);
133 | }
134 | 
135 | function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
136 | 	const { carouselRef, orientation } = useCarousel();
137 | 
138 | 	return (
139 | 		<div
140 | 			ref={carouselRef}
141 | 			className="overflow-hidden"
142 | 			data-slot="carousel-content"
143 | 		>
144 | 			<div
145 | 				className={cn(
146 | 					"flex",
147 | 					orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
148 | 					className,
149 | 				)}
150 | 				{...props}
151 | 			/>
152 | 		</div>
153 | 	);
154 | }
155 | 
156 | function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
157 | 	const { orientation } = useCarousel();
158 | 
159 | 	return (
160 | 		<div
161 | 			role="group"
162 | 			aria-roledescription="slide"
163 | 			data-slot="carousel-item"
164 | 			className={cn(
165 | 				"min-w-0 shrink-0 grow-0 basis-full",
166 | 				orientation === "horizontal" ? "pl-4" : "pt-4",
167 | 				className,
168 | 			)}
169 | 			{...props}
170 | 		/>
171 | 	);
172 | }
173 | 
174 | function CarouselPrevious({
175 | 	className,
176 | 	variant = "outline",
177 | 	size = "icon",
178 | 	...props
179 | }: React.ComponentProps<typeof Button>) {
180 | 	const { orientation, scrollPrev, canScrollPrev } = useCarousel();
181 | 
182 | 	return (
183 | 		<Button
184 | 			data-slot="carousel-previous"
185 | 			variant={variant}
186 | 			size={size}
187 | 			className={cn(
188 | 				"absolute size-8 rounded-full",
189 | 				orientation === "horizontal"
190 | 					? "top-1/2 -left-12 -translate-y-1/2"
191 | 					: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
192 | 				className,
193 | 			)}
194 | 			disabled={!canScrollPrev}
195 | 			onClick={scrollPrev}
196 | 			{...props}
197 | 		>
198 | 			<ArrowLeft />
199 | 			<span className="sr-only">Previous slide</span>
200 | 		</Button>
201 | 	);
202 | }
203 | 
204 | function CarouselNext({
205 | 	className,
206 | 	variant = "outline",
207 | 	size = "icon",
208 | 	...props
209 | }: React.ComponentProps<typeof Button>) {
210 | 	const { orientation, scrollNext, canScrollNext } = useCarousel();
211 | 
212 | 	return (
213 | 		<Button
214 | 			data-slot="carousel-next"
215 | 			variant={variant}
216 | 			size={size}
217 | 			className={cn(
218 | 				"absolute size-8 rounded-full",
219 | 				orientation === "horizontal"
220 | 					? "top-1/2 -right-12 -translate-y-1/2"
221 | 					: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
222 | 				className,
223 | 			)}
224 | 			disabled={!canScrollNext}
225 | 			onClick={scrollNext}
226 | 			{...props}
227 | 		>
228 | 			<ArrowRight />
229 | 			<span className="sr-only">Next slide</span>
230 | 		</Button>
231 | 	);
232 | }
233 | 
234 | export {
235 | 	type CarouselApi,
236 | 	Carousel,
237 | 	CarouselContent,
238 | 	CarouselItem,
239 | 	CarouselPrevious,
240 | 	CarouselNext,
241 | };
242 | 
```

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

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  3 | import { refreshAccessToken, validateAuthorizationCode } from "../oauth2";
  4 | export interface DiscordProfile extends Record<string, any> {
  5 | 	/** the user's id (i.e. the numerical snowflake) */
  6 | 	id: string;
  7 | 	/** the user's username, not unique across the platform */
  8 | 	username: string;
  9 | 	/** the user's Discord-tag */
 10 | 	discriminator: string;
 11 | 	/** the user's display name, if it is set  */
 12 | 	global_name: string | null;
 13 | 	/**
 14 | 	 * the user's avatar hash:
 15 | 	 * https://discord.com/developers/docs/reference#image-formatting
 16 | 	 */
 17 | 	avatar: string | null;
 18 | 	/** whether the user belongs to an OAuth2 application */
 19 | 	bot?: boolean;
 20 | 	/**
 21 | 	 * whether the user is an Official Discord System user (part of the urgent
 22 | 	 * message system)
 23 | 	 */
 24 | 	system?: boolean;
 25 | 	/** whether the user has two factor enabled on their account */
 26 | 	mfa_enabled: boolean;
 27 | 	/**
 28 | 	 * the user's banner hash:
 29 | 	 * https://discord.com/developers/docs/reference#image-formatting
 30 | 	 */
 31 | 	banner: string | null;
 32 | 
 33 | 	/** the user's banner color encoded as an integer representation of hexadecimal color code */
 34 | 	accent_color: number | null;
 35 | 
 36 | 	/**
 37 | 	 * the user's chosen language option:
 38 | 	 * https://discord.com/developers/docs/reference#locales
 39 | 	 */
 40 | 	locale: string;
 41 | 	/** whether the email on this account has been verified */
 42 | 	verified: boolean;
 43 | 	/** the user's email */
 44 | 	email: string;
 45 | 	/**
 46 | 	 * the flags on a user's account:
 47 | 	 * https://discord.com/developers/docs/resources/user#user-object-user-flags
 48 | 	 */
 49 | 	flags: number;
 50 | 	/**
 51 | 	 * the type of Nitro subscription on a user's account:
 52 | 	 * https://discord.com/developers/docs/resources/user#user-object-premium-types
 53 | 	 */
 54 | 	premium_type: number;
 55 | 	/**
 56 | 	 * the public flags on a user's account:
 57 | 	 * https://discord.com/developers/docs/resources/user#user-object-user-flags
 58 | 	 */
 59 | 	public_flags: number;
 60 | 	/** undocumented field; corresponds to the user's custom nickname */
 61 | 	display_name: string | null;
 62 | 	/**
 63 | 	 * undocumented field; corresponds to the Discord feature where you can e.g.
 64 | 	 * put your avatar inside of an ice cube
 65 | 	 */
 66 | 	avatar_decoration: string | null;
 67 | 	/**
 68 | 	 * undocumented field; corresponds to the premium feature where you can
 69 | 	 * select a custom banner color
 70 | 	 */
 71 | 	banner_color: string | null;
 72 | 	/** undocumented field; the CDN URL of their profile picture */
 73 | 	image_url: string;
 74 | }
 75 | 
 76 | export interface DiscordOptions extends ProviderOptions<DiscordProfile> {
 77 | 	clientId: string;
 78 | 	prompt?: "none" | "consent";
 79 | 	permissions?: number;
 80 | }
 81 | 
 82 | export const discord = (options: DiscordOptions) => {
 83 | 	return {
 84 | 		id: "discord",
 85 | 		name: "Discord",
 86 | 		createAuthorizationURL({ state, scopes, redirectURI }) {
 87 | 			const _scopes = options.disableDefaultScope ? [] : ["identify", "email"];
 88 | 			scopes && _scopes.push(...scopes);
 89 | 			options.scope && _scopes.push(...options.scope);
 90 | 			const hasBotScope = _scopes.includes("bot");
 91 | 			const permissionsParam =
 92 | 				hasBotScope && options.permissions !== undefined
 93 | 					? `&permissions=${options.permissions}`
 94 | 					: "";
 95 | 			return new URL(
 96 | 				`https://discord.com/api/oauth2/authorize?scope=${_scopes.join(
 97 | 					"+",
 98 | 				)}&response_type=code&client_id=${
 99 | 					options.clientId
100 | 				}&redirect_uri=${encodeURIComponent(
101 | 					options.redirectURI || redirectURI,
102 | 				)}&state=${state}&prompt=${
103 | 					options.prompt || "none"
104 | 				}${permissionsParam}`,
105 | 			);
106 | 		},
107 | 		validateAuthorizationCode: async ({ code, redirectURI }) => {
108 | 			return validateAuthorizationCode({
109 | 				code,
110 | 				redirectURI,
111 | 				options,
112 | 				tokenEndpoint: "https://discord.com/api/oauth2/token",
113 | 			});
114 | 		},
115 | 		refreshAccessToken: options.refreshAccessToken
116 | 			? options.refreshAccessToken
117 | 			: async (refreshToken) => {
118 | 					return refreshAccessToken({
119 | 						refreshToken,
120 | 						options: {
121 | 							clientId: options.clientId,
122 | 							clientKey: options.clientKey,
123 | 							clientSecret: options.clientSecret,
124 | 						},
125 | 						tokenEndpoint: "https://discord.com/api/oauth2/token",
126 | 					});
127 | 				},
128 | 		async getUserInfo(token) {
129 | 			if (options.getUserInfo) {
130 | 				return options.getUserInfo(token);
131 | 			}
132 | 			const { data: profile, error } = await betterFetch<DiscordProfile>(
133 | 				"https://discord.com/api/users/@me",
134 | 				{
135 | 					headers: {
136 | 						authorization: `Bearer ${token.accessToken}`,
137 | 					},
138 | 				},
139 | 			);
140 | 
141 | 			if (error) {
142 | 				return null;
143 | 			}
144 | 			if (profile.avatar === null) {
145 | 				const defaultAvatarNumber =
146 | 					profile.discriminator === "0"
147 | 						? Number(BigInt(profile.id) >> BigInt(22)) % 6
148 | 						: parseInt(profile.discriminator) % 5;
149 | 				profile.image_url = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarNumber}.png`;
150 | 			} else {
151 | 				const format = profile.avatar.startsWith("a_") ? "gif" : "png";
152 | 				profile.image_url = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${format}`;
153 | 			}
154 | 			const userMap = await options.mapProfileToUser?.(profile);
155 | 			return {
156 | 				user: {
157 | 					id: profile.id,
158 | 					name: profile.global_name || profile.username || "",
159 | 					email: profile.email,
160 | 					emailVerified: profile.verified,
161 | 					image: profile.image_url,
162 | 					...userMap,
163 | 				},
164 | 				data: profile,
165 | 			};
166 | 		},
167 | 		options,
168 | 	} satisfies OAuthProvider<DiscordProfile>;
169 | };
170 | 
```

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

```typescript
  1 | import { describe, expect, it, vi } from "vitest";
  2 | import { getTestInstance } from "../test-utils/test-instance";
  3 | 
  4 | describe("db", async () => {
  5 | 	it("should work with custom model names", async () => {
  6 | 		const { client, db } = await getTestInstance({
  7 | 			user: {
  8 | 				modelName: "users",
  9 | 			},
 10 | 			session: {
 11 | 				modelName: "sessions",
 12 | 			},
 13 | 			account: {
 14 | 				modelName: "accounts",
 15 | 			},
 16 | 		});
 17 | 		const res = await client.signUp.email({
 18 | 			email: "[email protected]",
 19 | 			password: "password",
 20 | 			name: "Test User",
 21 | 		});
 22 | 		const users = await db.findMany({
 23 | 			model: "user",
 24 | 		});
 25 | 		const session = await db.findMany({
 26 | 			model: "session",
 27 | 		});
 28 | 		const accounts = await db.findMany({
 29 | 			model: "account",
 30 | 		});
 31 | 		expect(res.data).toBeDefined();
 32 | 		//including the user that was created in the test instance
 33 | 		expect(users).toHaveLength(2);
 34 | 		expect(session).toHaveLength(2);
 35 | 		expect(accounts).toHaveLength(2);
 36 | 	});
 37 | 
 38 | 	it("db hooks", async () => {
 39 | 		let callback = false;
 40 | 		const { client, db } = await getTestInstance({
 41 | 			databaseHooks: {
 42 | 				user: {
 43 | 					create: {
 44 | 						async before(user) {
 45 | 							return {
 46 | 								data: {
 47 | 									...user,
 48 | 									image: "test-image",
 49 | 								},
 50 | 							};
 51 | 						},
 52 | 						async after(user) {
 53 | 							callback = true;
 54 | 						},
 55 | 					},
 56 | 				},
 57 | 			},
 58 | 		});
 59 | 		const res = await client.signUp.email({
 60 | 			email: "[email protected]",
 61 | 			name: "test",
 62 | 			password: "password",
 63 | 		});
 64 | 		const session = await client.getSession({
 65 | 			fetchOptions: {
 66 | 				headers: {
 67 | 					Authorization: `Bearer ${res.data?.token}`,
 68 | 				},
 69 | 				throw: true,
 70 | 			},
 71 | 		});
 72 | 		expect(session?.user?.image).toBe("test-image");
 73 | 		expect(callback).toBe(true);
 74 | 	});
 75 | 
 76 | 	it("should work with custom field names", async () => {
 77 | 		const { client } = await getTestInstance({
 78 | 			user: {
 79 | 				fields: {
 80 | 					email: "email_address",
 81 | 				},
 82 | 			},
 83 | 		});
 84 | 		const res = await client.signUp.email({
 85 | 			email: "[email protected]",
 86 | 			password: "password",
 87 | 			name: "Test User",
 88 | 		});
 89 | 		const session = await client.getSession({
 90 | 			fetchOptions: {
 91 | 				headers: {
 92 | 					Authorization: `Bearer ${res.data?.token}`,
 93 | 				},
 94 | 				throw: true,
 95 | 			},
 96 | 		});
 97 | 		expect(session?.user.email).toBe("[email protected]");
 98 | 	});
 99 | 
100 | 	it("delete hooks", async () => {
101 | 		const hookUserDeleteBefore = vi.fn();
102 | 		const hookUserDeleteAfter = vi.fn();
103 | 		const hookSessionDeleteBefore = vi.fn();
104 | 		const hookSessionDeleteAfter = vi.fn();
105 | 
106 | 		const { client } = await getTestInstance({
107 | 			session: {
108 | 				storeSessionInDatabase: true,
109 | 			},
110 | 			user: {
111 | 				deleteUser: {
112 | 					enabled: true,
113 | 				},
114 | 			},
115 | 			databaseHooks: {
116 | 				user: {
117 | 					delete: {
118 | 						async before(user, context) {
119 | 							hookUserDeleteBefore(user, context);
120 | 							return true;
121 | 						},
122 | 						async after(user, context) {
123 | 							hookUserDeleteAfter(user, context);
124 | 						},
125 | 					},
126 | 				},
127 | 				session: {
128 | 					delete: {
129 | 						async before(session, context) {
130 | 							hookSessionDeleteBefore(session, context);
131 | 							return true;
132 | 						},
133 | 						async after(session, context) {
134 | 							hookSessionDeleteAfter(session, context);
135 | 						},
136 | 					},
137 | 				},
138 | 			},
139 | 		});
140 | 
141 | 		const res = await client.signUp.email({
142 | 			email: "[email protected]",
143 | 			password: "password",
144 | 			name: "Delete Test User",
145 | 		});
146 | 
147 | 		expect(res.data).toBeDefined();
148 | 		const userId = res.data?.user.id;
149 | 
150 | 		await client.deleteUser({
151 | 			fetchOptions: {
152 | 				headers: {
153 | 					Authorization: `Bearer ${res.data?.token}`,
154 | 				},
155 | 				throw: true,
156 | 			},
157 | 		});
158 | 
159 | 		expect(hookUserDeleteBefore).toHaveBeenCalledOnce();
160 | 		expect(hookUserDeleteAfter).toHaveBeenCalledOnce();
161 | 		expect(hookSessionDeleteBefore).toHaveBeenCalledOnce();
162 | 		expect(hookSessionDeleteAfter).toHaveBeenCalledOnce();
163 | 
164 | 		expect(hookUserDeleteBefore).toHaveBeenCalledWith(
165 | 			expect.objectContaining({
166 | 				id: userId,
167 | 				email: "[email protected]",
168 | 				name: "Delete Test User",
169 | 			}),
170 | 			expect.any(Object),
171 | 		);
172 | 
173 | 		expect(hookUserDeleteAfter).toHaveBeenCalledWith(
174 | 			expect.objectContaining({
175 | 				id: userId,
176 | 				email: "[email protected]",
177 | 				name: "Delete Test User",
178 | 			}),
179 | 			expect.any(Object),
180 | 		);
181 | 	});
182 | 
183 | 	it("delete hooks abort", async () => {
184 | 		const hookUserDeleteBefore = vi.fn();
185 | 		const hookUserDeleteAfter = vi.fn();
186 | 
187 | 		const { client } = await getTestInstance({
188 | 			user: {
189 | 				deleteUser: {
190 | 					enabled: true,
191 | 				},
192 | 			},
193 | 			databaseHooks: {
194 | 				user: {
195 | 					delete: {
196 | 						async before(user, context) {
197 | 							hookUserDeleteBefore(user, context);
198 | 							return false;
199 | 						},
200 | 						async after(user, context) {
201 | 							hookUserDeleteAfter(user, context);
202 | 						},
203 | 					},
204 | 				},
205 | 			},
206 | 		});
207 | 
208 | 		const res = await client.signUp.email({
209 | 			email: "[email protected]",
210 | 			password: "password",
211 | 			name: "Abort Delete Test User",
212 | 		});
213 | 
214 | 		expect(res.data).toBeDefined();
215 | 		const userId = res.data?.user.id;
216 | 
217 | 		try {
218 | 			await client.deleteUser({
219 | 				fetchOptions: {
220 | 					headers: {
221 | 						Authorization: `Bearer ${res.data?.token}`,
222 | 					},
223 | 					throw: true,
224 | 				},
225 | 			});
226 | 		} catch (error) {
227 | 			// Expected to fail due to hook returning false
228 | 		}
229 | 
230 | 		expect(hookUserDeleteBefore).toHaveBeenCalledOnce();
231 | 		expect(hookUserDeleteAfter).not.toHaveBeenCalled();
232 | 
233 | 		expect(hookUserDeleteBefore).toHaveBeenCalledWith(
234 | 			expect.objectContaining({
235 | 				id: userId,
236 | 				email: "[email protected]",
237 | 				name: "Abort Delete Test User",
238 | 			}),
239 | 			expect.any(Object),
240 | 		);
241 | 	});
242 | });
243 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/utils/add-svelte-kit-env-modules.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import path from "path";
  2 | import fs from "fs";
  3 | 
  4 | /**
  5 |  * Adds SvelteKit environment modules and path aliases
  6 |  * @param aliases - The aliases object to populate
  7 |  * @param cwd - Current working directory (optional, defaults to process.cwd())
  8 |  */
  9 | export function addSvelteKitEnvModules(
 10 | 	aliases: Record<string, string>,
 11 | 	cwd?: string,
 12 | ) {
 13 | 	const workingDir = cwd || process.cwd();
 14 | 
 15 | 	// Add SvelteKit environment modules
 16 | 	aliases["$env/dynamic/private"] = createDataUriModule(
 17 | 		createDynamicEnvModule(),
 18 | 	);
 19 | 	aliases["$env/dynamic/public"] = createDataUriModule(
 20 | 		createDynamicEnvModule(),
 21 | 	);
 22 | 	aliases["$env/static/private"] = createDataUriModule(
 23 | 		createStaticEnvModule(filterPrivateEnv("PUBLIC_", "")),
 24 | 	);
 25 | 	aliases["$env/static/public"] = createDataUriModule(
 26 | 		createStaticEnvModule(filterPublicEnv("PUBLIC_", "")),
 27 | 	);
 28 | 
 29 | 	const svelteKitAliases = getSvelteKitPathAliases(workingDir);
 30 | 	Object.assign(aliases, svelteKitAliases);
 31 | }
 32 | 
 33 | function getSvelteKitPathAliases(cwd: string): Record<string, string> {
 34 | 	const aliases: Record<string, string> = {};
 35 | 
 36 | 	const packageJsonPath = path.join(cwd, "package.json");
 37 | 	const svelteConfigPath = path.join(cwd, "svelte.config.js");
 38 | 	const svelteConfigTsPath = path.join(cwd, "svelte.config.ts");
 39 | 
 40 | 	let isSvelteKitProject = false;
 41 | 
 42 | 	if (fs.existsSync(packageJsonPath)) {
 43 | 		try {
 44 | 			const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
 45 | 			const deps = {
 46 | 				...packageJson.dependencies,
 47 | 				...packageJson.devDependencies,
 48 | 			};
 49 | 			isSvelteKitProject = !!deps["@sveltejs/kit"];
 50 | 		} catch {
 51 | 			// Ignore JSON parse errors
 52 | 		}
 53 | 	}
 54 | 
 55 | 	if (!isSvelteKitProject) {
 56 | 		isSvelteKitProject =
 57 | 			fs.existsSync(svelteConfigPath) || fs.existsSync(svelteConfigTsPath);
 58 | 	}
 59 | 
 60 | 	if (!isSvelteKitProject) {
 61 | 		return aliases;
 62 | 	}
 63 | 
 64 | 	const libPaths = [path.join(cwd, "src", "lib"), path.join(cwd, "lib")];
 65 | 
 66 | 	for (const libPath of libPaths) {
 67 | 		if (fs.existsSync(libPath)) {
 68 | 			aliases["$lib"] = libPath;
 69 | 			// handles a common subpaths
 70 | 			const commonSubPaths = ["server", "utils", "components", "stores"];
 71 | 			for (const subPath of commonSubPaths) {
 72 | 				const subDir = path.join(libPath, subPath);
 73 | 				if (fs.existsSync(subDir)) {
 74 | 					aliases[`$lib/${subPath}`] = subDir;
 75 | 				}
 76 | 			}
 77 | 			break;
 78 | 		}
 79 | 	}
 80 | 	// Add simple stub for $app/server to prevent CLI errors
 81 | 	aliases["$app/server"] = createDataUriModule(createAppServerModule());
 82 | 
 83 | 	const customAliases = getSvelteConfigAliases(cwd);
 84 | 	Object.assign(aliases, customAliases);
 85 | 
 86 | 	return aliases;
 87 | }
 88 | // for custom aliases in svelte.config.js/ts
 89 | function getSvelteConfigAliases(cwd: string): Record<string, string> {
 90 | 	const aliases: Record<string, string> = {};
 91 | 	const configPaths = [
 92 | 		path.join(cwd, "svelte.config.js"),
 93 | 		path.join(cwd, "svelte.config.ts"),
 94 | 	];
 95 | 
 96 | 	for (const configPath of configPaths) {
 97 | 		if (fs.existsSync(configPath)) {
 98 | 			try {
 99 | 				const content = fs.readFileSync(configPath, "utf-8");
100 | 				const aliasMatch = content.match(/alias\s*:\s*\{([^}]+)\}/);
101 | 				if (aliasMatch && aliasMatch[1]) {
102 | 					const aliasContent = aliasMatch[1];
103 | 					const aliasMatches = aliasContent.matchAll(
104 | 						/['"`](\$[^'"`]+)['"`]\s*:\s*['"`]([^'"`]+)['"`]/g,
105 | 					);
106 | 
107 | 					for (const match of aliasMatches) {
108 | 						const [, alias, target] = match;
109 | 						if (alias && target) {
110 | 							aliases[alias + "/*"] = path.resolve(cwd, target) + "/*";
111 | 							aliases[alias] = path.resolve(cwd, target);
112 | 						}
113 | 					}
114 | 				}
115 | 			} catch {
116 | 				// Ignore file reading/parsing errors
117 | 			}
118 | 			break;
119 | 		}
120 | 	}
121 | 
122 | 	return aliases;
123 | }
124 | 
125 | function createAppServerModule(): string {
126 | 	return `
127 | // $app/server stub for CLI compatibility
128 | export default {};
129 | // jiti dirty hack: .unknown
130 | `;
131 | }
132 | 
133 | function createDataUriModule(module: string) {
134 | 	return `data:text/javascript;charset=utf-8,${encodeURIComponent(module)}`;
135 | }
136 | 
137 | function createStaticEnvModule(env: Record<string, string>) {
138 | 	const declarations = Object.keys(env)
139 | 		.filter((k) => validIdentifier.test(k) && !reserved.has(k))
140 | 		.map((k) => `export const ${k} = ${JSON.stringify(env[k])};`);
141 | 
142 | 	return `
143 |   ${declarations.join("\n")}
144 |   // jiti dirty hack: .unknown
145 |   `;
146 | }
147 | 
148 | function createDynamicEnvModule() {
149 | 	return `
150 |   export const env = process.env;
151 |   // jiti dirty hack: .unknown
152 |   `;
153 | }
154 | 
155 | export function filterPrivateEnv(publicPrefix: string, privatePrefix: string) {
156 | 	return Object.fromEntries(
157 | 		Object.entries(process.env).filter(
158 | 			([k]) =>
159 | 				k.startsWith(privatePrefix) &&
160 | 				(publicPrefix === "" || !k.startsWith(publicPrefix)),
161 | 		),
162 | 	) as Record<string, string>;
163 | }
164 | 
165 | export function filterPublicEnv(publicPrefix: string, privatePrefix: string) {
166 | 	return Object.fromEntries(
167 | 		Object.entries(process.env).filter(
168 | 			([k]) =>
169 | 				k.startsWith(publicPrefix) &&
170 | 				(privatePrefix === "" || !k.startsWith(privatePrefix)),
171 | 		),
172 | 	) as Record<string, string>;
173 | }
174 | 
175 | const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
176 | const reserved = new Set([
177 | 	"do",
178 | 	"if",
179 | 	"in",
180 | 	"for",
181 | 	"let",
182 | 	"new",
183 | 	"try",
184 | 	"var",
185 | 	"case",
186 | 	"else",
187 | 	"enum",
188 | 	"eval",
189 | 	"null",
190 | 	"this",
191 | 	"true",
192 | 	"void",
193 | 	"with",
194 | 	"await",
195 | 	"break",
196 | 	"catch",
197 | 	"class",
198 | 	"const",
199 | 	"false",
200 | 	"super",
201 | 	"throw",
202 | 	"while",
203 | 	"yield",
204 | 	"delete",
205 | 	"export",
206 | 	"import",
207 | 	"public",
208 | 	"return",
209 | 	"static",
210 | 	"switch",
211 | 	"typeof",
212 | 	"default",
213 | 	"extends",
214 | 	"finally",
215 | 	"package",
216 | 	"private",
217 | 	"continue",
218 | 	"debugger",
219 | 	"function",
220 | 	"arguments",
221 | 	"interface",
222 | 	"protected",
223 | 	"implements",
224 | 	"instanceof",
225 | ]);
226 | 
```

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

```markdown
  1 | ---
  2 | title: PayPal
  3 | description: Paypal provider setup and usage.
  4 | ---
  5 | 
  6 | <Steps>
  7 |     <Step>
  8 |         ### Get your PayPal Credentials
  9 |         To integrate with PayPal, you need to obtain API credentials by creating an application in the [PayPal Developer Portal](https://developer.paypal.com/dashboard).
 10 | 
 11 |          Follow these steps:
 12 |             1. Create an account on the PayPal Developer Portal
 13 |             2. Create a new application, [official docs]( https://developer.paypal.com/developer/applications/)
 14 |             3. Configure Log in with PayPal under "Other features"
 15 |             4. Set up your Return URL (redirect URL)
 16 |             5. Configure user information permissions
 17 |             6. Note your Client ID and Client Secret
 18 | 
 19 |         <Callout type="info">
 20 |             - PayPal has two environments: Sandbox (for testing) and Live (for production)
 21 |             - For testing, create sandbox test accounts in the Developer Dashboard under "Sandbox" → "Accounts"
 22 |             - You cannot use your real PayPal account to test in sandbox mode - you must use the generated test accounts
 23 |             - The Return URL in your PayPal app settings must exactly match your redirect URI
 24 |             - The PayPal API does not work with localhost. You need to use a public domain for the redirect URL and HTTPS for local testing. You can use [NGROK](https://ngrok.com/) or another similar tool for this.
 25 |         </Callout>
 26 |          Make sure to configure "Log in with PayPal" in your app settings:
 27 |             1. Go to your app in the Developer Dashboard
 28 |             2. Under "Other features", check "Log in with PayPal"
 29 |             3. Click "Advanced Settings"
 30 |             4. Enter your Return URL
 31 |             5. Select the user information you want to access (email, name, etc.)
 32 |             6. Enter Privacy Policy and User Agreement URLs
 33 | 
 34 |         <Callout type="info">
 35 |             - PayPal doesn't use traditional OAuth2 scopes in the authorization URL. Instead, you configure permissions directly in the Developer Dashboard
 36 |             - For live apps, PayPal must review and approve your application before it can go live, which typically takes a few weeks
 37 |         </Callout>
 38 |     </Step>
 39 | 
 40 |   <Step>
 41 |         ### Configure the provider
 42 |         To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
 43 | 
 44 |         ```ts title="auth.ts"
 45 |         import { betterAuth } from "better-auth"
 46 | 
 47 |         export const auth = betterAuth({
 48 |             socialProviders: {
 49 |                 paypal: { // [!code highlight]
 50 |                     clientId: process.env.PAYPAL_CLIENT_ID as string, // [!code highlight]
 51 |                     clientSecret: process.env.PAYPAL_CLIENT_SECRET as string, // [!code highlight]
 52 |                     environment: "sandbox", // or "live" for production //, // [!code highlight]
 53 |                 }, // [!code highlight]
 54 |             },
 55 |         })
 56 |         ```
 57 |         #### Options
 58 |         The PayPal provider accepts the following options:
 59 |     
 60 |         - `environment`: `'sandbox' | 'live'` - PayPal environment to use (default: `'sandbox'`)
 61 |         - `requestShippingAddress`: `boolean` - Whether to request shipping address information (default: `false`)
 62 |     
 63 |         ```ts title="auth.ts"
 64 |         export const auth = betterAuth({
 65 |             socialProviders: {
 66 |                 paypal: {
 67 |                     clientId: process.env.PAYPAL_CLIENT_ID as string,
 68 |                     clientSecret: process.env.PAYPAL_CLIENT_SECRET as string,
 69 |                     environment: "live", // Use "live" for production
 70 |                     requestShippingAddress: true, // Request address info
 71 |                 },
 72 |             },
 73 |         })
 74 |         ```
 75 |     </Step>
 76 |        <Step>
 77 |         ### Sign In with PayPal
 78 |         To sign in with PayPal, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
 79 |         - `provider`: The provider to use. It should be set to `paypal`.
 80 | 
 81 |         ```ts title="auth-client.ts"
 82 |         import { createAuthClient } from "better-auth/client"
 83 |         const authClient =  createAuthClient()
 84 | 
 85 |         const signIn = async () => {
 86 |             const data = await authClient.signIn.social({
 87 |                 provider: "paypal"
 88 |             })
 89 |         }
 90 |         ```
 91 |         ### Additional Options:
 92 |         - `environment`: PayPal environment to use.
 93 |             - Default: `"sandbox"`
 94 |             - Options: `"sandbox"` | `"live"`
 95 |         - `requestShippingAddress`: Whether to request shipping address information.
 96 |             - Default: `false`
 97 |         - `scope`: Additional scopes to request (combined with default permissions).
 98 |             - Default: Configured in PayPal Developer Dashboard
 99 |             - Note: PayPal doesn't use traditional OAuth2 scopes - permissions are set in the Dashboard
100 |             For more details refer to the [Scopes Reference](https://developer.paypal.com/docs/log-in-with-paypal/integrate/reference/#scope-attributes)
101 |         - `mapProfileToUser`: Custom function to map PayPal profile data to user object.
102 |         - `getUserInfo`: Custom function to retrieve user information.
103 |         For more details refer to the [User Reference](https://developer.paypal.com/docs/api/identity/v1/#userinfo_get)
104 |         - `verifyIdToken`: Custom ID token verification function.   
105 |     </Step>
106 | 
107 | </Steps>
108 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/api/rate-limiter/rate-limiter.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, vi } from "vitest";
  2 | import { getTestInstance } from "../../test-utils/test-instance";
  3 | import type { RateLimit } from "../../types";
  4 | 
  5 | describe(
  6 | 	"rate-limiter",
  7 | 	{
  8 | 		timeout: 10000,
  9 | 	},
 10 | 	async () => {
 11 | 		const { client, testUser } = await getTestInstance({
 12 | 			rateLimit: {
 13 | 				enabled: true,
 14 | 				window: 10,
 15 | 				max: 20,
 16 | 			},
 17 | 		});
 18 | 
 19 | 		it("should return 429 after 3 request for sign-in", async () => {
 20 | 			for (let i = 0; i < 5; i++) {
 21 | 				const response = await client.signIn.email({
 22 | 					email: testUser.email,
 23 | 					password: testUser.password,
 24 | 				});
 25 | 				if (i >= 3) {
 26 | 					expect(response.error?.status).toBe(429);
 27 | 				} else {
 28 | 					expect(response.error).toBeNull();
 29 | 				}
 30 | 			}
 31 | 		});
 32 | 
 33 | 		it("should reset the limit after the window period", async () => {
 34 | 			vi.useFakeTimers();
 35 | 			vi.advanceTimersByTime(11000);
 36 | 			for (let i = 0; i < 5; i++) {
 37 | 				const res = await client.signIn.email({
 38 | 					email: testUser.email,
 39 | 					password: testUser.password,
 40 | 				});
 41 | 				if (i >= 3) {
 42 | 					expect(res.error?.status).toBe(429);
 43 | 				} else {
 44 | 					expect(res.error).toBeNull();
 45 | 				}
 46 | 			}
 47 | 		});
 48 | 
 49 | 		it("should respond the correct retry-after header", async () => {
 50 | 			vi.useFakeTimers();
 51 | 			vi.advanceTimersByTime(3000);
 52 | 			let retryAfter = "";
 53 | 			await client.signIn.email(
 54 | 				{
 55 | 					email: testUser.email,
 56 | 					password: testUser.password,
 57 | 				},
 58 | 				{
 59 | 					onError(context) {
 60 | 						retryAfter = context.response.headers.get("X-Retry-After") ?? "";
 61 | 					},
 62 | 				},
 63 | 			);
 64 | 			expect(retryAfter).toBe("7");
 65 | 		});
 66 | 
 67 | 		it("should rate limit based on the path", async () => {
 68 | 			const signInRes = await client.signIn.email({
 69 | 				email: testUser.email,
 70 | 				password: testUser.password,
 71 | 			});
 72 | 			expect(signInRes.error?.status).toBe(429);
 73 | 
 74 | 			const signUpRes = await client.signUp.email({
 75 | 				email: "[email protected]",
 76 | 				password: testUser.password,
 77 | 				name: "test",
 78 | 			});
 79 | 			expect(signUpRes.error).toBeNull();
 80 | 		});
 81 | 
 82 | 		it("non-special-rules limits", async () => {
 83 | 			for (let i = 0; i < 25; i++) {
 84 | 				const response = await client.getSession();
 85 | 				expect(response.error?.status).toBe(i >= 20 ? 429 : undefined);
 86 | 			}
 87 | 		});
 88 | 
 89 | 		it("query params should be ignored", async () => {
 90 | 			for (let i = 0; i < 25; i++) {
 91 | 				const response = await client.listSessions({
 92 | 					fetchOptions: {
 93 | 						query: {
 94 | 							"test-query": Math.random().toString(),
 95 | 						},
 96 | 					},
 97 | 				});
 98 | 
 99 | 				if (i >= 20) {
100 | 					expect(response.error?.status).toBe(429);
101 | 				} else {
102 | 					expect(response.error?.status).toBe(401);
103 | 				}
104 | 			}
105 | 		});
106 | 	},
107 | );
108 | 
109 | describe("custom rate limiting storage", async () => {
110 | 	let store = new Map<string, string>();
111 | 	const expirationMap = new Map<string, number>();
112 | 	const { client, testUser } = await getTestInstance({
113 | 		rateLimit: {
114 | 			enabled: true,
115 | 		},
116 | 		secondaryStorage: {
117 | 			set(key, value, ttl) {
118 | 				store.set(key, value);
119 | 				if (ttl) expirationMap.set(key, ttl);
120 | 			},
121 | 			get(key) {
122 | 				return store.get(key) || null;
123 | 			},
124 | 			delete(key) {
125 | 				store.delete(key);
126 | 				expirationMap.delete(key);
127 | 			},
128 | 		},
129 | 	});
130 | 
131 | 	it("should use custom storage", async () => {
132 | 		await client.getSession();
133 | 		expect(store.size).toBe(3);
134 | 		let lastRequest = Date.now();
135 | 		for (let i = 0; i < 4; i++) {
136 | 			const response = await client.signIn.email({
137 | 				email: testUser.email,
138 | 				password: testUser.password,
139 | 			});
140 | 			const rateLimitData: RateLimit = JSON.parse(
141 | 				store.get("127.0.0.1/sign-in/email") ?? "{}",
142 | 			);
143 | 			expect(rateLimitData.lastRequest).toBeGreaterThanOrEqual(lastRequest);
144 | 			lastRequest = rateLimitData.lastRequest;
145 | 			if (i >= 3) {
146 | 				expect(response.error?.status).toBe(429);
147 | 				expect(rateLimitData.count).toBe(3);
148 | 			} else {
149 | 				expect(response.error).toBeNull();
150 | 				expect(rateLimitData.count).toBe(i + 1);
151 | 			}
152 | 			const rateLimitExp = expirationMap.get("127.0.0.1/sign-in/email");
153 | 			expect(rateLimitExp).toBe(10);
154 | 		}
155 | 	});
156 | });
157 | 
158 | describe("should work with custom rules", async () => {
159 | 	const { client, testUser } = await getTestInstance({
160 | 		rateLimit: {
161 | 			enabled: true,
162 | 			storage: "database",
163 | 			customRules: {
164 | 				"/sign-in/*": {
165 | 					window: 10,
166 | 					max: 2,
167 | 				},
168 | 				"/sign-up/email": {
169 | 					window: 10,
170 | 					max: 3,
171 | 				},
172 | 				"/get-session": false,
173 | 			},
174 | 		},
175 | 	});
176 | 
177 | 	it("should use custom rules", async () => {
178 | 		for (let i = 0; i < 4; i++) {
179 | 			const response = await client.signIn.email({
180 | 				email: testUser.email,
181 | 				password: testUser.password,
182 | 			});
183 | 			if (i >= 2) {
184 | 				expect(response.error?.status).toBe(429);
185 | 			} else {
186 | 				expect(response.error).toBeNull();
187 | 			}
188 | 		}
189 | 
190 | 		for (let i = 0; i < 5; i++) {
191 | 			const response = await client.signUp.email({
192 | 				email: `${Math.random()}@test.com`,
193 | 				password: testUser.password,
194 | 				name: "test",
195 | 			});
196 | 			if (i >= 3) {
197 | 				expect(response.error?.status).toBe(429);
198 | 			} else {
199 | 				expect(response.error).toBeNull();
200 | 			}
201 | 		}
202 | 	});
203 | 
204 | 	it("should use default rules if custom rules are not defined", async () => {
205 | 		for (let i = 0; i < 5; i++) {
206 | 			const response = await client.getSession();
207 | 			if (i >= 20) {
208 | 				expect(response.error?.status).toBe(429);
209 | 			} else {
210 | 				expect(response.error).toBeNull();
211 | 			}
212 | 		}
213 | 	});
214 | 
215 | 	it("should not rate limit if custom rule is false", async () => {
216 | 		let i = 0;
217 | 		let response = null;
218 | 		for (; i < 110; i++) {
219 | 			response = await client.getSession().then((res) => res.error);
220 | 		}
221 | 		expect(response).toBeNull();
222 | 		expect(i).toBe(110);
223 | 	});
224 | });
225 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/guides/saml-sso-with-okta.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: SAML SSO with Okta
  3 | description: A guide to integrating SAML Single Sign-On (SSO) with Better Auth, featuring Okta
  4 | ---
  5 | 
  6 | This guide walks you through setting up SAML Single Sign-On (SSO) with your Identity Provider (IdP), using Okta as an example. For advanced configuration details and the full API reference, check out the [SSO Plugin Documentation](/docs/plugins/sso).
  7 | 
  8 | ## What is SAML?
  9 | 
 10 | SAML (Security Assertion Markup Language) is an XML-based standard for exchanging authentication and authorization data between an Identity Provider (IdP) (e.g., Okta, Azure AD, OneLogin) and a Service Provider (SP) (in this case, Better Auth).
 11 | 
 12 | In this setup:
 13 | 
 14 | - **IdP (Okta)**: Authenticates users and sends assertions about their identity.
 15 | - **SP (Better Auth)**: Validates assertions and logs the user in.up.
 16 | 
 17 | ### Step 1: Create a SAML Application in Okta
 18 | 
 19 | 1. Log in to your Okta Admin Console
 20 | 2. Navigate to Applications > Applications
 21 | 3. Click "Create App Integration"
 22 | 4. Select "SAML 2.0" as the Sign-in method
 23 | 5. Configure the following settings:
 24 | 
 25 |    - **Single Sign-on URL**: Your Better Auth ACS endpoint (e.g., `http://localhost:3000/api/auth/sso/saml2/sp/acs/sso`). while `sso` being your providerId
 26 |    - **Audience URI (SP Entity ID)**: Your Better Auth metadata URL (e.g., `http://localhost:3000/api/auth/sso/saml2/sp/metadata`)
 27 |    - **Name ID format**: Email Address or any of your choice.
 28 | 
 29 | 6. Download the IdP metadata XML file and certificate
 30 | 
 31 | ### Step 2: Configure Better Auth
 32 | 
 33 | Here’s an example configuration for Okta in a dev environment:
 34 | 
 35 | ```typescript
 36 | const ssoConfig = {
 37 |   defaultSSO: [{
 38 |     domain: "localhost:3000", // Your domain
 39 |     providerId: "sso",
 40 |     samlConfig: {
 41 |       // SP Configuration
 42 |       issuer: "http://localhost:3000/api/auth/sso/saml2/sp/metadata",
 43 |       entryPoint: "https://trial-1076874.okta.com/app/trial-1076874_samltest_1/exktofb0a62hqLAUL697/sso/saml",
 44 |       callbackUrl: "/dashboard", // Redirect after successful authentication
 45 |       
 46 |       // IdP Configuration
 47 |       idpMetadata: {
 48 |         entityID: "https://trial-1076874.okta.com/app/exktofb0a62hqLAUL697/sso/saml/metadata",
 49 |         singleSignOnService: [{
 50 |           Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
 51 |           Location: "https://trial-1076874.okta.com/app/trial-1076874_samltest_1/exktofb0a62hqLAUL697/sso/saml"
 52 |         }],
 53 |         cert: `-----BEGIN CERTIFICATE-----
 54 | MIIDqjCCApKgAwIBAgIGAZhVGMeUMA0GCSqGSIb3DQEBCwUAMIGVMQswCQYDVQQGEwJVUzETMBEG
 55 | ...
 56 | [Your Okta Certificate]
 57 | ...
 58 | -----END CERTIFICATE-----`
 59 |       },
 60 |       
 61 |       // SP Metadata
 62 |       spMetadata: {
 63 |         metadata: `<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" 
 64 |           entityID="http://localhost:3000/api/sso/saml2/sp/metadata">
 65 |           ...
 66 |           [Your SP Metadata XML]
 67 |           ...
 68 |         </md:EntityDescriptor>`
 69 |       }
 70 |     }
 71 |   }]
 72 | }
 73 | ```
 74 | 
 75 | ### Step 3: Multiple Default Providers (Optional)
 76 | 
 77 | You can configure multiple SAML providers for different domains:
 78 | 
 79 | ```typescript
 80 | const ssoConfig = {
 81 |   defaultSSO: [
 82 |     {
 83 |       domain: "company.com",
 84 |       providerId: "company-okta",
 85 |       samlConfig: {
 86 |         // Okta SAML configuration for company.com
 87 |       }
 88 |     },
 89 |     {
 90 |       domain: "partner.com", 
 91 |       providerId: "partner-adfs",
 92 |       samlConfig: {
 93 |         // ADFS SAML configuration for partner.com
 94 |       }
 95 |     },
 96 |     {
 97 |       domain: "contractor.org",
 98 |       providerId: "contractor-azure",
 99 |       samlConfig: {
100 |         // Azure AD SAML configuration for contractor.org
101 |       }
102 |     }
103 |   ]
104 | }
105 | ```
106 | 
107 | <Callout type="info">
108 | **Explicit**: Pass providerId directly when signing in.
109 | **Domain fallback:** Matches based on the user’s email domain. e.g. [email protected] → matches `company-okta` provider.
110 | </Callout>
111 | 
112 | 
113 | ### Step 4: Initiating Sign-In
114 | 
115 | You can start an SSO flow in three ways:
116 | 
117 | **1. Explicitly by `providerId` (recommended):**
118 | 
119 | ```typescript
120 | // Explicitly specify which provider to use
121 | await authClient.signIn.sso({
122 |   providerId: "company-okta",
123 |   callbackURL: "/dashboard"
124 | });
125 | ```
126 | 
127 | **2. By email domain matching:**
128 | 
129 | ```typescript
130 | // Automatically matches provider based on email domain
131 | await authClient.signIn.sso({
132 |   email: "[email protected]",
133 |   callbackURL: "/dashboard"
134 | });
135 | ```
136 | 
137 | **3. By specifying domain:**
138 | 
139 | ```typescript
140 | // Explicitly specify domain for matching
141 | await authClient.signIn.sso({
142 |   domain: "partner.com",
143 |   callbackURL: "/dashboard"
144 | });
145 | ```
146 | 
147 | **Important Notes**:
148 |  - DummyIDP should ONLY be used for development and testing
149 |  - Never use these certificates in production
150 |  - The example uses `localhost:3000` - adjust URLs for your environment
151 |  - For production, always use proper IdP providers like Okta, Azure AD, or OneLogin
152 | 
153 | ### Step 5: Dynamically Registering SAML Providers
154 | 
155 | For dynamic registration, you should register SAML providers using the API. See the [SSO Plugin Documentation](/docs/plugins/sso#register-a-saml-provider) for detailed registration instructions.
156 | 
157 | Example registration:
158 | 
159 | ```typescript
160 | await authClient.sso.register({
161 |   providerId: "okta-prod",
162 |   issuer: "https://your-domain.com",
163 |   domain: "your-domain.com",
164 |   samlConfig: {
165 |     // Your production SAML configuration
166 |   }
167 | });
168 | ```
169 | 
170 | ## Additional Resources
171 | 
172 | - [SSO Plugin Documentation](/docs/plugins/sso)
173 | - [Okta SAML Documentation](https://developer.okta.com/docs/concepts/saml/)
174 | - [SAML 2.0 Specification](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf)
175 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/commands/generate.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Command } from "commander";
  2 | import { getConfig } from "../utils/get-config";
  3 | import * as z from "zod/v4";
  4 | import { existsSync } from "fs";
  5 | import path from "path";
  6 | import { logger, createTelemetry, getTelemetryAuthConfig } from "better-auth";
  7 | import yoctoSpinner from "yocto-spinner";
  8 | import prompts from "prompts";
  9 | import fs from "fs/promises";
 10 | import chalk from "chalk";
 11 | import { getAdapter } from "better-auth/db";
 12 | import { generateSchema } from "../generators";
 13 | 
 14 | export async function generateAction(opts: any) {
 15 | 	const options = z
 16 | 		.object({
 17 | 			cwd: z.string(),
 18 | 			config: z.string().optional(),
 19 | 			output: z.string().optional(),
 20 | 			y: z.boolean().optional(),
 21 | 			yes: z.boolean().optional(),
 22 | 		})
 23 | 		.parse(opts);
 24 | 
 25 | 	const cwd = path.resolve(options.cwd);
 26 | 	if (!existsSync(cwd)) {
 27 | 		logger.error(`The directory "${cwd}" does not exist.`);
 28 | 		process.exit(1);
 29 | 	}
 30 | 	const config = await getConfig({
 31 | 		cwd,
 32 | 		configPath: options.config,
 33 | 	});
 34 | 	if (!config) {
 35 | 		logger.error(
 36 | 			"No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag.",
 37 | 		);
 38 | 		return;
 39 | 	}
 40 | 
 41 | 	const adapter = await getAdapter(config).catch((e) => {
 42 | 		logger.error(e.message);
 43 | 		process.exit(1);
 44 | 	});
 45 | 
 46 | 	const spinner = yoctoSpinner({ text: "preparing schema..." }).start();
 47 | 
 48 | 	const schema = await generateSchema({
 49 | 		adapter,
 50 | 		file: options.output,
 51 | 		options: config,
 52 | 	});
 53 | 
 54 | 	spinner.stop();
 55 | 	if (!schema.code) {
 56 | 		logger.info("Your schema is already up to date.");
 57 | 		// telemetry: track generate attempted, no changes
 58 | 		try {
 59 | 			const telemetry = await createTelemetry(config);
 60 | 			await telemetry.publish({
 61 | 				type: "cli_generate",
 62 | 				payload: {
 63 | 					outcome: "no_changes",
 64 | 					config: getTelemetryAuthConfig(config, {
 65 | 						adapter: adapter.id,
 66 | 						database:
 67 | 							typeof config.database === "function" ? "adapter" : "kysely",
 68 | 					}),
 69 | 				},
 70 | 			});
 71 | 		} catch {}
 72 | 		process.exit(0);
 73 | 	}
 74 | 	if (schema.overwrite) {
 75 | 		let confirm = options.y || options.yes;
 76 | 		if (!confirm) {
 77 | 			const response = await prompts({
 78 | 				type: "confirm",
 79 | 				name: "confirm",
 80 | 				message: `The file ${
 81 | 					schema.fileName
 82 | 				} already exists. Do you want to ${chalk.yellow(
 83 | 					`${schema.overwrite ? "overwrite" : "append"}`,
 84 | 				)} the schema to the file?`,
 85 | 			});
 86 | 			confirm = response.confirm;
 87 | 		}
 88 | 
 89 | 		if (confirm) {
 90 | 			const exist = existsSync(path.join(cwd, schema.fileName));
 91 | 			if (!exist) {
 92 | 				await fs.mkdir(path.dirname(path.join(cwd, schema.fileName)), {
 93 | 					recursive: true,
 94 | 				});
 95 | 			}
 96 | 			if (schema.overwrite) {
 97 | 				await fs.writeFile(path.join(cwd, schema.fileName), schema.code);
 98 | 			} else {
 99 | 				await fs.appendFile(path.join(cwd, schema.fileName), schema.code);
100 | 			}
101 | 			logger.success(
102 | 				`🚀 Schema was ${
103 | 					schema.overwrite ? "overwritten" : "appended"
104 | 				} successfully!`,
105 | 			);
106 | 			// telemetry: track generate success overwrite/append
107 | 			try {
108 | 				const telemetry = await createTelemetry(config);
109 | 				await telemetry.publish({
110 | 					type: "cli_generate",
111 | 					payload: {
112 | 						outcome: schema.overwrite ? "overwritten" : "appended",
113 | 						config: getTelemetryAuthConfig(config),
114 | 					},
115 | 				});
116 | 			} catch {}
117 | 			process.exit(0);
118 | 		} else {
119 | 			logger.error("Schema generation aborted.");
120 | 			// telemetry: track generate aborted
121 | 			try {
122 | 				const telemetry = await createTelemetry(config);
123 | 				await telemetry.publish({
124 | 					type: "cli_generate",
125 | 					payload: {
126 | 						outcome: "aborted",
127 | 						config: getTelemetryAuthConfig(config),
128 | 					},
129 | 				});
130 | 			} catch {}
131 | 			process.exit(1);
132 | 		}
133 | 	}
134 | 
135 | 	if (options.y) {
136 | 		console.warn("WARNING: --y is deprecated. Consider -y or --yes");
137 | 		options.yes = true;
138 | 	}
139 | 
140 | 	let confirm = options.yes;
141 | 
142 | 	if (!confirm) {
143 | 		const response = await prompts({
144 | 			type: "confirm",
145 | 			name: "confirm",
146 | 			message: `Do you want to generate the schema to ${chalk.yellow(
147 | 				schema.fileName,
148 | 			)}?`,
149 | 		});
150 | 		confirm = response.confirm;
151 | 	}
152 | 
153 | 	if (!confirm) {
154 | 		logger.error("Schema generation aborted.");
155 | 		// telemetry: track generate aborted before write
156 | 		try {
157 | 			const telemetry = await createTelemetry(config);
158 | 			await telemetry.publish({
159 | 				type: "cli_generate",
160 | 				payload: { outcome: "aborted", config: getTelemetryAuthConfig(config) },
161 | 			});
162 | 		} catch {}
163 | 		process.exit(1);
164 | 	}
165 | 
166 | 	if (!options.output) {
167 | 		const dirExist = existsSync(path.dirname(path.join(cwd, schema.fileName)));
168 | 		if (!dirExist) {
169 | 			await fs.mkdir(path.dirname(path.join(cwd, schema.fileName)), {
170 | 				recursive: true,
171 | 			});
172 | 		}
173 | 	}
174 | 	await fs.writeFile(
175 | 		options.output || path.join(cwd, schema.fileName),
176 | 		schema.code,
177 | 	);
178 | 	logger.success(`🚀 Schema was generated successfully!`);
179 | 	// telemetry: track generate success
180 | 	try {
181 | 		const telemetry = await createTelemetry(config);
182 | 		await telemetry.publish({
183 | 			type: "cli_generate",
184 | 			payload: { outcome: "generated", config: getTelemetryAuthConfig(config) },
185 | 		});
186 | 	} catch {}
187 | 	process.exit(0);
188 | }
189 | 
190 | export const generate = new Command("generate")
191 | 	.option(
192 | 		"-c, --cwd <cwd>",
193 | 		"the working directory. defaults to the current directory.",
194 | 		process.cwd(),
195 | 	)
196 | 	.option(
197 | 		"--config <config>",
198 | 		"the path to the configuration file. defaults to the first configuration file found.",
199 | 	)
200 | 	.option("--output <output>", "the file to output to the generated schema")
201 | 	.option("-y, --yes", "automatically answer yes to all prompts", false)
202 | 	.option("--y", "(deprecated) same as --yes", false)
203 | 	.action(generateAction);
204 | 
```

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

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import * as SelectPrimitive from "@radix-ui/react-select";
  5 | import { Check, ChevronDown, ChevronUp, ChevronsUpDown } from "lucide-react";
  6 | 
  7 | import { cn } from "@/lib/utils";
  8 | 
  9 | const Select = SelectPrimitive.Root;
 10 | 
 11 | const SelectGroup = SelectPrimitive.Group;
 12 | 
 13 | const SelectValue = SelectPrimitive.Value;
 14 | 
 15 | const SelectTrigger = React.forwardRef<
 16 | 	React.ElementRef<typeof SelectPrimitive.Trigger>,
 17 | 	React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
 18 | >(({ className, children, ...props }, ref) => (
 19 | 	<SelectPrimitive.Trigger
 20 | 		ref={ref}
 21 | 		className={cn(
 22 | 			"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
 23 | 			className,
 24 | 		)}
 25 | 		{...props}
 26 | 	>
 27 | 		{children}
 28 | 		<SelectPrimitive.Icon asChild>
 29 | 			<ChevronsUpDown className="size-4 opacity-50" />
 30 | 		</SelectPrimitive.Icon>
 31 | 	</SelectPrimitive.Trigger>
 32 | ));
 33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
 34 | 
 35 | const SelectScrollUpButton = React.forwardRef<
 36 | 	React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
 37 | 	React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
 38 | >(({ className, ...props }, ref) => (
 39 | 	<SelectPrimitive.ScrollUpButton
 40 | 		ref={ref}
 41 | 		className={cn(
 42 | 			"flex cursor-default items-center justify-center py-1",
 43 | 			className,
 44 | 		)}
 45 | 		{...props}
 46 | 	>
 47 | 		<ChevronUp className="h-4 w-4" />
 48 | 	</SelectPrimitive.ScrollUpButton>
 49 | ));
 50 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
 51 | 
 52 | const SelectScrollDownButton = React.forwardRef<
 53 | 	React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
 54 | 	React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
 55 | >(({ className, ...props }, ref) => (
 56 | 	<SelectPrimitive.ScrollDownButton
 57 | 		ref={ref}
 58 | 		className={cn(
 59 | 			"flex cursor-default items-center justify-center py-1",
 60 | 			className,
 61 | 		)}
 62 | 		{...props}
 63 | 	>
 64 | 		<ChevronDown className="h-4 w-4" />
 65 | 	</SelectPrimitive.ScrollDownButton>
 66 | ));
 67 | SelectScrollDownButton.displayName =
 68 | 	SelectPrimitive.ScrollDownButton.displayName;
 69 | 
 70 | const SelectContent = React.forwardRef<
 71 | 	React.ElementRef<typeof SelectPrimitive.Content>,
 72 | 	React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
 73 | >(({ className, children, position = "popper", ...props }, ref) => (
 74 | 	<SelectPrimitive.Portal>
 75 | 		<SelectPrimitive.Content
 76 | 			ref={ref}
 77 | 			className={cn(
 78 | 				"relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
 79 | 				position === "popper" &&
 80 | 					"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
 81 | 				className,
 82 | 			)}
 83 | 			position={position}
 84 | 			{...props}
 85 | 		>
 86 | 			<SelectScrollUpButton />
 87 | 			<SelectPrimitive.Viewport
 88 | 				className={cn(
 89 | 					"p-1",
 90 | 					position === "popper" &&
 91 | 						"h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)",
 92 | 				)}
 93 | 			>
 94 | 				{children}
 95 | 			</SelectPrimitive.Viewport>
 96 | 			<SelectScrollDownButton />
 97 | 		</SelectPrimitive.Content>
 98 | 	</SelectPrimitive.Portal>
 99 | ));
100 | SelectContent.displayName = SelectPrimitive.Content.displayName;
101 | 
102 | const SelectLabel = React.forwardRef<
103 | 	React.ElementRef<typeof SelectPrimitive.Label>,
104 | 	React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
105 | >(({ className, ...props }, ref) => (
106 | 	<SelectPrimitive.Label
107 | 		ref={ref}
108 | 		className={cn("px-2 py-1.5 text-sm font-semibold", className)}
109 | 		{...props}
110 | 	/>
111 | ));
112 | SelectLabel.displayName = SelectPrimitive.Label.displayName;
113 | 
114 | const SelectItem = React.forwardRef<
115 | 	React.ElementRef<typeof SelectPrimitive.Item>,
116 | 	React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
117 | >(({ className, children, ...props }, ref) => (
118 | 	<SelectPrimitive.Item
119 | 		ref={ref}
120 | 		className={cn(
121 | 			"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
122 | 			className,
123 | 		)}
124 | 		{...props}
125 | 	>
126 | 		<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
127 | 			<SelectPrimitive.ItemIndicator>
128 | 				<Check className="h-4 w-4" />
129 | 			</SelectPrimitive.ItemIndicator>
130 | 		</span>
131 | 		<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
132 | 	</SelectPrimitive.Item>
133 | ));
134 | SelectItem.displayName = SelectPrimitive.Item.displayName;
135 | 
136 | const SelectSeparator = React.forwardRef<
137 | 	React.ElementRef<typeof SelectPrimitive.Separator>,
138 | 	React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
139 | >(({ className, ...props }, ref) => (
140 | 	<SelectPrimitive.Separator
141 | 		ref={ref}
142 | 		className={cn("-mx-1 my-1 h-px bg-muted", className)}
143 | 		{...props}
144 | 	/>
145 | ));
146 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
147 | 
148 | export {
149 | 	Select,
150 | 	SelectGroup,
151 | 	SelectValue,
152 | 	SelectTrigger,
153 | 	SelectContent,
154 | 	SelectLabel,
155 | 	SelectItem,
156 | 	SelectSeparator,
157 | 	SelectScrollUpButton,
158 | 	SelectScrollDownButton,
159 | };
160 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/last-login-method/custom-prefix.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, expect, it } from "vitest";
  2 | import { getTestInstance } from "../../test-utils/test-instance";
  3 | import { lastLoginMethod } from ".";
  4 | import { lastLoginMethodClient } from "./client";
  5 | import { parseCookies } from "../../cookies";
  6 | 
  7 | describe("lastLoginMethod custom cookie prefix", async () => {
  8 | 	it("should work with default cookie name regardless of custom prefix", async () => {
  9 | 		const { client, cookieSetter, testUser } = await getTestInstance(
 10 | 			{
 11 | 				advanced: {
 12 | 					cookiePrefix: "custom-auth",
 13 | 				},
 14 | 				plugins: [lastLoginMethod()],
 15 | 			},
 16 | 			{
 17 | 				clientOptions: {
 18 | 					plugins: [lastLoginMethodClient()],
 19 | 				},
 20 | 			},
 21 | 		);
 22 | 
 23 | 		const headers = new Headers();
 24 | 		await client.signIn.email(
 25 | 			{
 26 | 				email: testUser.email,
 27 | 				password: testUser.password,
 28 | 			},
 29 | 			{
 30 | 				onSuccess(context) {
 31 | 					cookieSetter(headers)(context);
 32 | 				},
 33 | 			},
 34 | 		);
 35 | 		const cookies = parseCookies(headers.get("cookie") || "");
 36 | 		// Uses exact cookie name from config, not affected by cookiePrefix
 37 | 		expect(cookies.get("better-auth.last_used_login_method")).toBe("email");
 38 | 	});
 39 | 
 40 | 	it("should work with custom cookie name and prefix", async () => {
 41 | 		const { client, cookieSetter, testUser } = await getTestInstance(
 42 | 			{
 43 | 				advanced: {
 44 | 					cookiePrefix: "my-app",
 45 | 				},
 46 | 				plugins: [lastLoginMethod({ cookieName: "my-app.last_method" })],
 47 | 			},
 48 | 			{
 49 | 				clientOptions: {
 50 | 					plugins: [lastLoginMethodClient()],
 51 | 				},
 52 | 			},
 53 | 		);
 54 | 
 55 | 		const headers = new Headers();
 56 | 		await client.signIn.email(
 57 | 			{
 58 | 				email: testUser.email,
 59 | 				password: testUser.password,
 60 | 			},
 61 | 			{
 62 | 				onSuccess(context) {
 63 | 					cookieSetter(headers)(context);
 64 | 				},
 65 | 			},
 66 | 		);
 67 | 		const cookies = parseCookies(headers.get("cookie") || "");
 68 | 		expect(cookies.get("my-app.last_method")).toBe("email");
 69 | 	});
 70 | 
 71 | 	it("should work with custom cookie name regardless of prefix", async () => {
 72 | 		const { client, cookieSetter, testUser } = await getTestInstance(
 73 | 			{
 74 | 				advanced: {
 75 | 					cookiePrefix: "my-app",
 76 | 				},
 77 | 				plugins: [lastLoginMethod({ cookieName: "last_login_method" })],
 78 | 			},
 79 | 			{
 80 | 				clientOptions: {
 81 | 					plugins: [lastLoginMethodClient()],
 82 | 				},
 83 | 			},
 84 | 		);
 85 | 
 86 | 		const headers = new Headers();
 87 | 		await client.signIn.email(
 88 | 			{
 89 | 				email: testUser.email,
 90 | 				password: testUser.password,
 91 | 			},
 92 | 			{
 93 | 				onSuccess(context) {
 94 | 					cookieSetter(headers)(context);
 95 | 				},
 96 | 			},
 97 | 		);
 98 | 		const cookies = parseCookies(headers.get("cookie") || "");
 99 | 		// Uses exact cookie name from config, not affected by cookiePrefix
100 | 		expect(cookies.get("last_login_method")).toBe("email");
101 | 	});
102 | 
103 | 	it("should work with cross-subdomain and custom prefix", async () => {
104 | 		const { client, testUser } = await getTestInstance(
105 | 			{
106 | 				baseURL: "https://auth.example.com",
107 | 				advanced: {
108 | 					cookiePrefix: "custom-auth",
109 | 					crossSubDomainCookies: {
110 | 						enabled: true,
111 | 						domain: "example.com",
112 | 					},
113 | 				},
114 | 				plugins: [lastLoginMethod()],
115 | 			},
116 | 			{
117 | 				clientOptions: {
118 | 					plugins: [lastLoginMethodClient()],
119 | 				},
120 | 			},
121 | 		);
122 | 
123 | 		await client.signIn.email(
124 | 			{
125 | 				email: testUser.email,
126 | 				password: testUser.password,
127 | 			},
128 | 			{
129 | 				onResponse(context) {
130 | 					const setCookie = context.response.headers.get("set-cookie");
131 | 					expect(setCookie).toContain("Domain=example.com");
132 | 					expect(setCookie).toContain("SameSite=Lax");
133 | 					// Uses exact cookie name from config, not affected by cookiePrefix
134 | 					expect(setCookie).toContain(
135 | 						"better-auth.last_used_login_method=email",
136 | 					);
137 | 				},
138 | 			},
139 | 		);
140 | 	});
141 | 
142 | 	it("should work with cross-origin cookies", async () => {
143 | 		const { client, testUser } = await getTestInstance(
144 | 			{
145 | 				baseURL: "https://api.example.com",
146 | 				advanced: {
147 | 					crossOriginCookies: {
148 | 						enabled: true,
149 | 					},
150 | 					defaultCookieAttributes: {
151 | 						sameSite: "none",
152 | 						secure: true,
153 | 					},
154 | 				},
155 | 				plugins: [lastLoginMethod()],
156 | 			},
157 | 			{
158 | 				clientOptions: {
159 | 					plugins: [lastLoginMethodClient()],
160 | 				},
161 | 			},
162 | 		);
163 | 
164 | 		await client.signIn.email(
165 | 			{
166 | 				email: testUser.email,
167 | 				password: testUser.password,
168 | 			},
169 | 			{
170 | 				onResponse(context) {
171 | 					const setCookie = context.response.headers.get("set-cookie");
172 | 					expect(setCookie).toContain("SameSite=None");
173 | 					expect(setCookie).toContain("Secure");
174 | 					// Should not contain Domain attribute for cross-origin
175 | 					expect(setCookie).not.toContain("Domain=");
176 | 					expect(setCookie).toContain(
177 | 						"better-auth.last_used_login_method=email",
178 | 					);
179 | 				},
180 | 			},
181 | 		);
182 | 	});
183 | 
184 | 	it("should handle cross-origin on localhost for development", async () => {
185 | 		const { client, testUser } = await getTestInstance(
186 | 			{
187 | 				baseURL: "http://localhost:3000",
188 | 				advanced: {
189 | 					crossOriginCookies: {
190 | 						enabled: true,
191 | 						allowLocalhostUnsecure: true,
192 | 					},
193 | 					defaultCookieAttributes: {
194 | 						sameSite: "none",
195 | 						secure: false,
196 | 					},
197 | 				},
198 | 				plugins: [lastLoginMethod()],
199 | 			},
200 | 			{
201 | 				clientOptions: {
202 | 					plugins: [lastLoginMethodClient()],
203 | 				},
204 | 			},
205 | 		);
206 | 
207 | 		await client.signIn.email(
208 | 			{
209 | 				email: testUser.email,
210 | 				password: testUser.password,
211 | 			},
212 | 			{
213 | 				onResponse(context) {
214 | 					const setCookie = context.response.headers.get("set-cookie");
215 | 					expect(setCookie).toContain("SameSite=None");
216 | 					// Should not contain Secure on localhost when allowLocalhostUnsecure is true
217 | 					expect(setCookie).not.toContain("Secure");
218 | 					expect(setCookie).toContain(
219 | 						"better-auth.last_used_login_method=email",
220 | 					);
221 | 				},
222 | 			},
223 | 		);
224 | 	});
225 | });
226 | 
```

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

```markdown
  1 | ---
  2 | title: Dodo Payments
  3 | description: Better Auth Plugin for Dodo Payments
  4 | ---
  5 | 
  6 | [Dodo Payments](https://dodopayments.com) is a global Merchant-of-Record platform that lets AI, SaaS and digital businesses sell in 150+ countries without touching tax, fraud, or compliance. A single, developer-friendly API powers checkout, billing, and payouts so you can launch worldwide in minutes.
  7 | 
  8 | <Card
  9 |   href="https://discord.gg/bYqAp4ayYh"
 10 |   title="Get support on Dodo Payments' Discord"
 11 | >
 12 |   This plugin is maintained by the Dodo Payments team.<br />
 13 |   Have questions? Our team is available on Discord to assist you anytime.
 14 | </Card>
 15 | 
 16 | ## Features
 17 | 
 18 | - Automatic customer creation on sign-up
 19 | - Type-safe checkout flows with product slug mapping
 20 | - Self-service customer portal
 21 | - Real-time webhook event processing with signature verification
 22 | 
 23 | <Card href="https://app.dodopayments.com" title="Get started with Dodo Payments">
 24 |   You need a Dodo Payments account and API keys to use this integration.
 25 | </Card>
 26 | 
 27 | ## Installation
 28 | 
 29 | <Steps>
 30 |   <Step title="Install dependencies">
 31 |     Run the following command in your project root:
 32 | ```bash
 33 | npm install @dodopayments/better-auth dodopayments better-auth zod
 34 | ```
 35 |   
 36 |   </Step>
 37 |   <Step title="Configure environment variables">
 38 |     Add these to your `.env` file:
 39 | ```txt
 40 | DODO_PAYMENTS_API_KEY=your_api_key_here
 41 | DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here
 42 | ```
 43 |   </Step>
 44 | 
 45 |   <Step title="Set up server-side integration">
 46 |     Create or update `src/lib/auth.ts`:
 47 | ```typescript
 48 | import { betterAuth } from "better-auth";
 49 | import {
 50 |   dodopayments,
 51 |   checkout,
 52 |   portal,
 53 |   webhooks,
 54 | } from "@dodopayments/better-auth";
 55 | import DodoPayments from "dodopayments";
 56 | 
 57 | export const dodoPayments = new DodoPayments({
 58 |   bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
 59 |   environment: "test_mode"
 60 | });
 61 | 
 62 | export const auth = betterAuth({
 63 |   plugins: [
 64 |     dodopayments({
 65 |       client: dodoPayments,
 66 |       createCustomerOnSignUp: true,
 67 |       use: [
 68 |         checkout({
 69 |           products: [
 70 |             {
 71 |               productId: "pdt_xxxxxxxxxxxxxxxxxxxxx",
 72 |               slug: "premium-plan",
 73 |             },
 74 |           ],
 75 |           successUrl: "/dashboard/success",
 76 |           authenticatedUsersOnly: true,
 77 |         }),
 78 |         portal(),
 79 |         webhooks({
 80 |           webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_SECRET!,
 81 |           onPayload: async (payload) => {
 82 |             console.log("Received webhook:", payload.event_type);
 83 |           },
 84 |         }),
 85 |       ],
 86 |     }),
 87 |   ],
 88 | });
 89 | ```
 90 |     <Card>
 91 |       Set `environment` to `live_mode` for production.
 92 |     </Card>
 93 |   </Step>
 94 | 
 95 |   <Step title="Set up client-side integration">
 96 |     Create or update `src/lib/auth-client.ts`:
 97 | ```typescript
 98 | import { dodopaymentsClient } from "@dodopayments/better-auth";
 99 | 
100 | export const authClient = createAuthClient({
101 |   baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000",
102 |   plugins: [dodopaymentsClient()],
103 | });
104 | ```
105 |   </Step>
106 | </Steps>
107 | 
108 | ## Usage
109 | 
110 | ### Creating a Checkout Session
111 | 
112 | ```typescript
113 | const { data: checkout, error } = await authClient.dodopayments.checkout({
114 |   slug: "premium-plan",
115 |   customer: {
116 |     email: "[email protected]",
117 |     name: "John Doe",
118 |   },
119 |   billing: {
120 |     city: "San Francisco",
121 |     country: "US",
122 |     state: "CA",
123 |     street: "123 Market St",
124 |     zipcode: "94103",
125 |   },
126 |   referenceId: "order_123",
127 | });
128 | 
129 | if (checkout) {
130 |   window.location.href = checkout.url;
131 | }
132 | ```
133 | 
134 | ### Accessing the Customer Portal
135 | 
136 | ```typescript
137 | const { data: customerPortal, error } = await authClient.dodopayments.customer.portal();
138 | if (customerPortal && customerPortal.redirect) {
139 |   window.location.href = customerPortal.url;
140 | }
141 | ```
142 | 
143 | ### Listing Customer Data
144 | 
145 | ```typescript
146 | // Get subscriptions
147 | const { data: subscriptions, error } =
148 |   await authClient.dodopayments.customer.subscriptions.list({
149 |     query: {
150 |       limit: 10,
151 |       page: 1,
152 |       active: true,
153 |     },
154 |   });
155 | 
156 | // Get payment history
157 | const { data: payments, error } = await authClient.dodopayments.customer.payments.list({
158 |   query: {
159 |     limit: 10,
160 |     page: 1,
161 |     status: "succeeded",
162 |   },
163 | });
164 | ```
165 | 
166 | ### Webhooks
167 | 
168 | <Card>
169 |   The webhooks plugin processes real-time payment events from Dodo Payments with secure signature verification. The default endpoint is `/api/auth/dodopayments/webhooks`.
170 | </Card>
171 | 
172 | <Steps>
173 |   <Step title="Generate and set webhook secret">
174 |     Generate a webhook secret for your endpoint URL (e.g., `https://your-domain.com/api/auth/dodopayments/webhooks`) in the Dodo Payments Dashboard and set it in your .env file:
175 | ```txt
176 | DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here
177 | ```
178 |   </Step>
179 | 
180 |   <Step title="Handle webhook events">
181 |     Example handler:
182 | ```typescript
183 | webhooks({
184 |   webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_SECRET!,
185 |   onPayload: async (payload) => {
186 |     console.log("Received webhook:", payload.event_type);
187 |   },
188 | });
189 | ```
190 |   </Step>
191 | </Steps>
192 | 
193 | ## Configuration Reference
194 | 
195 | ### Plugin Options
196 | 
197 | - **client** (required): DodoPayments client instance
198 | - **createCustomerOnSignUp** (optional): Auto-create customers on user signup  
199 | - **use** (required): Array of plugins to enable (checkout, portal, webhooks)
200 | 
201 | ### Checkout Plugin Options
202 | 
203 | - **products**: Array of products or async function returning products
204 | - **successUrl**: URL to redirect after successful payment
205 | - **authenticatedUsersOnly**: Require user authentication (default: false)
206 | 
207 | If you encounter any issues, please refer to the [Dodo Payments documentation](https://docs.dodopayments.com) for troubleshooting steps.
208 | 
```

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

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import * as SelectPrimitive from "@radix-ui/react-select";
  5 | import { Check, ChevronDown, ChevronUp, ChevronsUpDown } from "lucide-react";
  6 | 
  7 | import { cn } from "@/lib/utils";
  8 | 
  9 | const Select = SelectPrimitive.Root;
 10 | 
 11 | const SelectGroup = SelectPrimitive.Group;
 12 | 
 13 | const SelectValue = SelectPrimitive.Value;
 14 | 
 15 | const SelectTrigger = React.forwardRef<
 16 | 	React.ElementRef<typeof SelectPrimitive.Trigger>,
 17 | 	React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
 18 | >(({ className, children, ...props }, ref) => (
 19 | 	<SelectPrimitive.Trigger
 20 | 		ref={ref}
 21 | 		className={cn(
 22 | 			"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
 23 | 			className,
 24 | 		)}
 25 | 		{...props}
 26 | 	>
 27 | 		{children}
 28 | 		<SelectPrimitive.Icon asChild>
 29 | 			<ChevronsUpDown className="size-4 opacity-50" />
 30 | 		</SelectPrimitive.Icon>
 31 | 	</SelectPrimitive.Trigger>
 32 | ));
 33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
 34 | 
 35 | const SelectScrollUpButton = React.forwardRef<
 36 | 	React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
 37 | 	React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
 38 | >(({ className, ...props }, ref) => (
 39 | 	<SelectPrimitive.ScrollUpButton
 40 | 		ref={ref}
 41 | 		className={cn(
 42 | 			"flex cursor-default items-center justify-center py-1",
 43 | 			className,
 44 | 		)}
 45 | 		{...props}
 46 | 	>
 47 | 		<ChevronUp className="h-4 w-4" />
 48 | 	</SelectPrimitive.ScrollUpButton>
 49 | ));
 50 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
 51 | 
 52 | const SelectScrollDownButton = React.forwardRef<
 53 | 	React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
 54 | 	React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
 55 | >(({ className, ...props }, ref) => (
 56 | 	<SelectPrimitive.ScrollDownButton
 57 | 		ref={ref}
 58 | 		className={cn(
 59 | 			"flex cursor-default items-center justify-center py-1",
 60 | 			className,
 61 | 		)}
 62 | 		{...props}
 63 | 	>
 64 | 		<ChevronDown className="h-4 w-4" />
 65 | 	</SelectPrimitive.ScrollDownButton>
 66 | ));
 67 | SelectScrollDownButton.displayName =
 68 | 	SelectPrimitive.ScrollDownButton.displayName;
 69 | 
 70 | const SelectContent = React.forwardRef<
 71 | 	React.ElementRef<typeof SelectPrimitive.Content>,
 72 | 	React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
 73 | >(({ className, children, position = "popper", ...props }, ref) => (
 74 | 	<SelectPrimitive.Portal>
 75 | 		<SelectPrimitive.Content
 76 | 			ref={ref}
 77 | 			className={cn(
 78 | 				"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
 79 | 				position === "popper" &&
 80 | 					"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
 81 | 				className,
 82 | 			)}
 83 | 			position={position}
 84 | 			{...props}
 85 | 		>
 86 | 			<SelectScrollUpButton />
 87 | 			<SelectPrimitive.Viewport
 88 | 				className={cn(
 89 | 					"p-1",
 90 | 					position === "popper" &&
 91 | 						"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
 92 | 				)}
 93 | 			>
 94 | 				{children}
 95 | 			</SelectPrimitive.Viewport>
 96 | 			<SelectScrollDownButton />
 97 | 		</SelectPrimitive.Content>
 98 | 	</SelectPrimitive.Portal>
 99 | ));
100 | SelectContent.displayName = SelectPrimitive.Content.displayName;
101 | 
102 | const SelectLabel = React.forwardRef<
103 | 	React.ElementRef<typeof SelectPrimitive.Label>,
104 | 	React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
105 | >(({ className, ...props }, ref) => (
106 | 	<SelectPrimitive.Label
107 | 		ref={ref}
108 | 		className={cn("px-2 py-1.5 text-sm font-semibold", className)}
109 | 		{...props}
110 | 	/>
111 | ));
112 | SelectLabel.displayName = SelectPrimitive.Label.displayName;
113 | 
114 | const SelectItem = React.forwardRef<
115 | 	React.ElementRef<typeof SelectPrimitive.Item>,
116 | 	React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
117 | >(({ className, children, ...props }, ref) => (
118 | 	<SelectPrimitive.Item
119 | 		ref={ref}
120 | 		className={cn(
121 | 			"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
122 | 			className,
123 | 		)}
124 | 		{...props}
125 | 	>
126 | 		<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
127 | 			<SelectPrimitive.ItemIndicator>
128 | 				<Check className="h-4 w-4" />
129 | 			</SelectPrimitive.ItemIndicator>
130 | 		</span>
131 | 		<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
132 | 	</SelectPrimitive.Item>
133 | ));
134 | SelectItem.displayName = SelectPrimitive.Item.displayName;
135 | 
136 | const SelectSeparator = React.forwardRef<
137 | 	React.ElementRef<typeof SelectPrimitive.Separator>,
138 | 	React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
139 | >(({ className, ...props }, ref) => (
140 | 	<SelectPrimitive.Separator
141 | 		ref={ref}
142 | 		className={cn("-mx-1 my-1 h-px bg-muted", className)}
143 | 		{...props}
144 | 	/>
145 | ));
146 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
147 | 
148 | export {
149 | 	Select,
150 | 	SelectGroup,
151 | 	SelectValue,
152 | 	SelectTrigger,
153 | 	SelectContent,
154 | 	SelectLabel,
155 | 	SelectItem,
156 | 	SelectSeparator,
157 | 	SelectScrollUpButton,
158 | 	SelectScrollDownButton,
159 | };
160 | 
```
Page 17/69FirstPrevNextLast