#
tokens: 48607/50000 10/1100 files (page 24/69)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 24 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-declaration
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── demo.ts
│       │   │   │   │   ├── index.ts
│       │   │   │   │   └── username.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
│   │   └── 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.base.json
├── tsconfig.json
└── turbo.json
```

# Files

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

```typescript
  1 | import { describe, it, expect, vi, beforeEach, afterEach } 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 | 
226 | describe("should work in development/test environment", () => {
227 | 	const LOCALHOST_IP = "127.0.0.1";
228 | 	const REQUEST_PATH = "/sign-in/email";
229 | 
230 | 	let originalNodeEnv: string | undefined;
231 | 	beforeEach(() => {
232 | 		originalNodeEnv = process.env.NODE_ENV;
233 | 	});
234 | 	afterEach(() => {
235 | 		process.env.NODE_ENV = originalNodeEnv;
236 | 		vi.unstubAllEnvs();
237 | 	});
238 | 
239 | 	it("should work in development environment", async () => {
240 | 		vi.stubEnv("NODE_ENV", "development");
241 | 
242 | 		const store = new Map<string, string>();
243 | 		const { client, testUser } = await getTestInstance({
244 | 			rateLimit: {
245 | 				enabled: true,
246 | 				window: 10,
247 | 				max: 3,
248 | 			},
249 | 			secondaryStorage: {
250 | 				set(key, value) {
251 | 					store.set(key, value);
252 | 				},
253 | 				get(key) {
254 | 					return store.get(key) || null;
255 | 				},
256 | 				delete(key) {
257 | 					store.delete(key);
258 | 				},
259 | 			},
260 | 		});
261 | 
262 | 		for (let i = 0; i < 4; i++) {
263 | 			const response = await client.signIn.email({
264 | 				email: testUser.email,
265 | 				password: testUser.password,
266 | 			});
267 | 
268 | 			if (i >= 3) {
269 | 				expect(response.error?.status).toBe(429);
270 | 			} else {
271 | 				expect(response.error).toBeNull();
272 | 			}
273 | 		}
274 | 
275 | 		const signInKeys = Array.from(store.keys()).filter((key) =>
276 | 			key.endsWith(REQUEST_PATH),
277 | 		);
278 | 
279 | 		expect(signInKeys.length).toBeGreaterThan(0);
280 | 		expect(signInKeys[0]).toBe(`${LOCALHOST_IP}${REQUEST_PATH}`);
281 | 	});
282 | 
283 | 	it("should work in test environment", async () => {
284 | 		vi.stubEnv("NODE_ENV", "test");
285 | 
286 | 		const store = new Map<string, string>();
287 | 		const { client, testUser } = await getTestInstance({
288 | 			rateLimit: {
289 | 				enabled: true,
290 | 				window: 10,
291 | 				max: 3,
292 | 			},
293 | 			secondaryStorage: {
294 | 				set(key, value) {
295 | 					store.set(key, value);
296 | 				},
297 | 				get(key) {
298 | 					return store.get(key) || null;
299 | 				},
300 | 				delete(key) {
301 | 					store.delete(key);
302 | 				},
303 | 			},
304 | 		});
305 | 
306 | 		for (let i = 0; i < 4; i++) {
307 | 			const response = await client.signIn.email({
308 | 				email: testUser.email,
309 | 				password: testUser.password,
310 | 			});
311 | 
312 | 			if (i >= 3) {
313 | 				expect(response.error?.status).toBe(429);
314 | 			} else {
315 | 				expect(response.error).toBeNull();
316 | 			}
317 | 		}
318 | 
319 | 		const signInKeys = Array.from(store.keys()).filter((key) =>
320 | 			key.endsWith(REQUEST_PATH),
321 | 		);
322 | 
323 | 		expect(signInKeys.length).toBeGreaterThan(0);
324 | 		expect(signInKeys[0]).toBe(`${LOCALHOST_IP}${REQUEST_PATH}`);
325 | 	});
326 | });
327 | 
```

--------------------------------------------------------------------------------
/docs/app/global.css:
--------------------------------------------------------------------------------

```css
  1 | @import "tailwindcss";
  2 | @import "fumadocs-ui/css/black.css";
  3 | @import "fumadocs-ui/css/preset.css";
  4 | @config "../tailwind.config.js";
  5 | @plugin 'tailwindcss-animate';
  6 | @custom-variant dark (&:is(.dark *));
  7 | 
  8 | :root {
  9 | 	--fd-nav-height: 56px;
 10 | 	--fd-banner-height: 0px;
 11 | 	--fd-tocnav-height: 0px;
 12 | 
 13 | 	--background: oklch(1 0 0);
 14 | 
 15 | 	--foreground: oklch(0.147 0.004 49.25);
 16 | 
 17 | 	--card: oklch(1 0 0);
 18 | 
 19 | 	--card-foreground: oklch(0.147 0.004 49.25);
 20 | 
 21 | 	--popover: oklch(1 0 0);
 22 | 
 23 | 	--popover-foreground: oklch(0.147 0.004 49.25);
 24 | 
 25 | 	--primary: oklch(0.216 0.006 56.043);
 26 | 
 27 | 	--primary-foreground: oklch(0.985 0.001 106.423);
 28 | 
 29 | 	--secondary: oklch(0.97 0.001 106.424);
 30 | 
 31 | 	--secondary-foreground: oklch(0.216 0.006 56.043);
 32 | 
 33 | 	--muted: oklch(0.97 0.001 106.424);
 34 | 
 35 | 	--muted-foreground: oklch(0.553 0.013 58.071);
 36 | 
 37 | 	--accent: oklch(0.97 0.001 106.424);
 38 | 
 39 | 	--accent-foreground: oklch(0.216 0.006 56.043);
 40 | 
 41 | 	--destructive: oklch(0.577 0.245 27.325);
 42 | 
 43 | 	--destructive-foreground: oklch(0.577 0.245 27.325);
 44 | 
 45 | 	--border: oklch(0.923 0.003 48.717);
 46 | 
 47 | 	--input: oklch(0.923 0.003 48.717);
 48 | 
 49 | 	--ring: oklch(0.709 0.01 56.259);
 50 | 
 51 | 	--chart-1: oklch(0.646 0.222 41.116);
 52 | 
 53 | 	--chart-2: oklch(0.6 0.118 184.704);
 54 | 
 55 | 	--chart-3: oklch(0.398 0.07 227.392);
 56 | 
 57 | 	--chart-4: oklch(0.828 0.189 84.429);
 58 | 
 59 | 	--chart-5: oklch(0.769 0.188 70.08);
 60 | 
 61 | 	--radius: 0.2rem;
 62 | 
 63 | 	--sidebar: oklch(0.985 0.001 106.423);
 64 | 
 65 | 	--sidebar-foreground: oklch(0.147 0.004 49.25);
 66 | 
 67 | 	--sidebar-primary: oklch(0.216 0.006 56.043);
 68 | 
 69 | 	--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
 70 | 
 71 | 	--sidebar-accent: oklch(0.97 0.001 106.424);
 72 | 
 73 | 	--sidebar-accent-foreground: oklch(0.216 0.006 56.043);
 74 | 
 75 | 	--sidebar-border: oklch(0.923 0.003 48.717);
 76 | 
 77 | 	--sidebar-ring: oklch(0.709 0.01 56.259);
 78 | 
 79 | 	/* Scrollbar theme (light) */
 80 | 	--scrollbar-thumb: var(--border);
 81 | 	--scrollbar-thumb-hover: var(--ring);
 82 | 	--scrollbar-track: transparent;
 83 | }
 84 | 
 85 | .dark {
 86 | 	--background: hsl(0 0% 0%);
 87 | 
 88 | 	--foreground: oklch(0.985 0.001 106.423);
 89 | 
 90 | 	--card: oklch(0.147 0.004 49.25);
 91 | 
 92 | 	--card-foreground: oklch(0.985 0.001 106.423);
 93 | 
 94 | 	--popover: oklch(0.147 0.004 49.25);
 95 | 
 96 | 	--popover-foreground: oklch(0.985 0.001 106.423);
 97 | 
 98 | 	--primary: oklch(0.985 0.001 106.423);
 99 | 
100 | 	--primary-foreground: oklch(0.216 0.006 56.043);
101 | 
102 | 	--secondary: oklch(0.268 0.007 34.298);
103 | 
104 | 	--secondary-foreground: oklch(0.985 0.001 106.423);
105 | 
106 | 	--muted: oklch(0.268 0.007 34.298);
107 | 
108 | 	--muted-foreground: oklch(0.709 0.01 56.259);
109 | 
110 | 	--accent: oklch(0.268 0.007 34.298);
111 | 
112 | 	--accent-foreground: oklch(0.985 0.001 106.423);
113 | 
114 | 	--destructive: oklch(0.396 0.141 25.723);
115 | 
116 | 	--destructive-foreground: oklch(0.637 0.237 25.331);
117 | 
118 | 	--border: oklch(0.268 0.007 34.298);
119 | 
120 | 	--input: oklch(0.268 0.007 34.298);
121 | 
122 | 	--ring: oklch(0.553 0.013 58.071);
123 | 
124 | 	--chart-1: oklch(0.488 0.243 264.376);
125 | 
126 | 	--chart-2: oklch(0.696 0.17 162.48);
127 | 
128 | 	--chart-3: oklch(0.769 0.188 70.08);
129 | 
130 | 	--chart-4: oklch(0.627 0.265 303.9);
131 | 
132 | 	--chart-5: oklch(0.645 0.246 16.439);
133 | 
134 | 	--sidebar: oklch(0.216 0.006 56.043);
135 | 
136 | 	--sidebar-foreground: oklch(0.985 0.001 106.423);
137 | 
138 | 	--sidebar-primary: oklch(0.488 0.243 264.376);
139 | 
140 | 	--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
141 | 
142 | 	--sidebar-accent: oklch(0.268 0.007 34.298);
143 | 
144 | 	--sidebar-accent-foreground: oklch(0.985 0.001 106.423);
145 | 
146 | 	--sidebar-border: oklch(0.268 0.007 34.298);
147 | 
148 | 	--sidebar-ring: oklch(0.553 0.013 58.071);
149 | 
150 | 	/* Scrollbar theme (dark) */
151 | 	--scrollbar-thumb: var(--border);
152 | 	--scrollbar-thumb-hover: var(--ring);
153 | 	--scrollbar-track: transparent;
154 | }
155 | 
156 | @theme inline {
157 | 	--color-background: var(--background);
158 | 
159 | 	--color-foreground: var(--foreground);
160 | 
161 | 	--color-card: var(--card);
162 | 
163 | 	--color-card-foreground: var(--card-foreground);
164 | 
165 | 	--color-popover: var(--popover);
166 | 
167 | 	--color-popover-foreground: var(--popover-foreground);
168 | 
169 | 	--color-primary: var(--primary);
170 | 
171 | 	--color-primary-foreground: var(--primary-foreground);
172 | 
173 | 	--color-secondary: var(--secondary);
174 | 
175 | 	--color-secondary-foreground: var(--secondary-foreground);
176 | 
177 | 	--color-muted: var(--muted);
178 | 
179 | 	--color-muted-foreground: var(--muted-foreground);
180 | 
181 | 	--color-accent: var(--accent);
182 | 
183 | 	--color-accent-foreground: var(--accent-foreground);
184 | 
185 | 	--color-destructive: var(--destructive);
186 | 
187 | 	--color-destructive-foreground: var(--destructive-foreground);
188 | 
189 | 	--color-border: var(--border);
190 | 
191 | 	--color-input: var(--input);
192 | 
193 | 	--color-ring: var(--ring);
194 | 
195 | 	--color-chart-1: var(--chart-1);
196 | 
197 | 	--color-chart-2: var(--chart-2);
198 | 
199 | 	--color-chart-3: var(--chart-3);
200 | 
201 | 	--color-chart-4: var(--chart-4);
202 | 
203 | 	--color-chart-5: var(--chart-5);
204 | 
205 | 	--radius-sm: calc(var(--radius) - 4px);
206 | 
207 | 	--radius-md: calc(var(--radius) - 2px);
208 | 
209 | 	--radius-lg: var(--radius);
210 | 
211 | 	--radius-xl: calc(var(--radius) + 4px);
212 | 
213 | 	--color-sidebar: var(--sidebar);
214 | 
215 | 	--color-sidebar-foreground: var(--sidebar-foreground);
216 | 
217 | 	--color-sidebar-primary: var(--sidebar-primary);
218 | 
219 | 	--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
220 | 
221 | 	--color-sidebar-accent: var(--sidebar-accent);
222 | 
223 | 	--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
224 | 
225 | 	--color-sidebar-border: var(--sidebar-border);
226 | 
227 | 	--color-sidebar-ring: var(--sidebar-ring);
228 | 	--animate-accordion-down: accordion-down 0.2s ease-out;
229 | 	--animate-accordion-up: accordion-up 0.2s ease-out;
230 | 
231 | 	@keyframes accordion-down {
232 | 		from {
233 | 			height: 0;
234 | 		}
235 | 		to {
236 | 			height: var(--radix-accordion-content-height);
237 | 		}
238 | 	}
239 | 
240 | 	@keyframes accordion-up {
241 | 		from {
242 | 			height: var(--radix-accordion-content-height);
243 | 		}
244 | 		to {
245 | 			height: 0;
246 | 		}
247 | 	}
248 | }
249 | 
250 | @layer base {
251 | 	* {
252 | 		@apply border-border outline-ring/50;
253 | 	}
254 | 	body {
255 | 		@apply overscroll-none bg-background text-foreground selection:bg-foreground selection:text-background;
256 | 	}
257 | }
258 | 
259 | html {
260 | 	scroll-behavior: auto;
261 | 	scroll-padding-top: calc(
262 | 		var(--fd-nav-height, 56px) +
263 | 		var(--fd-banner-height, 0px) +
264 | 		var(--fd-tocnav-height, 0px) +
265 | 		24px
266 | 	);
267 | }
268 | 
269 | html:not([data-anchor-scrolling]) {
270 | 	scroll-behavior: smooth;
271 | }
272 | 
273 | /* Global, accessible custom scrollbars */
274 | * {
275 | 	scrollbar-width: thin; /* Firefox */
276 | 	scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
277 | }
278 | 
279 | /* WebKit-based browsers */
280 | ::-webkit-scrollbar {
281 | 	width: 12px;
282 | 	height: 12px;
283 | }
284 | ::-webkit-scrollbar-track {
285 | 	background: var(--scrollbar-track);
286 | }
287 | ::-webkit-scrollbar-thumb {
288 | 	background-color: var(--scrollbar-thumb);
289 | 	border-radius: 9999px;
290 | 	border: 3px solid transparent; /* creates a gap between thumb and track */
291 | 	background-clip: content-box;
292 | }
293 | ::-webkit-scrollbar-thumb:hover {
294 | 	background-color: var(--scrollbar-thumb-hover);
295 | }
296 | ::-webkit-scrollbar-corner {
297 | 	background: transparent;
298 | }
299 | 
300 | @layer utilities {
301 | 	.no-scrollbar::-webkit-scrollbar {
302 | 		display: none;
303 | 	}
304 | 	.no-scrollbar {
305 | 		-ms-overflow-style: none;
306 | 		scrollbar-width: none;
307 | 	}
308 | }
309 | 
310 | .markdown-content {
311 | 	@apply text-sm leading-relaxed;
312 | }
313 | 
314 | .markdown-content pre {
315 | 	@apply max-w-full overflow-x-auto;
316 | }
317 | 
318 | .markdown-content pre code {
319 | 	@apply whitespace-pre-wrap break-words;
320 | }
321 | 
322 | .markdown-content h1,
323 | .markdown-content h2,
324 | .markdown-content h3,
325 | .markdown-content h4,
326 | .markdown-content h5,
327 | .markdown-content h6 {
328 | 	@apply font-semibold text-foreground;
329 | }
330 | 
331 | .markdown-content p {
332 | 	@apply mb-2 last:mb-0;
333 | }
334 | 
335 | .markdown-content ul,
336 | .markdown-content ol {
337 | 	@apply space-y-2 list-disc;
338 | }
339 | 
340 | .markdown-content li {
341 | 	@apply text-sm;
342 | }
343 | 
344 | .markdown-content code {
345 | 	@apply bg-muted px-1.5 py-0.5 rounded text-xs font-mono;
346 | }
347 | 
348 | .markdown-content pre {
349 | 	@apply overflow-x-auto;
350 | }
351 | 
352 | .markdown-content blockquote {
353 | 	@apply border-l-4 border-muted-foreground/20 pl-4 my-2 italic;
354 | }
355 | 
356 | .markdown-content table {
357 | 	@apply w-full border-collapse;
358 | }
359 | 
360 | .markdown-content th,
361 | .markdown-content td {
362 | 	@apply border border-border px-2 py-1 text-xs;
363 | }
364 | 
365 | .markdown-content th {
366 | 	@apply bg-muted font-medium;
367 | }
368 | 
369 | @keyframes stream-pulse {
370 | 	0%,
371 | 	100% {
372 | 		opacity: 1;
373 | 	}
374 | 	50% {
375 | 		opacity: 0.5;
376 | 	}
377 | }
378 | 
379 | .streaming-cursor {
380 | 	animation: stream-pulse 1s ease-in-out infinite;
381 | }
382 | 
```

--------------------------------------------------------------------------------
/docs/components/ui/context-menu.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
  5 | import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
  6 | 
  7 | import { cn } from "@/lib/utils";
  8 | 
  9 | function ContextMenu({
 10 | 	...props
 11 | }: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
 12 | 	return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />;
 13 | }
 14 | 
 15 | function ContextMenuTrigger({
 16 | 	...props
 17 | }: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
 18 | 	return (
 19 | 		<ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
 20 | 	);
 21 | }
 22 | 
 23 | function ContextMenuGroup({
 24 | 	...props
 25 | }: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
 26 | 	return (
 27 | 		<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
 28 | 	);
 29 | }
 30 | 
 31 | function ContextMenuPortal({
 32 | 	...props
 33 | }: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
 34 | 	return (
 35 | 		<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
 36 | 	);
 37 | }
 38 | 
 39 | function ContextMenuSub({
 40 | 	...props
 41 | }: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
 42 | 	return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />;
 43 | }
 44 | 
 45 | function ContextMenuRadioGroup({
 46 | 	...props
 47 | }: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
 48 | 	return (
 49 | 		<ContextMenuPrimitive.RadioGroup
 50 | 			data-slot="context-menu-radio-group"
 51 | 			{...props}
 52 | 		/>
 53 | 	);
 54 | }
 55 | 
 56 | function ContextMenuSubTrigger({
 57 | 	className,
 58 | 	inset,
 59 | 	children,
 60 | 	...props
 61 | }: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
 62 | 	inset?: boolean;
 63 | }) {
 64 | 	return (
 65 | 		<ContextMenuPrimitive.SubTrigger
 66 | 			data-slot="context-menu-sub-trigger"
 67 | 			data-inset={inset}
 68 | 			className={cn(
 69 | 				"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
 70 | 				className,
 71 | 			)}
 72 | 			{...props}
 73 | 		>
 74 | 			{children}
 75 | 			<ChevronRightIcon className="ml-auto" />
 76 | 		</ContextMenuPrimitive.SubTrigger>
 77 | 	);
 78 | }
 79 | 
 80 | function ContextMenuSubContent({
 81 | 	className,
 82 | 	...props
 83 | }: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
 84 | 	return (
 85 | 		<ContextMenuPrimitive.SubContent
 86 | 			data-slot="context-menu-sub-content"
 87 | 			className={cn(
 88 | 				"bg-popover text-popover-foreground 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 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg",
 89 | 				className,
 90 | 			)}
 91 | 			{...props}
 92 | 		/>
 93 | 	);
 94 | }
 95 | 
 96 | function ContextMenuContent({
 97 | 	className,
 98 | 	...props
 99 | }: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {
100 | 	return (
101 | 		<ContextMenuPrimitive.Portal>
102 | 			<ContextMenuPrimitive.Content
103 | 				data-slot="context-menu-content"
104 | 				className={cn(
105 | 					"bg-popover text-popover-foreground 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 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md",
106 | 					className,
107 | 				)}
108 | 				{...props}
109 | 			/>
110 | 		</ContextMenuPrimitive.Portal>
111 | 	);
112 | }
113 | 
114 | function ContextMenuItem({
115 | 	className,
116 | 	inset,
117 | 	variant = "default",
118 | 	...props
119 | }: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
120 | 	inset?: boolean;
121 | 	variant?: "default" | "destructive";
122 | }) {
123 | 	return (
124 | 		<ContextMenuPrimitive.Item
125 | 			data-slot="context-menu-item"
126 | 			data-inset={inset}
127 | 			data-variant={variant}
128 | 			className={cn(
129 | 				"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/40 data-[variant=destructive]:focus:text-destructive-foreground data-[variant=destructive]:*:[svg]:!text-destructive-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
130 | 				className,
131 | 			)}
132 | 			{...props}
133 | 		/>
134 | 	);
135 | }
136 | 
137 | function ContextMenuCheckboxItem({
138 | 	className,
139 | 	children,
140 | 	checked,
141 | 	...props
142 | }: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
143 | 	return (
144 | 		<ContextMenuPrimitive.CheckboxItem
145 | 			data-slot="context-menu-checkbox-item"
146 | 			className={cn(
147 | 				"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
148 | 				className,
149 | 			)}
150 | 			checked={checked}
151 | 			{...props}
152 | 		>
153 | 			<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
154 | 				<ContextMenuPrimitive.ItemIndicator>
155 | 					<CheckIcon className="size-4" />
156 | 				</ContextMenuPrimitive.ItemIndicator>
157 | 			</span>
158 | 			{children}
159 | 		</ContextMenuPrimitive.CheckboxItem>
160 | 	);
161 | }
162 | 
163 | function ContextMenuRadioItem({
164 | 	className,
165 | 	children,
166 | 	...props
167 | }: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {
168 | 	return (
169 | 		<ContextMenuPrimitive.RadioItem
170 | 			data-slot="context-menu-radio-item"
171 | 			className={cn(
172 | 				"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
173 | 				className,
174 | 			)}
175 | 			{...props}
176 | 		>
177 | 			<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
178 | 				<ContextMenuPrimitive.ItemIndicator>
179 | 					<CircleIcon className="size-2 fill-current" />
180 | 				</ContextMenuPrimitive.ItemIndicator>
181 | 			</span>
182 | 			{children}
183 | 		</ContextMenuPrimitive.RadioItem>
184 | 	);
185 | }
186 | 
187 | function ContextMenuLabel({
188 | 	className,
189 | 	inset,
190 | 	...props
191 | }: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
192 | 	inset?: boolean;
193 | }) {
194 | 	return (
195 | 		<ContextMenuPrimitive.Label
196 | 			data-slot="context-menu-label"
197 | 			data-inset={inset}
198 | 			className={cn(
199 | 				"text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
200 | 				className,
201 | 			)}
202 | 			{...props}
203 | 		/>
204 | 	);
205 | }
206 | 
207 | function ContextMenuSeparator({
208 | 	className,
209 | 	...props
210 | }: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
211 | 	return (
212 | 		<ContextMenuPrimitive.Separator
213 | 			data-slot="context-menu-separator"
214 | 			className={cn("bg-border -mx-1 my-1 h-px", className)}
215 | 			{...props}
216 | 		/>
217 | 	);
218 | }
219 | 
220 | function ContextMenuShortcut({
221 | 	className,
222 | 	...props
223 | }: React.ComponentProps<"span">) {
224 | 	return (
225 | 		<span
226 | 			data-slot="context-menu-shortcut"
227 | 			className={cn(
228 | 				"text-muted-foreground ml-auto text-xs tracking-widest",
229 | 				className,
230 | 			)}
231 | 			{...props}
232 | 		/>
233 | 	);
234 | }
235 | 
236 | export {
237 | 	ContextMenu,
238 | 	ContextMenuTrigger,
239 | 	ContextMenuContent,
240 | 	ContextMenuItem,
241 | 	ContextMenuCheckboxItem,
242 | 	ContextMenuRadioItem,
243 | 	ContextMenuLabel,
244 | 	ContextMenuSeparator,
245 | 	ContextMenuShortcut,
246 | 	ContextMenuGroup,
247 | 	ContextMenuPortal,
248 | 	ContextMenuSub,
249 | 	ContextMenuSubContent,
250 | 	ContextMenuSubTrigger,
251 | 	ContextMenuRadioGroup,
252 | };
253 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/guides/your-first-plugin.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: Create your first plugin
  3 | description: A step-by-step guide to creating your first Better Auth plugin.
  4 | ---
  5 | 
  6 | In this guide, we’ll walk you through the steps of creating your first Better Auth plugin.
  7 | 
  8 | 
  9 | <Callout type="warn">
 10 | This guide assumes you have <Link href="/docs/installation">setup the basics</Link> of Better Auth and are ready to create your first plugin.
 11 | </Callout>
 12 | 
 13 | <Steps>
 14 | <Step>
 15 | ## Plan your idea
 16 | Before beginning, you must know what plugin you intend to create.
 17 | 
 18 | In this guide, we’ll create a **birthday plugin** to keep track of user birth dates.
 19 | </Step>
 20 | 
 21 | <Step>
 22 | ## Server plugin first
 23 | Better Auth plugins operate as a pair: a <Link href="/docs/concepts/plugins#create-a-server-plugin">server plugin</Link> and a <Link href="/docs/concepts/plugins#create-a-client-plugin">client plugin</Link>.
 24 | The server plugin forms the foundation of your authentication system, while the client plugin provides convenient frontend APIs to interact with your server implementation.
 25 | 
 26 | 
 27 | <Callout>
 28 | You can read more about server/client plugins in our <Link href="/docs/concepts/plugins#creating-a-plugin">documentation</Link>.
 29 | </Callout>
 30 | 
 31 | ### Creating the server plugin
 32 | Go ahead and find a suitable location to create your birthday plugin folder, with an `index.ts` file within.
 33 | <Files>
 34 |   <Folder name="birthday-plugin" defaultOpen>
 35 |     <File name="index.ts" />
 36 |   </Folder>
 37 | </Files>
 38 | In the `index.ts` file, we’ll export a function that represents our server plugin.
 39 | This will be what we will later add to our plugin list in the `auth.ts` file.
 40 | 
 41 | ```ts title="index.ts"
 42 | import { createAuthClient } from "better-auth/client";
 43 | import type { BetterAuthPlugin } from "better-auth";
 44 | 
 45 | export const birthdayPlugin = () =>
 46 |   ({
 47 |     id: "birthdayPlugin",
 48 |   } satisfies BetterAuthPlugin);
 49 | 
 50 | ```
 51 | Although this does nothing, you have technically just made yourself your first plugin, congratulations! 🎉
 52 | 
 53 | </Step>
 54 | 
 55 | <Step>
 56 | ### Defining a schema
 57 | In order to save each user’s birthday data, we must create a schema on top of the `user` model.
 58 | 
 59 | By creating a schema here, this also allows <Link href="/docs/concepts/cli">Better Auth’s CLI</Link> to generate the schemas required to update your database.
 60 | 
 61 | <Callout type="info">
 62 | You can learn more about <Link href="/docs/concepts/plugins#schema">plugin schemas here</Link>.
 63 | </Callout>
 64 | 
 65 | ```ts title="index.ts"
 66 | //...
 67 | export const birthdayPlugin = () =>
 68 |   ({
 69 |     id: "birthdayPlugin",
 70 |     schema: {// [!code highlight]
 71 |       user: {// [!code highlight]
 72 |         fields: {// [!code highlight]
 73 |           birthday: {// [!code highlight]
 74 |             type: "date", // string, number, boolean, date // [!code highlight]
 75 |             required: true, // if the field should be required on a new record. (default: false) // [!code highlight]
 76 |             unique: false, // if the field should be unique. (default: false) // [!code highlight]
 77 |             references: null // if the field is a reference to another table. (default: null) // [!code highlight]
 78 |           },// [!code highlight]
 79 |         },// [!code highlight]
 80 |       },// [!code highlight]
 81 |     },
 82 |   } satisfies BetterAuthPlugin);
 83 | ```
 84 | 
 85 | </Step>
 86 | 
 87 | <Step>
 88 | ### Authorization logic
 89 | For this example guide, we’ll set up authentication logic to check and ensure that the user who signs-up is older than 5.
 90 | But the same concept could be applied for something like verifying users agreeing to the TOS or anything alike.
 91 | 
 92 | To do this, we’ll utilize <Link href="/docs/concepts/plugins#hooks">Hooks</Link>, which allows us to run code `before` or `after` an action is performed.
 93 | 
 94 | ```ts title="index.ts"
 95 | export const birthdayPlugin = () => ({
 96 |     //...
 97 |     // In our case, we want to write authorization logic,
 98 |     // meaning we want to intercept it `before` hand.
 99 |     hooks: {
100 |       before: [
101 |         {
102 |           matcher: (context) => /* ... */,
103 |           handler: createAuthMiddleware(async (ctx) => {
104 |             //...
105 |           }),
106 |         },
107 |       ],
108 |     },
109 | } satisfies BetterAuthPlugin)
110 | ```
111 | 
112 | In our case we want to match any requests going to the signup path:
113 | ```ts title="Before hook"
114 | {
115 |   matcher: (context) => context.path.startsWith("/sign-up/email"),
116 |   //...
117 | }
118 | ```
119 | 
120 | And for our logic, we’ll write the following code to check the if user’s birthday makes them above 5 years old.
121 | ```ts title="Imports"
122 | import { APIError } from "better-auth/api";
123 | import { createAuthMiddleware } from "better-auth/plugins";
124 | ```
125 | ```ts title="Before hook"
126 | {
127 |   //...
128 |   handler: createAuthMiddleware(async (ctx) => {
129 |     const { birthday } = ctx.body;
130 |     if(!(birthday instanceof Date)) {
131 |       throw new APIError("BAD_REQUEST", { message: "Birthday must be of type Date." });
132 |     }
133 | 
134 |     const today = new Date();
135 |     const fiveYearsAgo = new Date(today.setFullYear(today.getFullYear() - 5));
136 | 
137 |     if(birthday >= fiveYearsAgo) {
138 |       throw new APIError("BAD_REQUEST", { message: "User must be above 5 years old." });
139 |     }
140 | 
141 |     return { context: ctx };
142 |   }),
143 | }
144 | ```
145 | 
146 | **Authorized!** 🔒
147 | 
148 | We’ve now successfully written code to ensure authorization for users above 5!
149 | 
150 | </Step>
151 | 
152 | <Step>
153 | ## Client Plugin
154 | We’re close to the finish line! 🏁
155 | 
156 | Now that we have created our server plugin, the next step is to develop our client plugin.
157 | Since there isn’t much frontend APIs going on for this plugin, there isn’t much to do!
158 | 
159 | First, let’s create our `client.ts` file first:
160 | <Files>
161 |   <Folder name="birthday-plugin" defaultOpen>
162 |     <File name="index.ts" />
163 |     <File name="client.ts" />
164 |   </Folder>
165 | </Files>
166 | Then, add the following code:
167 | ```ts title="client.ts"
168 | import { BetterAuthClientPlugin } from "better-auth";
169 | import type { birthdayPlugin } from "./index"; // make sure to import the server plugin as a type // [!code highlight]
170 | 
171 | type BirthdayPlugin = typeof birthdayPlugin;
172 | 
173 | export const birthdayClientPlugin = () => {
174 |   return {
175 |     id: "birthdayPlugin",
176 |     $InferServerPlugin: {} as ReturnType<BirthdayPlugin>,
177 |   } satisfies BetterAuthClientPlugin;
178 | };
179 | ```
180 | What we’ve done is allow the client plugin to infer the types defined by our schema from the server plugin.
181 | 
182 | And that’s it!  This is all it takes for the birthday client plugin. 🎂
183 | 
184 | </Step>
185 | 
186 | <Step>
187 | ## Initiate your plugin!
188 | Both the `client` and `server` plugins are now ready, the last step is to import them to both your `auth-client.ts` and your `server.ts` files respectively to initiate the plugin.
189 | 
190 | ### Server initiation
191 | ```ts title="server.ts"
192 | import { betterAuth } from "better-auth";
193 | import { birthdayPlugin } from "./birthday-plugin";// [!code highlight]
194 |  
195 | export const auth = betterAuth({
196 |     plugins: [
197 |       birthdayPlugin(),// [!code highlight]
198 |     ]
199 | });
200 | ```
201 | 
202 | ### Client initiation
203 | ```ts title="auth-client.ts"
204 | import { createAuthClient } from "better-auth/client";
205 | import { birthdayClientPlugin } from "./birthday-plugin/client";// [!code highlight]
206 |  
207 | const authClient = createAuthClient({
208 |     plugins: [
209 |       birthdayClientPlugin()// [!code highlight]
210 |     ]
211 | });
212 | ```
213 | 
214 | ### Oh yeah, the schemas! 
215 | Don’t forget to add your `birthday` field to your `user` table model! 
216 | 
217 | Or, use the `generate` <Link href="/docs/concepts/cli#generate">CLI command</Link>:
218 | ```bash
219 | npx @better-auth/cli@latest generate
220 | ```
221 | 
222 | </Step>
223 | </Steps>
224 | 
225 | ## Wrapping Up
226 | 
227 | Congratulations! You’ve successfully created your first ever Better Auth plugin.
228 | We highly recommend you visit our <Link href="/docs/concepts/plugins">plugins documentation</Link> to learn more information.
229 | 
230 | If you have a plugin you’d like to share with the community, feel free to let us know through 
231 | our <Link href="https://discord.gg/better-auth">Discord server</Link>,
232 | or through a <Link href="https://github.com/better-auth/better-auth/pulls">pull-request</Link>
233 | and we may add it to the <Link href="/docs/plugins/community-plugins">community-plugins</Link> list!
234 | 
```

--------------------------------------------------------------------------------
/packages/cli/test/generate-all-db.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, expect, it } from "vitest";
  2 | import { generateDrizzleSchema } from "../src/generators/drizzle";
  3 | import { drizzleAdapter } from "better-auth/adapters/drizzle";
  4 | import { twoFactor, username } from "better-auth/plugins";
  5 | import { passkey } from "better-auth/plugins/passkey";
  6 | import type { BetterAuthOptions } from "better-auth";
  7 | 
  8 | describe("generate drizzle schema for all databases", async () => {
  9 | 	it("should generate drizzle schema for MySQL", async () => {
 10 | 		const schema = await generateDrizzleSchema({
 11 | 			file: "test.drizzle",
 12 | 			adapter: drizzleAdapter(
 13 | 				{},
 14 | 				{
 15 | 					provider: "mysql",
 16 | 					schema: {},
 17 | 				},
 18 | 			)({} as BetterAuthOptions),
 19 | 			options: {
 20 | 				database: drizzleAdapter(
 21 | 					{},
 22 | 					{
 23 | 						provider: "mysql",
 24 | 						schema: {},
 25 | 					},
 26 | 				),
 27 | 				plugins: [twoFactor(), username()],
 28 | 				user: {
 29 | 					modelName: "custom_user",
 30 | 				},
 31 | 				account: {
 32 | 					modelName: "custom_account",
 33 | 				},
 34 | 				session: {
 35 | 					modelName: "custom_session",
 36 | 				},
 37 | 				verification: {
 38 | 					modelName: "custom_verification",
 39 | 				},
 40 | 			},
 41 | 		});
 42 | 		expect(schema.code).toMatchFileSnapshot(
 43 | 			"./__snapshots__/auth-schema-mysql.txt",
 44 | 		);
 45 | 	});
 46 | 
 47 | 	it("should generate drizzle schema for SQLite", async () => {
 48 | 		const schema = await generateDrizzleSchema({
 49 | 			file: "test.drizzle",
 50 | 			adapter: drizzleAdapter(
 51 | 				{},
 52 | 				{
 53 | 					provider: "sqlite",
 54 | 					schema: {},
 55 | 				},
 56 | 			)({} as BetterAuthOptions),
 57 | 			options: {
 58 | 				database: drizzleAdapter(
 59 | 					{},
 60 | 					{
 61 | 						provider: "sqlite",
 62 | 						schema: {},
 63 | 					},
 64 | 				),
 65 | 				plugins: [twoFactor(), username()],
 66 | 				user: {
 67 | 					modelName: "custom_user",
 68 | 				},
 69 | 				account: {
 70 | 					modelName: "custom_account",
 71 | 				},
 72 | 				session: {
 73 | 					modelName: "custom_session",
 74 | 				},
 75 | 				verification: {
 76 | 					modelName: "custom_verification",
 77 | 				},
 78 | 			},
 79 | 		});
 80 | 		expect(schema.code).toMatchFileSnapshot(
 81 | 			"./__snapshots__/auth-schema-sqlite.txt",
 82 | 		);
 83 | 	});
 84 | 
 85 | 	it("should generate drizzle schema for MySQL with number id", async () => {
 86 | 		const schema = await generateDrizzleSchema({
 87 | 			file: "test.drizzle",
 88 | 			adapter: drizzleAdapter(
 89 | 				{},
 90 | 				{
 91 | 					provider: "mysql",
 92 | 					schema: {},
 93 | 				},
 94 | 			)({} as BetterAuthOptions),
 95 | 			options: {
 96 | 				database: drizzleAdapter(
 97 | 					{},
 98 | 					{
 99 | 						provider: "mysql",
100 | 						schema: {},
101 | 					},
102 | 				),
103 | 				plugins: [twoFactor(), username()],
104 | 				advanced: {
105 | 					database: {
106 | 						useNumberId: true,
107 | 					},
108 | 				},
109 | 				user: {
110 | 					modelName: "custom_user",
111 | 				},
112 | 				account: {
113 | 					modelName: "custom_account",
114 | 				},
115 | 				session: {
116 | 					modelName: "custom_session",
117 | 				},
118 | 				verification: {
119 | 					modelName: "custom_verification",
120 | 				},
121 | 			},
122 | 		});
123 | 		expect(schema.code).toMatchFileSnapshot(
124 | 			"./__snapshots__/auth-schema-mysql-number-id.txt",
125 | 		);
126 | 	});
127 | 
128 | 	it("should generate drizzle schema for SQLite with number id", async () => {
129 | 		const schema = await generateDrizzleSchema({
130 | 			file: "test.drizzle",
131 | 			adapter: drizzleAdapter(
132 | 				{},
133 | 				{
134 | 					provider: "sqlite",
135 | 					schema: {},
136 | 				},
137 | 			)({} as BetterAuthOptions),
138 | 			options: {
139 | 				database: drizzleAdapter(
140 | 					{},
141 | 					{
142 | 						provider: "sqlite",
143 | 						schema: {},
144 | 					},
145 | 				),
146 | 				plugins: [twoFactor(), username()],
147 | 				advanced: {
148 | 					database: {
149 | 						useNumberId: true,
150 | 					},
151 | 				},
152 | 				user: {
153 | 					modelName: "custom_user",
154 | 				},
155 | 				account: {
156 | 					modelName: "custom_account",
157 | 				},
158 | 				session: {
159 | 					modelName: "custom_session",
160 | 				},
161 | 				verification: {
162 | 					modelName: "custom_verification",
163 | 				},
164 | 			},
165 | 		});
166 | 		expect(schema.code).toMatchFileSnapshot(
167 | 			"./__snapshots__/auth-schema-sqlite-number-id.txt",
168 | 		);
169 | 	});
170 | });
171 | 
172 | describe("generate drizzle schema for all databases with passkey plugin", async () => {
173 | 	it("should generate drizzle schema for MySQL with passkey plugin", async () => {
174 | 		const schema = await generateDrizzleSchema({
175 | 			file: "test.drizzle",
176 | 			adapter: drizzleAdapter(
177 | 				{},
178 | 				{
179 | 					provider: "mysql",
180 | 					schema: {},
181 | 				},
182 | 			)({} as BetterAuthOptions),
183 | 			options: {
184 | 				database: drizzleAdapter(
185 | 					{},
186 | 					{
187 | 						provider: "mysql",
188 | 						schema: {},
189 | 					},
190 | 				),
191 | 				plugins: [passkey()],
192 | 				user: {
193 | 					modelName: "custom_user",
194 | 				},
195 | 				account: {
196 | 					modelName: "custom_account",
197 | 				},
198 | 				session: {
199 | 					modelName: "custom_session",
200 | 				},
201 | 				verification: {
202 | 					modelName: "custom_verification",
203 | 				},
204 | 			},
205 | 		});
206 | 		expect(schema.code).toMatchFileSnapshot(
207 | 			"./__snapshots__/auth-schema-mysql-passkey.txt",
208 | 		);
209 | 	});
210 | 
211 | 	it("should generate drizzle schema for SQLite with passkey plugin", async () => {
212 | 		const schema = await generateDrizzleSchema({
213 | 			file: "test.drizzle",
214 | 			adapter: drizzleAdapter(
215 | 				{},
216 | 				{
217 | 					provider: "sqlite",
218 | 					schema: {},
219 | 				},
220 | 			)({} as BetterAuthOptions),
221 | 			options: {
222 | 				database: drizzleAdapter(
223 | 					{},
224 | 					{
225 | 						provider: "sqlite",
226 | 						schema: {},
227 | 					},
228 | 				),
229 | 				plugins: [passkey()],
230 | 				user: {
231 | 					modelName: "custom_user",
232 | 				},
233 | 				account: {
234 | 					modelName: "custom_account",
235 | 				},
236 | 				session: {
237 | 					modelName: "custom_session",
238 | 				},
239 | 				verification: {
240 | 					modelName: "custom_verification",
241 | 				},
242 | 			},
243 | 		});
244 | 		expect(schema.code).toMatchFileSnapshot(
245 | 			"./__snapshots__/auth-schema-sqlite-passkey.txt",
246 | 		);
247 | 	});
248 | 
249 | 	it("should generate drizzle schema for PostgreSQL with passkey plugin", async () => {
250 | 		const schema = await generateDrizzleSchema({
251 | 			file: "test.drizzle",
252 | 			adapter: drizzleAdapter(
253 | 				{},
254 | 				{
255 | 					provider: "pg",
256 | 					schema: {},
257 | 				},
258 | 			)({} as BetterAuthOptions),
259 | 			options: {
260 | 				database: drizzleAdapter(
261 | 					{},
262 | 					{
263 | 						provider: "pg",
264 | 						schema: {},
265 | 					},
266 | 				),
267 | 				plugins: [passkey()],
268 | 				user: {
269 | 					modelName: "custom_user",
270 | 				},
271 | 				account: {
272 | 					modelName: "custom_account",
273 | 				},
274 | 				session: {
275 | 					modelName: "custom_session",
276 | 				},
277 | 				verification: {
278 | 					modelName: "custom_verification",
279 | 				},
280 | 			},
281 | 		});
282 | 		expect(schema.code).toMatchFileSnapshot(
283 | 			"./__snapshots__/auth-schema-pg-passkey.txt",
284 | 		);
285 | 	});
286 | 
287 | 	it("should generate drizzle schema for MySQL with passkey plugin and number id", async () => {
288 | 		const schema = await generateDrizzleSchema({
289 | 			file: "test.drizzle",
290 | 			adapter: drizzleAdapter(
291 | 				{},
292 | 				{
293 | 					provider: "mysql",
294 | 					schema: {},
295 | 				},
296 | 			)({} as BetterAuthOptions),
297 | 			options: {
298 | 				database: drizzleAdapter(
299 | 					{},
300 | 					{
301 | 						provider: "mysql",
302 | 						schema: {},
303 | 					},
304 | 				),
305 | 				plugins: [passkey()],
306 | 				advanced: {
307 | 					database: {
308 | 						useNumberId: true,
309 | 					},
310 | 				},
311 | 				user: {
312 | 					modelName: "custom_user",
313 | 				},
314 | 				account: {
315 | 					modelName: "custom_account",
316 | 				},
317 | 				session: {
318 | 					modelName: "custom_session",
319 | 				},
320 | 				verification: {
321 | 					modelName: "custom_verification",
322 | 				},
323 | 			},
324 | 		});
325 | 		expect(schema.code).toMatchFileSnapshot(
326 | 			"./__snapshots__/auth-schema-mysql-passkey-number-id.txt",
327 | 		);
328 | 	});
329 | 
330 | 	it("should generate drizzle schema for SQLite with passkey plugin and number id", async () => {
331 | 		const schema = await generateDrizzleSchema({
332 | 			file: "test.drizzle",
333 | 			adapter: drizzleAdapter(
334 | 				{},
335 | 				{
336 | 					provider: "sqlite",
337 | 					schema: {},
338 | 				},
339 | 			)({} as BetterAuthOptions),
340 | 			options: {
341 | 				database: drizzleAdapter(
342 | 					{},
343 | 					{
344 | 						provider: "sqlite",
345 | 						schema: {},
346 | 					},
347 | 				),
348 | 				plugins: [passkey()],
349 | 				advanced: {
350 | 					database: {
351 | 						useNumberId: true,
352 | 					},
353 | 				},
354 | 				user: {
355 | 					modelName: "custom_user",
356 | 				},
357 | 				account: {
358 | 					modelName: "custom_account",
359 | 				},
360 | 				session: {
361 | 					modelName: "custom_session",
362 | 				},
363 | 				verification: {
364 | 					modelName: "custom_verification",
365 | 				},
366 | 			},
367 | 		});
368 | 		expect(schema.code).toMatchFileSnapshot(
369 | 			"./__snapshots__/auth-schema-sqlite-passkey-number-id.txt",
370 | 		);
371 | 	});
372 | });
373 | 
```

--------------------------------------------------------------------------------
/docs/content/blogs/1-3.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: "Better Auth 1.3"
  3 | description: "SSO with SAML, Multi Team Support, Additional Fields for Organization, Performance and more."
  4 | date: 2025-07-19
  5 | author:
  6 |   name: "Bereket Engida"
  7 |   avatar: "/avatars/beka.jpg"
  8 |   twitter: "iambereket"
  9 | image: "/release-og/1-3.png"
 10 | tags: ["1.3", "authentication", "oidc", "mcp", "sso", "organization"]
 11 | ---
 12 | 
 13 | ## Better Auth 1.3 Release
 14 | 
 15 | We're excited to announce the release of Better Auth 1.3. This release includes a lot of new features and improvements.
 16 | 
 17 | To upgrade, run:
 18 | 
 19 | ```package-install
 20 | npm install [email protected]
 21 | ```
 22 | 
 23 | ---
 24 | 
 25 | ## 🚀 Highlights
 26 | 
 27 | ### **SSO Plugin**
 28 | 
 29 | The SSO plugin has been moved to its own package and now supports both **OIDC** and **SAML 2.0**.
 30 | 
 31 | 👉 [Read the SSO docs](/docs/plugins/sso)
 32 | 
 33 | ```ts title="auth.ts"
 34 | import { betterAuth } from "better-auth";
 35 | import { sso } from "@better-auth/sso";
 36 | 
 37 | export const auth = betterAuth({
 38 |   plugins: [
 39 |     sso({
 40 |       oidc: {
 41 |         clientId: process.env.OIDC_CLIENT_ID!,
 42 |         clientSecret: process.env.OIDC_CLIENT_SECRET!,
 43 |       },
 44 |       saml: {
 45 |         entryPoint: "https://example.com/saml",
 46 |         issuer: "better-auth-example",
 47 |         certificate: "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
 48 |       },
 49 |       providersLimit: async (user) => {
 50 |         const plan = await getUserPlan(user);
 51 |         return plan.name === "pro" ? 10 : 1;
 52 |       },
 53 |     }),
 54 |   ],
 55 | });
 56 | ```
 57 | 
 58 | ---
 59 | 
 60 | ### **OIDC & MCP Plugins – Now Stable**
 61 | 
 62 | Both OIDC and MCP plugins are production‑ready.
 63 | 
 64 | ✅ Features:
 65 | 
 66 | * Refresh token support in discovery & token endpoints
 67 | * JWKs and PKCE for public clients
 68 | * Trusted clients
 69 | * Encrypted & hashed client secrets
 70 | 
 71 | 👉 [Read OIDC docs](/docs/plugins/oidc-provider)
 72 | 👉 [Read MCP docs](/docs/plugins/mcp)
 73 | 
 74 | ```ts title="auth.ts"
 75 | import { mcp } from "better-auth/plugins";
 76 | 
 77 | export const auth = betterAuth({
 78 |   plugins: [
 79 |     mcp({
 80 |       loginPage: "/login",
 81 |     }),
 82 |   ],
 83 | });
 84 | ```
 85 | 
 86 | ---
 87 | 
 88 | ### **Stripe Plugin is now production ready**
 89 | 
 90 | The Stripe plugin is now stable and usage based pricing is coming very soon.
 91 | 
 92 | 👉 [Read Stripe docs](/docs/plugins/stripe)
 93 | 
 94 | 
 95 | ```ts title="auth.ts"
 96 | import { betterAuth } from "better-auth";
 97 | import { stripe } from "@better-auth/stripe";
 98 | 
 99 | export const auth = betterAuth({
100 |   plugins: [
101 |     stripe({
102 |       // ...
103 |     }),
104 |   ],
105 | });
106 | ```
107 | 
108 | ### **SIWE Plugin**
109 | 
110 | Native support for **Sign‑In with Ethereum**.
111 | 
112 | 👉 [Read SIWE docs](/docs/plugins/siwe)
113 | 
114 | ```ts title="auth.ts"
115 | import { siwe } from "better-auth/plugins";
116 | 
117 | export const auth = betterAuth({
118 |   plugins: [
119 |     siwe(),
120 |   ],
121 | });
122 | ```
123 | 
124 | ---
125 | 
126 | ### **New Social Providers**
127 | 
128 | We’ve added providers for **Notion, Slack, Linear, and Faceit**.
129 | 
130 | ```ts title="auth.ts"
131 | import { betterAuth } from "better-auth";
132 | 
133 | export const auth = betterAuth({
134 |   socialProviders: {
135 |     notion: { /* ... */ },
136 |     slack: { /* ... */ },
137 |     linear: { /* ... */ },
138 |     faceit: { /* ... */ },
139 |   },
140 | });
141 | ```
142 | 
143 | ---
144 | 
145 | ### **SvelteKit Cookie Helper Plugin**
146 | 
147 | Utilities for handling cookies in SvelteKit server actions.
148 | 
149 | <Callout type="warn">
150 |   Breaking change: `building` and `getRequestEvent` must now be passed in as props.
151 | </Callout>
152 | 
153 | ```ts title="auth.ts"
154 | import { betterAuth } from "better-auth";
155 | import { sveltekitCookies } from "better-auth/svelte-kit";
156 | import { getRequestEvent } from "$app/server";
157 | 
158 | export const auth = betterAuth({
159 |   plugins: [sveltekitCookies(getRequestEvent)],
160 | });
161 | ```
162 | 
163 | ---
164 | 
165 | ### **Email Verification on Sign‑In**
166 | 
167 | ```ts title="auth.ts"
168 | export const auth = betterAuth({
169 |   emailVerification: {
170 |     sendOnSignIn: true, // sends a verification email on sign‑in if the user isn’t verified
171 |   },
172 | });
173 | ```
174 | 
175 | ---
176 | 
177 | ### **Multi‑Team Support**
178 | 
179 | The organization plugin now supports members belonging to multiple teams.
180 | 
181 | **Breaking change:**
182 | `teamId` has been removed from the `member` table. A new `teamMembers` table is required.
183 | 
184 | ```ts title="auth.ts"
185 | export const auth = betterAuth({
186 |   plugins: [
187 |     organization({
188 |       // ...
189 |     }),
190 |   ],
191 | });
192 | ```
193 | 
194 | ```ts title="auth-client.ts"
195 | import { createAuthClient } from "better-auth/client";
196 | import { organizationClient } from "better-auth/client/plugins";
197 | import { auth } from "./auth";
198 | 
199 | export const authClient = createAuthClient({
200 |   // pass your auth instance to infer additional fields
201 |   plugins: [organizationClient({ $inferAuth: {} as typeof auth })], // [!code highlight]
202 | });
203 | ```
204 | 
205 | ---
206 | 
207 | ### **Additional Organization Fields**
208 | 
209 | Add custom fields to `organization`, `member`, and `invitation` models.
210 | 
211 | ```ts title="auth.ts"
212 | export const auth = betterAuth({
213 |   plugins: [
214 |     organization({
215 |       schema: {
216 |         organization: { additionalFields: { /* ... */ } },
217 |         member: { additionalFields: { /* ... */ } },
218 |         invitation: { additionalFields: { /* ... */ } },
219 |       },
220 |     }),
221 |   ],
222 | });
223 | ```
224 | 
225 | Other new options:
226 | 
227 | * `maximumMembersPerTeam` – set team member limits
228 | * `listUserInvitations` – list all invitations for a user
229 | 
230 | ---
231 | 
232 | ### **Generic OAuth Improvements**
233 | 
234 | * Added support for extra token URL params
235 | * OAuth token encryption options
236 | 
237 | ```ts title="auth.ts"
238 | export const auth = betterAuth({
239 |   plugins: [
240 |     genericOAuth({
241 |       // ...
242 |     }),
243 |   ],
244 | });
245 | ```
246 | 
247 | ---
248 | 
249 | ### **API Keys**
250 | 
251 | * `requireName` option for key creation
252 | * `verifyKey` now supports async functions
253 | 
254 | ---
255 | 
256 | ### **Username**
257 | 
258 | * Availability checks
259 | * Custom normalization
260 | 
261 | ---
262 | 
263 | ### ✨ More Features
264 | 
265 | * Migrated to **Zod 4** for better type safety and performance
266 | * CLI supports custom adapter `createSchema`
267 | * `inferAuth` utility to infer types from the client
268 | * Improved docs with `auth` and `authClient` examples
269 | * `rememberMe` support in `signUp`
270 | * `afterEmailVerification` hook
271 | * `freshAge` and custom `errorURL` respected properly
272 | * OAuth2 tokens now include `refresh_token_expires_in`
273 | 
274 | ---
275 | 
276 | ### 🐛 Bug Fixes & Improvements
277 | 
278 | #### Plugins
279 | 
280 | * Expo: Fixed type path import
281 | * SSO: Fixed SAML redirection & type checks
282 | * Dropbox: Token access type support
283 | * Stripe:
284 | 
285 |   * Prevent duplicate customers
286 |   * Allow upgrading incomplete subscriptions
287 | * Admin:
288 | 
289 |   * Fixed missing `ctx` in hooks
290 |   * Proper error when removing invalid user IDs
291 | 
292 | #### OAuth & Providers
293 | 
294 | * Fixed duplicate OAuth registration
295 | * Improved Google/Microsoft scope handling
296 | * Fixed malformed error URLs in generic OAuth
297 | * Facebook: Better detection for limited token JWT
298 | * Twitter: Improved email verification logic
299 | 
300 | #### Core Authentication
301 | 
302 | * Exclude current user from username uniqueness check
303 | * Support `callbackURL` in `signInUsername`
304 | * Allow account linking without email
305 | * Fixed missing `null` type in `/get-session` response
306 | * Global `onSuccess` hook now works
307 | * JWT: Alternate algorithms supported in JWKS
308 | * `origin-check`: Wildcard trusted origins supported
309 | 
310 | #### CLI, DB, and Adapters
311 | 
312 | * CLI: Improved Drizzle schema formatting
313 | * MongoAdapter: Works with `create-adapter`
314 | * Schema generation respects `useNumberId`
315 | * Postgres: Better varchar normalization and type comparison
316 | * Drizzle CLI: Uses `serial` as PK if `useNumberId` is enabled
317 | 
318 | #### Email & OTP
319 | 
320 | * OTPs now encrypted
321 | * Fixed `onEmailVerification` not firing
322 | * Proper error when sign‑up is disabled
323 | * Phone number: Reset clears verification values
324 | 
325 | #### Two-Factor Auth
326 | 
327 | * Default OTP period fix
328 | * URI generation doesn’t require enabling 2FA
329 | * Fixed OTP URI separator mismatch
330 | 
331 | #### Miscellaneous
332 | 
333 | * Delete organization if member not found
334 | * Correct error codes for API key rate limits
335 | * Additional fields now show in OpenAPI
336 | * Fixed FK constraint generation for MySQL
337 | * Various improvements to account linking
338 | * OIDC `offline_access` no longer requires `prompt=consent`
339 | * Fixed malformed base64 encoding for token validation
340 | 
341 | ---
342 | 
343 | A lot of refinements to make everything smoother, faster, and more reliable.
344 | 👉 [Check the full changelog](https://github.com/better-auth/better-auth/releases/tag/v1.3.0)
345 | 
346 | ---
347 | 
```

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

```typescript
 1 | import { cn } from "@/lib/utils";
 2 | interface TabProps {
 3 | 	fileName: string;
 4 | 	isActive: boolean;
 5 | 	brightnessLevel?: number; // New optional prop for brightness level
 6 | 	onClick: () => void;
 7 | 	onClose: () => void;
 8 | }
 9 | 
10 | const brightnessLevels = [
11 | 	"bg-background",
12 | 	"bg-background-200", //
13 | 	"bg-background-300",
14 | 	"bg-background-400",
15 | 	"bg-background-500",
16 | 	"bg-background-600",
17 | 	"bg-background-700",
18 | ];
19 | 
20 | export function CodeTab({
21 | 	fileName,
22 | 	isActive,
23 | 	brightnessLevel = 0,
24 | 	onClick,
25 | 	onClose,
26 | }: TabProps) {
27 | 	const activeBrightnessClass = isActive
28 | 		? brightnessLevels[brightnessLevel % brightnessLevels.length]
29 | 		: "bg-muted";
30 | 
31 | 	const textColor = isActive ? "text-foreground" : "text-muted-foreground";
32 | 	const borderColor = isActive
33 | 		? "border-t-foreground"
34 | 		: "border-t-transparent hover:bg-background/50";
35 | 
36 | 	return (
37 | 		<div
38 | 			className={cn(
39 | 				"flex items-center px-3 py-2 gap-2 text-sm font-medium border-t-2 cursor-pointer transition-colors duration-200",
40 | 				activeBrightnessClass,
41 | 				textColor,
42 | 				borderColor,
43 | 			)}
44 | 			onClick={onClick}
45 | 		>
46 | 			{fileName.endsWith(".ts") && (
47 | 				<svg
48 | 					xmlns="http://www.w3.org/2000/svg"
49 | 					width="1em"
50 | 					height="1em"
51 | 					viewBox="0 0 128 128"
52 | 				>
53 | 					<path
54 | 						className="fill-current"
55 | 						d="M2 63.91v62.5h125v-125H2zm100.73-5a15.56 15.56 0 0 1 7.82 4.5a20.6 20.6 0 0 1 3 4c0 .16-5.4 3.81-8.69 5.85c-.12.08-.6-.44-1.13-1.23a7.09 7.09 0 0 0-5.87-3.53c-3.79-.26-6.23 1.73-6.21 5a4.6 4.6 0 0 0 .54 2.34c.83 1.73 2.38 2.76 7.24 4.86c8.95 3.85 12.78 6.39 15.16 10c2.66 4 3.25 10.46 1.45 15.24c-2 5.2-6.9 8.73-13.83 9.9a38.3 38.3 0 0 1-9.52-.1A23 23 0 0 1 80 109.19c-1.15-1.27-3.39-4.58-3.25-4.82a9 9 0 0 1 1.15-.73l4.6-2.64l3.59-2.08l.75 1.11a16.8 16.8 0 0 0 4.74 4.54c4 2.1 9.46 1.81 12.16-.62a5.43 5.43 0 0 0 .69-6.92c-1-1.39-3-2.56-8.59-5c-6.45-2.78-9.23-4.5-11.77-7.24a16.5 16.5 0 0 1-3.43-6.25a25 25 0 0 1-.22-8c1.33-6.23 6-10.58 12.82-11.87a31.7 31.7 0 0 1 9.49.26zm-29.34 5.24v5.12H57.16v46.23H45.65V69.26H29.38v-5a49 49 0 0 1 .14-5.16c.06-.08 10-.12 22-.1h21.81z"
56 | 					></path>
57 | 				</svg>
58 | 			)}
59 | 			{fileName.endsWith(".tsx") && (
60 | 				<svg
61 | 					fill="currentColor"
62 | 					xmlns="http://www.w3.org/2000/svg"
63 | 					width="1.3em"
64 | 					height="1.3em"
65 | 					viewBox="0 0 30 30"
66 | 				>
67 | 					<path
68 | 						className="fill-current"
69 | 						d="M 10.679688 4.1816406 C 10.068687 4.1816406 9.502 4.3184219 9 4.6074219 C 7.4311297 5.5132122 6.8339651 7.7205462 7.1503906 10.46875 C 4.6127006 11.568833 3 13.188667 3 15 C 3 16.811333 4.6127006 18.431167 7.1503906 19.53125 C 6.8341285 22.279346 7.4311297 24.486788 9 25.392578 C 9.501 25.681578 10.067687 25.818359 10.679688 25.818359 C 11.982314 25.818359 13.48785 25.164589 15 24.042969 C 16.512282 25.164589 18.01964 25.818359 19.322266 25.818359 C 19.933266 25.818359 20.499953 25.681578 21.001953 25.392578 C 22.570814 24.486793 23.167976 22.279432 22.851562 19.53125 C 25.388297 18.431178 27 16.81094 27 15 C 27 13.188667 25.387299 11.568833 22.849609 10.46875 C 23.165872 7.7206538 22.56887 5.5132122 21 4.6074219 C 20.499 4.3174219 19.932312 4.1816406 19.320312 4.1816406 C 18.017686 4.1816406 16.51215 4.8354109 15 5.9570312 C 13.487763 4.8354109 11.981863 4.1816406 10.679688 4.1816406 z M 10.679688 5.9316406 C 11.461321 5.9316406 12.49496 6.3472486 13.617188 7.1171875 C 12.95737 7.7398717 12.311153 8.4479321 11.689453 9.2363281 C 10.681079 9.3809166 9.7303472 9.5916908 8.8496094 9.8554688 C 8.8448793 9.7943902 8.8336776 9.7303008 8.8300781 9.6699219 C 8.7230781 7.8899219 9.114 6.5630469 9.875 6.1230469 C 10.1 5.9930469 10.362688 5.9316406 10.679688 5.9316406 z M 19.320312 5.9316406 C 19.636312 5.9316406 19.9 5.9930469 20.125 6.1230469 C 20.886 6.5620469 21.276922 7.8899219 21.169922 9.6699219 C 21.166295 9.7303008 21.155145 9.7943902 21.150391 9.8554688 C 20.2691 9.5915252 19.317669 9.3809265 18.308594 9.2363281 C 17.686902 8.4480417 17.042616 7.7397993 16.382812 7.1171875 C 17.504962 6.3473772 18.539083 5.9316406 19.320312 5.9316406 z M 15 8.2285156 C 15.27108 8.4752506 15.540266 8.7360345 15.8125 9.0214844 C 15.542718 9.012422 15.274373 9 15 9 C 14.726286 9 14.458598 9.0124652 14.189453 9.0214844 C 14.461446 8.7363308 14.729174 8.4750167 15 8.2285156 z M 15 10.75 C 15.828688 10.75 16.614128 10.796321 17.359375 10.876953 C 17.813861 11.494697 18.261774 12.147811 18.681641 12.875 C 19.084074 13.572033 19.439938 14.285488 19.753906 15 C 19.439896 15.714942 19.084316 16.429502 18.681641 17.126953 C 18.263078 17.852044 17.816279 18.500949 17.363281 19.117188 C 16.591711 19.201607 15.800219 19.25 15 19.25 C 14.171312 19.25 13.385872 19.203679 12.640625 19.123047 C 12.186139 18.505303 11.738226 17.854142 11.318359 17.126953 C 10.915684 16.429502 10.560194 15.714942 10.246094 15 C 10.559972 14.285488 10.915926 13.572033 11.318359 12.875 C 11.737083 12.149909 12.183612 11.499051 12.636719 10.882812 C 13.408289 10.798393 14.199781 10.75 15 10.75 z M 19.746094 11.291016 C 20.142841 11.386804 20.524253 11.490209 20.882812 11.605469 C 20.801579 11.97252 20.702235 12.346608 20.589844 12.724609 C 20.461164 12.483141 20.336375 12.240903 20.197266 12 C 20.054139 11.752196 19.895244 11.529558 19.746094 11.291016 z M 10.251953 11.292969 C 10.103305 11.530776 9.9454023 11.752991 9.8027344 12 C 9.6636666 12.240944 9.5387971 12.483106 9.4101562 12.724609 C 9.29751 12.345829 9.1965499 11.971295 9.1152344 11.603516 C 9.4803698 11.48815 9.86083 11.385986 10.251953 11.292969 z M 7.46875 12.246094 C 7.6794464 13.135714 7.9717297 14.057918 8.3476562 14.998047 C 7.9725263 15.935943 7.6814729 16.856453 7.4707031 17.744141 C 5.7292327 16.903203 4.75 15.856373 4.75 15 C 4.75 14.121 5.701875 13.119266 7.296875 12.322266 C 7.3513169 12.295031 7.4131225 12.272692 7.46875 12.246094 z M 22.529297 12.255859 C 24.270767 13.096797 25.25 14.143627 25.25 15 C 25.25 15.879 24.298125 16.880734 22.703125 17.677734 C 22.648683 17.704969 22.586877 17.727308 22.53125 17.753906 C 22.32043 16.863764 22.030541 15.940699 21.654297 15 C 22.028977 14.062913 22.318703 13.142804 22.529297 12.255859 z M 15 13 C 13.895 13 13 13.895 13 15 C 13 16.105 13.895 17 15 17 C 16.105 17 17 16.105 17 15 C 17 13.895 16.105 13 15 13 z M 9.4101562 17.275391 C 9.5388794 17.516948 9.6655262 17.759008 9.8046875 18 C 9.9476585 18.247625 10.104915 18.470608 10.253906 18.708984 C 9.857159 18.613196 9.4757466 18.509791 9.1171875 18.394531 C 9.1984813 18.02725 9.2976676 17.653633 9.4101562 17.275391 z M 20.589844 17.277344 C 20.702364 17.655759 20.803517 18.02905 20.884766 18.396484 C 20.51963 18.51185 20.13917 18.614014 19.748047 18.707031 C 19.896695 18.469224 20.054598 18.247009 20.197266 18 C 20.336044 17.759557 20.461449 17.518344 20.589844 17.277344 z M 8.8496094 20.144531 C 9.7309004 20.408475 10.682331 20.619073 11.691406 20.763672 C 12.313288 21.552345 12.957085 22.261935 13.617188 22.884766 C 12.495042 23.654481 11.461272 24.070312 10.679688 24.070312 C 10.363687 24.070312 10.1 24.006953 9.875 23.876953 C 9.114 23.437953 8.7230781 22.112031 8.8300781 20.332031 C 8.8337424 20.271023 8.8447938 20.206253 8.8496094 20.144531 z M 21.150391 20.144531 C 21.155182 20.206253 21.166285 20.271023 21.169922 20.332031 C 21.276922 22.112031 20.886 23.436953 20.125 23.876953 C 19.9 24.006953 19.637312 24.070313 19.320312 24.070312 C 18.538728 24.070312 17.504958 23.654609 16.382812 22.884766 C 17.042964 22.261863 17.688542 21.552454 18.310547 20.763672 C 19.318921 20.619083 20.269653 20.408309 21.150391 20.144531 z M 14.1875 20.978516 C 14.457282 20.987578 14.725627 21 15 21 C 15.274373 21 15.542718 20.987578 15.8125 20.978516 C 15.540266 21.263964 15.27108 21.524765 15 21.771484 C 14.72892 21.524749 14.459734 21.263966 14.1875 20.978516 z"
70 | 					></path>
71 | 				</svg>
72 | 			)}
73 | 			<span className="truncate max-w-[100px]">{fileName}</span>
74 | 		</div>
75 | 	);
76 | }
77 | 
```

--------------------------------------------------------------------------------
/docs/components/docs/layout/toc.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | import type { TOCItemType } from "fumadocs-core/server";
  3 | import * as Primitive from "fumadocs-core/toc";
  4 | import {
  5 | 	type ComponentProps,
  6 | 	createContext,
  7 | 	type HTMLAttributes,
  8 | 	type ReactNode,
  9 | 	use,
 10 | 	useEffect,
 11 | 	useMemo,
 12 | 	useRef,
 13 | 	useState,
 14 | } from "react";
 15 | import { cn } from "@/lib/utils";
 16 | import { useI18n } from "fumadocs-ui/provider";
 17 | import { TocThumb } from "./toc-thumb";
 18 | import { ScrollArea, ScrollViewport } from "../ui/scroll-area";
 19 | import type {
 20 | 	PopoverContentProps,
 21 | 	PopoverTriggerProps,
 22 | } from "@radix-ui/react-popover";
 23 | import { ChevronRight, Text } from "lucide-react";
 24 | import { usePageStyles } from "fumadocs-ui/provider";
 25 | import {
 26 | 	Collapsible,
 27 | 	CollapsibleContent,
 28 | 	CollapsibleTrigger,
 29 | } from "../ui/collapsible";
 30 | 
 31 | export interface TOCProps {
 32 | 	/**
 33 | 	 * Custom content in TOC container, before the main TOC
 34 | 	 */
 35 | 	header?: ReactNode;
 36 | 
 37 | 	/**
 38 | 	 * Custom content in TOC container, after the main TOC
 39 | 	 */
 40 | 	footer?: ReactNode;
 41 | 
 42 | 	children: ReactNode;
 43 | }
 44 | 
 45 | export function Toc(props: HTMLAttributes<HTMLDivElement>) {
 46 | 	const { toc } = usePageStyles();
 47 | 
 48 | 	return (
 49 | 		<div
 50 | 			id="nd-toc"
 51 | 			{...props}
 52 | 			className={cn(
 53 | 				"sticky top-[calc(var(--fd-banner-height)+var(--fd-nav-height))] h-(--fd-toc-height) pb-2 pt-12",
 54 | 				toc,
 55 | 				props.className,
 56 | 			)}
 57 | 			style={
 58 | 				{
 59 | 					...props.style,
 60 | 					"--fd-toc-height":
 61 | 						"calc(100dvh - var(--fd-banner-height) - var(--fd-nav-height))",
 62 | 				} as object
 63 | 			}
 64 | 		>
 65 | 			<div className="flex h-full w-(--fd-toc-width) max-w-full flex-col gap-3 pe-4">
 66 | 				{props.children}
 67 | 			</div>
 68 | 		</div>
 69 | 	);
 70 | }
 71 | 
 72 | export function TocItemsEmpty() {
 73 | 	const { text } = useI18n();
 74 | 
 75 | 	return (
 76 | 		<div className="rounded-lg border bg-fd-card p-3 text-xs text-fd-muted-foreground">
 77 | 			{text.tocNoHeadings}
 78 | 		</div>
 79 | 	);
 80 | }
 81 | 
 82 | export function TOCScrollArea({
 83 | 	isMenu,
 84 | 	...props
 85 | }: ComponentProps<typeof ScrollArea> & { isMenu?: boolean }) {
 86 | 	const viewRef = useRef<HTMLDivElement>(null);
 87 | 
 88 | 	return (
 89 | 		<ScrollArea
 90 | 			{...props}
 91 | 			className={cn("flex flex-col ps-px", props.className)}
 92 | 		>
 93 | 			<Primitive.ScrollProvider containerRef={viewRef}>
 94 | 				<ScrollViewport
 95 | 					className={cn(
 96 | 						"relative min-h-0 text-sm",
 97 | 						isMenu && "mt-2 mb-4 mx-4 md:mx-6",
 98 | 					)}
 99 | 					ref={viewRef}
100 | 				>
101 | 					{props.children}
102 | 				</ScrollViewport>
103 | 			</Primitive.ScrollProvider>
104 | 		</ScrollArea>
105 | 	);
106 | }
107 | 
108 | export function TOCItems({ items }: { items: TOCItemType[] }) {
109 | 	const containerRef = useRef<HTMLDivElement>(null);
110 | 
111 | 	const [svg, setSvg] = useState<{
112 | 		path: string;
113 | 		width: number;
114 | 		height: number;
115 | 	}>();
116 | 
117 | 	useEffect(() => {
118 | 		if (!containerRef.current) return;
119 | 		const container = containerRef.current;
120 | 
121 | 		function onResize(): void {
122 | 			if (container.clientHeight === 0) return;
123 | 			let w = 0,
124 | 				h = 0;
125 | 			const d: string[] = [];
126 | 			for (let i = 0; i < items.length; i++) {
127 | 				const element: HTMLElement | null = container.querySelector(
128 | 					`a[href="#${items[i].url.slice(1)}"]`,
129 | 				);
130 | 				if (!element) continue;
131 | 
132 | 				const styles = getComputedStyle(element);
133 | 				const offset = getLineOffset(items[i].depth) + 1,
134 | 					top = element.offsetTop + parseFloat(styles.paddingTop),
135 | 					bottom =
136 | 						element.offsetTop +
137 | 						element.clientHeight -
138 | 						parseFloat(styles.paddingBottom);
139 | 
140 | 				w = Math.max(offset, w);
141 | 				h = Math.max(h, bottom);
142 | 
143 | 				d.push(`${i === 0 ? "M" : "L"}${offset} ${top}`);
144 | 				d.push(`L${offset} ${bottom}`);
145 | 			}
146 | 
147 | 			setSvg({
148 | 				path: d.join(" "),
149 | 				width: w + 1,
150 | 				height: h,
151 | 			});
152 | 		}
153 | 
154 | 		const observer = new ResizeObserver(onResize);
155 | 		onResize();
156 | 
157 | 		observer.observe(container);
158 | 		return () => {
159 | 			observer.disconnect();
160 | 		};
161 | 	}, [items]);
162 | 
163 | 	if (items.length === 0) return <TocItemsEmpty />;
164 | 
165 | 	return (
166 | 		<>
167 | 			{svg ? (
168 | 				<div
169 | 					className="absolute start-0 top-0 rtl:-scale-x-100"
170 | 					style={{
171 | 						width: svg.width,
172 | 						height: svg.height,
173 | 						maskImage: `url("data:image/svg+xml,${
174 | 							// Inline SVG
175 | 							encodeURIComponent(
176 | 								`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${svg.width} ${svg.height}"><path d="${svg.path}" stroke="black" stroke-width="1" fill="none" /></svg>`,
177 | 							)
178 | 						}")`,
179 | 					}}
180 | 				>
181 | 					<TocThumb
182 | 						containerRef={containerRef}
183 | 						className="mt-(--fd-top) h-(--fd-height) bg-fd-primary transition-all"
184 | 					/>
185 | 				</div>
186 | 			) : null}
187 | 			<div className="flex flex-col" ref={containerRef}>
188 | 				{items.map((item, i) => (
189 | 					<TOCItem
190 | 						key={item.url}
191 | 						item={item}
192 | 						upper={items[i - 1]?.depth}
193 | 						lower={items[i + 1]?.depth}
194 | 					/>
195 | 				))}
196 | 			</div>
197 | 		</>
198 | 	);
199 | }
200 | 
201 | function getItemOffset(depth: number): number {
202 | 	if (depth <= 2) return 14;
203 | 	if (depth === 3) return 26;
204 | 	return 36;
205 | }
206 | 
207 | function getLineOffset(depth: number): number {
208 | 	return depth >= 3 ? 10 : 0;
209 | }
210 | 
211 | function TOCItem({
212 | 	item,
213 | 	upper = item.depth,
214 | 	lower = item.depth,
215 | }: {
216 | 	item: TOCItemType;
217 | 	upper?: number;
218 | 	lower?: number;
219 | }) {
220 | 	const offset = getLineOffset(item.depth),
221 | 		upperOffset = getLineOffset(upper),
222 | 		lowerOffset = getLineOffset(lower);
223 | 
224 | 	return (
225 | 		<Primitive.TOCItem
226 | 			href={item.url}
227 | 			style={{
228 | 				paddingInlineStart: getItemOffset(item.depth),
229 | 			}}
230 | 			className="prose relative py-1.5 text-sm text-fd-muted-foreground transition-colors [overflow-wrap:anywhere] first:pt-0 last:pb-0 data-[active=true]:text-fd-primary"
231 | 		>
232 | 			{offset !== upperOffset ? (
233 | 				<svg
234 | 					xmlns="http://www.w3.org/2000/svg"
235 | 					viewBox="0 0 16 16"
236 | 					className="absolute -top-1.5 start-0 size-4 rtl:-scale-x-100"
237 | 				>
238 | 					<line
239 | 						x1={upperOffset}
240 | 						y1="0"
241 | 						x2={offset}
242 | 						y2="12"
243 | 						className="stroke-fd-foreground/10"
244 | 						strokeWidth="1"
245 | 					/>
246 | 				</svg>
247 | 			) : null}
248 | 			<div
249 | 				className={cn(
250 | 					"absolute inset-y-0 w-px bg-fd-foreground/10",
251 | 					offset !== upperOffset && "top-1.5",
252 | 					offset !== lowerOffset && "bottom-1.5",
253 | 				)}
254 | 				style={{
255 | 					insetInlineStart: offset,
256 | 				}}
257 | 			/>
258 | 			{item.title}
259 | 		</Primitive.TOCItem>
260 | 	);
261 | }
262 | 
263 | type MakeRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
264 | 
265 | const Context = createContext<{
266 | 	open: boolean;
267 | 	setOpen: (open: boolean) => void;
268 | } | null>(null);
269 | 
270 | const TocProvider = Context.Provider || Context;
271 | 
272 | export function TocPopover({
273 | 	open,
274 | 	onOpenChange,
275 | 	ref: _ref,
276 | 	...props
277 | }: MakeRequired<ComponentProps<typeof Collapsible>, "open" | "onOpenChange">) {
278 | 	return (
279 | 		<Collapsible open={open} onOpenChange={onOpenChange} {...props}>
280 | 			<TocProvider
281 | 				value={useMemo(
282 | 					() => ({
283 | 						open,
284 | 						setOpen: onOpenChange,
285 | 					}),
286 | 					[onOpenChange, open],
287 | 				)}
288 | 			>
289 | 				{props.children}
290 | 			</TocProvider>
291 | 		</Collapsible>
292 | 	);
293 | }
294 | 
295 | export function TocPopoverTrigger({
296 | 	items,
297 | 	...props
298 | }: PopoverTriggerProps & { items: TOCItemType[] }) {
299 | 	const { text } = useI18n();
300 | 	const { open } = use(Context)!;
301 | 	const active = Primitive.useActiveAnchor();
302 | 	const current = useMemo(() => {
303 | 		return items.find((item) => active === item.url.slice(1))?.title;
304 | 	}, [items, active]);
305 | 
306 | 	return (
307 | 		<CollapsibleTrigger
308 | 			{...props}
309 | 			className={cn(
310 | 				"inline-flex items-center text-sm gap-2 text-nowrap px-4 py-2.5 text-start md:px-6 focus-visible:outline-none",
311 | 				props.className,
312 | 			)}
313 | 		>
314 | 			<Text className="size-4 shrink-0" />
315 | 			{text.toc}
316 | 			<ChevronRight
317 | 				className={cn(
318 | 					"size-4 shrink-0 text-fd-muted-foreground transition-all",
319 | 					!current && "opacity-0",
320 | 					open ? "rotate-90" : "-ms-1.5",
321 | 				)}
322 | 			/>
323 | 			<span
324 | 				className={cn(
325 | 					"truncate text-fd-muted-foreground transition-opacity -ms-1.5",
326 | 					(!current || open) && "opacity-0",
327 | 				)}
328 | 			>
329 | 				{current}
330 | 			</span>
331 | 		</CollapsibleTrigger>
332 | 	);
333 | }
334 | 
335 | export function TocPopoverContent(props: PopoverContentProps) {
336 | 	return (
337 | 		<CollapsibleContent
338 | 			data-toc-popover=""
339 | 			className="flex flex-col max-h-[50vh]"
340 | 			{...props}
341 | 		>
342 | 			{props.children}
343 | 		</CollapsibleContent>
344 | 	);
345 | }
346 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/test-utils/test-instance.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { AsyncLocalStorage } from "node:async_hooks";
  2 | import { afterAll } from "vitest";
  3 | import { betterAuth } from "../auth";
  4 | import { createAuthClient } from "../client/vanilla";
  5 | import type { Session, User } from "../types";
  6 | import type { BetterAuthClientOptions } from "@better-auth/core";
  7 | import { getMigrations } from "../db/get-migration";
  8 | import { parseSetCookieHeader, setCookieToHeader } from "../cookies";
  9 | import type { SuccessContext } from "@better-fetch/fetch";
 10 | import { getAdapter } from "../db/utils";
 11 | import Database from "better-sqlite3";
 12 | import { getBaseURL } from "../utils/url";
 13 | import { Kysely, MysqlDialect, PostgresDialect, sql } from "kysely";
 14 | import { Pool } from "pg";
 15 | import { MongoClient } from "mongodb";
 16 | import { mongodbAdapter } from "../adapters/mongodb-adapter";
 17 | import { createPool } from "mysql2/promise";
 18 | import { bearer } from "../plugins";
 19 | import type { BetterAuthOptions } from "@better-auth/core";
 20 | 
 21 | const cleanupSet = new Set<Function>();
 22 | 
 23 | type CurrentUserContext = {
 24 | 	headers: Headers;
 25 | };
 26 | const currentUserContextStorage = new AsyncLocalStorage<CurrentUserContext>();
 27 | 
 28 | afterAll(async () => {
 29 | 	for (const cleanup of cleanupSet) {
 30 | 		await cleanup();
 31 | 		cleanupSet.delete(cleanup);
 32 | 	}
 33 | });
 34 | 
 35 | export async function getTestInstance<
 36 | 	O extends Partial<BetterAuthOptions>,
 37 | 	C extends BetterAuthClientOptions,
 38 | >(
 39 | 	options?: O,
 40 | 	config?: {
 41 | 		clientOptions?: C;
 42 | 		port?: number;
 43 | 		disableTestUser?: boolean;
 44 | 		testUser?: Partial<User>;
 45 | 		testWith?: "sqlite" | "postgres" | "mongodb" | "mysql";
 46 | 	},
 47 | ) {
 48 | 	const testWith = config?.testWith || "sqlite";
 49 | 
 50 | 	const postgres = new Kysely({
 51 | 		dialect: new PostgresDialect({
 52 | 			pool: new Pool({
 53 | 				connectionString: "postgres://user:password@localhost:5432/better_auth",
 54 | 			}),
 55 | 		}),
 56 | 	});
 57 | 
 58 | 	const sqlite = new Database(":memory:");
 59 | 
 60 | 	const mysql = new Kysely({
 61 | 		dialect: new MysqlDialect(
 62 | 			createPool("mysql://user:password@localhost:3306/better_auth"),
 63 | 		),
 64 | 	});
 65 | 
 66 | 	async function mongodbClient() {
 67 | 		const dbClient = async (connectionString: string, dbName: string) => {
 68 | 			const client = new MongoClient(connectionString);
 69 | 			await client.connect();
 70 | 			const db = client.db(dbName);
 71 | 			return db;
 72 | 		};
 73 | 		const db = await dbClient("mongodb://127.0.0.1:27017", "better-auth");
 74 | 		return db;
 75 | 	}
 76 | 
 77 | 	const opts = {
 78 | 		socialProviders: {
 79 | 			github: {
 80 | 				clientId: "test",
 81 | 				clientSecret: "test",
 82 | 			},
 83 | 			google: {
 84 | 				clientId: "test",
 85 | 				clientSecret: "test",
 86 | 			},
 87 | 		},
 88 | 		secret: "better-auth.secret",
 89 | 		database:
 90 | 			testWith === "postgres"
 91 | 				? { db: postgres, type: "postgres" }
 92 | 				: testWith === "mongodb"
 93 | 					? mongodbAdapter(await mongodbClient())
 94 | 					: testWith === "mysql"
 95 | 						? { db: mysql, type: "mysql" }
 96 | 						: sqlite,
 97 | 		emailAndPassword: {
 98 | 			enabled: true,
 99 | 		},
100 | 		rateLimit: {
101 | 			enabled: false,
102 | 		},
103 | 		advanced: {
104 | 			cookies: {},
105 | 		},
106 | 		logger: {
107 | 			level: "debug",
108 | 		},
109 | 	} satisfies BetterAuthOptions;
110 | 
111 | 	const auth = betterAuth({
112 | 		baseURL: "http://localhost:" + (config?.port || 3000),
113 | 		...opts,
114 | 		...options,
115 | 		plugins: [bearer(), ...(options?.plugins || [])],
116 | 	} as unknown as O);
117 | 
118 | 	const testUser = {
119 | 		email: "[email protected]",
120 | 		password: "test123456",
121 | 		name: "test user",
122 | 		...config?.testUser,
123 | 	};
124 | 	async function createTestUser() {
125 | 		if (config?.disableTestUser) {
126 | 			return;
127 | 		}
128 | 		//@ts-expect-error
129 | 		await auth.api.signUpEmail({
130 | 			body: testUser,
131 | 		});
132 | 	}
133 | 
134 | 	if (testWith !== "mongodb") {
135 | 		const { runMigrations } = await getMigrations({
136 | 			...auth.options,
137 | 			database: opts.database,
138 | 		});
139 | 		await runMigrations();
140 | 	}
141 | 
142 | 	await createTestUser();
143 | 
144 | 	const cleanup = async () => {
145 | 		if (testWith === "mongodb") {
146 | 			const db = await mongodbClient();
147 | 			await db.dropDatabase();
148 | 			return;
149 | 		}
150 | 		if (testWith === "postgres") {
151 | 			await sql`DROP SCHEMA public CASCADE; CREATE SCHEMA public;`.execute(
152 | 				postgres,
153 | 			);
154 | 			await postgres.destroy();
155 | 			return;
156 | 		}
157 | 
158 | 		if (testWith === "mysql") {
159 | 			await sql`SET FOREIGN_KEY_CHECKS = 0;`.execute(mysql);
160 | 			const tables = await mysql.introspection.getTables();
161 | 			for (const table of tables) {
162 | 				// @ts-expect-error
163 | 				await mysql.deleteFrom(table.name).execute();
164 | 			}
165 | 			await sql`SET FOREIGN_KEY_CHECKS = 1;`.execute(mysql);
166 | 			return;
167 | 		}
168 | 		if (testWith === "sqlite") {
169 | 			sqlite.close();
170 | 			return;
171 | 		}
172 | 	};
173 | 	cleanupSet.add(cleanup);
174 | 
175 | 	const customFetchImpl = async (
176 | 		url: string | URL | Request,
177 | 		init?: RequestInit,
178 | 	) => {
179 | 		const headers = init?.headers || {};
180 | 		const storageHeaders = currentUserContextStorage.getStore()?.headers;
181 | 		return auth.handler(
182 | 			new Request(
183 | 				url,
184 | 				init
185 | 					? {
186 | 							...init,
187 | 							headers: new Headers({
188 | 								...(storageHeaders
189 | 									? Object.fromEntries(storageHeaders.entries())
190 | 									: {}),
191 | 								...(headers instanceof Headers
192 | 									? Object.fromEntries(headers.entries())
193 | 									: typeof headers === "object"
194 | 										? headers
195 | 										: {}),
196 | 							}),
197 | 						}
198 | 					: {
199 | 							headers,
200 | 						},
201 | 			),
202 | 		);
203 | 	};
204 | 
205 | 	const client = createAuthClient({
206 | 		...(config?.clientOptions as C extends undefined ? {} : C),
207 | 		baseURL: getBaseURL(
208 | 			options?.baseURL || "http://localhost:" + (config?.port || 3000),
209 | 			options?.basePath || "/api/auth",
210 | 		),
211 | 		fetchOptions: {
212 | 			customFetchImpl,
213 | 		},
214 | 	});
215 | 
216 | 	async function signInWithTestUser() {
217 | 		if (config?.disableTestUser) {
218 | 			throw new Error("Test user is disabled");
219 | 		}
220 | 		let headers = new Headers();
221 | 		const setCookie = (name: string, value: string) => {
222 | 			const current = headers.get("cookie");
223 | 			headers.set("cookie", `${current || ""}; ${name}=${value}`);
224 | 		};
225 | 		//@ts-expect-error
226 | 		const { data, error } = await client.signIn.email({
227 | 			email: testUser.email,
228 | 			password: testUser.password,
229 | 			fetchOptions: {
230 | 				//@ts-expect-error
231 | 				onSuccess(context) {
232 | 					const header = context.response.headers.get("set-cookie");
233 | 					const cookies = parseSetCookieHeader(header || "");
234 | 					const signedCookie = cookies.get("better-auth.session_token")?.value;
235 | 					headers.set("cookie", `better-auth.session_token=${signedCookie}`);
236 | 				},
237 | 			},
238 | 		});
239 | 		return {
240 | 			session: data.session as Session,
241 | 			user: data.user as User,
242 | 			headers,
243 | 			setCookie,
244 | 			runWithUser: async (fn: (headers: Headers) => Promise<void>) => {
245 | 				return currentUserContextStorage.run({ headers }, async () => {
246 | 					await fn(headers);
247 | 				});
248 | 			},
249 | 		};
250 | 	}
251 | 	async function signInWithUser(email: string, password: string) {
252 | 		const headers = new Headers();
253 | 		//@ts-expect-error
254 | 		const { data } = await client.signIn.email({
255 | 			email,
256 | 			password,
257 | 			fetchOptions: {
258 | 				//@ts-expect-error
259 | 				onSuccess(context) {
260 | 					const header = context.response.headers.get("set-cookie");
261 | 					const cookies = parseSetCookieHeader(header || "");
262 | 					const signedCookie = cookies.get("better-auth.session_token")?.value;
263 | 					headers.set("cookie", `better-auth.session_token=${signedCookie}`);
264 | 				},
265 | 			},
266 | 		});
267 | 		return {
268 | 			res: data as {
269 | 				user: User;
270 | 				session: Session;
271 | 			},
272 | 			headers,
273 | 		};
274 | 	}
275 | 
276 | 	function sessionSetter(headers: Headers) {
277 | 		return (context: SuccessContext) => {
278 | 			const header = context.response.headers.get("set-cookie");
279 | 			if (header) {
280 | 				const cookies = parseSetCookieHeader(header || "");
281 | 				const signedCookie = cookies.get("better-auth.session_token")?.value;
282 | 				headers.set("cookie", `better-auth.session_token=${signedCookie}`);
283 | 			}
284 | 		};
285 | 	}
286 | 
287 | 	return {
288 | 		auth,
289 | 		client,
290 | 		testUser,
291 | 		signInWithTestUser,
292 | 		signInWithUser,
293 | 		cookieSetter: setCookieToHeader,
294 | 		customFetchImpl,
295 | 		sessionSetter,
296 | 		db: await getAdapter(auth.options),
297 | 		runWithUser: async (
298 | 			email: string,
299 | 			password: string,
300 | 			fn: (headers: Headers) => Promise<void> | void,
301 | 		) => {
302 | 			const { headers } = await signInWithUser(email, password);
303 | 			return currentUserContextStorage.run({ headers }, async () => {
304 | 				await fn(headers);
305 | 			});
306 | 		},
307 | 	};
308 | }
309 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
  5 | import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
  6 | 
  7 | import { cn } from "@/lib/utils";
  8 | 
  9 | function DropdownMenu({
 10 | 	...props
 11 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
 12 | 	return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
 13 | }
 14 | 
 15 | function DropdownMenuPortal({
 16 | 	...props
 17 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
 18 | 	return (
 19 | 		<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
 20 | 	);
 21 | }
 22 | 
 23 | function DropdownMenuTrigger({
 24 | 	...props
 25 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
 26 | 	return (
 27 | 		<DropdownMenuPrimitive.Trigger
 28 | 			data-slot="dropdown-menu-trigger"
 29 | 			{...props}
 30 | 		/>
 31 | 	);
 32 | }
 33 | 
 34 | function DropdownMenuContent({
 35 | 	className,
 36 | 	sideOffset = 4,
 37 | 	...props
 38 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
 39 | 	return (
 40 | 		<DropdownMenuPrimitive.Portal>
 41 | 			<DropdownMenuPrimitive.Content
 42 | 				data-slot="dropdown-menu-content"
 43 | 				sideOffset={sideOffset}
 44 | 				className={cn(
 45 | 					"bg-popover text-popover-foreground 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 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-32 overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
 46 | 					className,
 47 | 				)}
 48 | 				{...props}
 49 | 			/>
 50 | 		</DropdownMenuPrimitive.Portal>
 51 | 	);
 52 | }
 53 | 
 54 | function DropdownMenuGroup({
 55 | 	...props
 56 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
 57 | 	return (
 58 | 		<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
 59 | 	);
 60 | }
 61 | 
 62 | function DropdownMenuItem({
 63 | 	className,
 64 | 	inset,
 65 | 	variant = "default",
 66 | 	...props
 67 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
 68 | 	inset?: boolean;
 69 | 	variant?: "default" | "destructive";
 70 | }) {
 71 | 	return (
 72 | 		<DropdownMenuPrimitive.Item
 73 | 			data-slot="dropdown-menu-item"
 74 | 			data-inset={inset}
 75 | 			data-variant={variant}
 76 | 			className={cn(
 77 | 				"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/40 data-[variant=destructive]:focus:text-destructive-foreground data-[variant=destructive]:*:[svg]:text-destructive-foreground! [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
 78 | 				className,
 79 | 			)}
 80 | 			{...props}
 81 | 		/>
 82 | 	);
 83 | }
 84 | 
 85 | function DropdownMenuCheckboxItem({
 86 | 	className,
 87 | 	children,
 88 | 	checked,
 89 | 	...props
 90 | }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
 91 | 	return (
 92 | 		<DropdownMenuPrimitive.CheckboxItem
 93 | 			data-slot="dropdown-menu-checkbox-item"
 94 | 			className={cn(
 95 | 				"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
 96 | 				className,
 97 | 			)}
 98 | 			checked={checked}
 99 | 			{...props}
100 | 		>
101 | 			<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
102 | 				<DropdownMenuPrimitive.ItemIndicator>
103 | 					<CheckIcon className="size-4" />
104 | 				</DropdownMenuPrimitive.ItemIndicator>
105 | 			</span>
106 | 			{children}
107 | 		</DropdownMenuPrimitive.CheckboxItem>
108 | 	);
109 | }
110 | 
111 | function DropdownMenuRadioGroup({
112 | 	...props
113 | }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
114 | 	return (
115 | 		<DropdownMenuPrimitive.RadioGroup
116 | 			data-slot="dropdown-menu-radio-group"
117 | 			{...props}
118 | 		/>
119 | 	);
120 | }
121 | 
122 | function DropdownMenuRadioItem({
123 | 	className,
124 | 	children,
125 | 	...props
126 | }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
127 | 	return (
128 | 		<DropdownMenuPrimitive.RadioItem
129 | 			data-slot="dropdown-menu-radio-item"
130 | 			className={cn(
131 | 				"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
132 | 				className,
133 | 			)}
134 | 			{...props}
135 | 		>
136 | 			<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
137 | 				<DropdownMenuPrimitive.ItemIndicator>
138 | 					<CircleIcon className="size-2 fill-current" />
139 | 				</DropdownMenuPrimitive.ItemIndicator>
140 | 			</span>
141 | 			{children}
142 | 		</DropdownMenuPrimitive.RadioItem>
143 | 	);
144 | }
145 | 
146 | function DropdownMenuLabel({
147 | 	className,
148 | 	inset,
149 | 	...props
150 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
151 | 	inset?: boolean;
152 | }) {
153 | 	return (
154 | 		<DropdownMenuPrimitive.Label
155 | 			data-slot="dropdown-menu-label"
156 | 			data-inset={inset}
157 | 			className={cn(
158 | 				"px-2 py-1.5 text-sm font-medium data-inset:pl-8",
159 | 				className,
160 | 			)}
161 | 			{...props}
162 | 		/>
163 | 	);
164 | }
165 | 
166 | function DropdownMenuSeparator({
167 | 	className,
168 | 	...props
169 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
170 | 	return (
171 | 		<DropdownMenuPrimitive.Separator
172 | 			data-slot="dropdown-menu-separator"
173 | 			className={cn("bg-border -mx-1 my-1 h-px", className)}
174 | 			{...props}
175 | 		/>
176 | 	);
177 | }
178 | 
179 | function DropdownMenuShortcut({
180 | 	className,
181 | 	...props
182 | }: React.ComponentProps<"span">) {
183 | 	return (
184 | 		<span
185 | 			data-slot="dropdown-menu-shortcut"
186 | 			className={cn(
187 | 				"text-muted-foreground ml-auto text-xs tracking-widest",
188 | 				className,
189 | 			)}
190 | 			{...props}
191 | 		/>
192 | 	);
193 | }
194 | 
195 | function DropdownMenuSub({
196 | 	...props
197 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
198 | 	return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
199 | }
200 | 
201 | function DropdownMenuSubTrigger({
202 | 	className,
203 | 	inset,
204 | 	children,
205 | 	...props
206 | }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
207 | 	inset?: boolean;
208 | }) {
209 | 	return (
210 | 		<DropdownMenuPrimitive.SubTrigger
211 | 			data-slot="dropdown-menu-sub-trigger"
212 | 			data-inset={inset}
213 | 			className={cn(
214 | 				"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-inset:pl-8",
215 | 				className,
216 | 			)}
217 | 			{...props}
218 | 		>
219 | 			{children}
220 | 			<ChevronRightIcon className="ml-auto size-4" />
221 | 		</DropdownMenuPrimitive.SubTrigger>
222 | 	);
223 | }
224 | 
225 | function DropdownMenuSubContent({
226 | 	className,
227 | 	...props
228 | }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
229 | 	return (
230 | 		<DropdownMenuPrimitive.SubContent
231 | 			data-slot="dropdown-menu-sub-content"
232 | 			className={cn(
233 | 				"bg-popover text-popover-foreground 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 z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-lg",
234 | 				className,
235 | 			)}
236 | 			{...props}
237 | 		/>
238 | 	);
239 | }
240 | 
241 | export {
242 | 	DropdownMenu,
243 | 	DropdownMenuPortal,
244 | 	DropdownMenuTrigger,
245 | 	DropdownMenuContent,
246 | 	DropdownMenuGroup,
247 | 	DropdownMenuLabel,
248 | 	DropdownMenuItem,
249 | 	DropdownMenuCheckboxItem,
250 | 	DropdownMenuRadioGroup,
251 | 	DropdownMenuRadioItem,
252 | 	DropdownMenuSeparator,
253 | 	DropdownMenuShortcut,
254 | 	DropdownMenuSub,
255 | 	DropdownMenuSubTrigger,
256 | 	DropdownMenuSubContent,
257 | };
258 | 
```
Page 24/69FirstPrevNextLast