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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/demo/nextjs/components/one-tap.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import { client, signIn } from "@/lib/auth-client";
  4 | import { useEffect, useState } from "react";
  5 | import {
  6 | 	Dialog,
  7 | 	DialogContent,
  8 | 	DialogDescription,
  9 | 	DialogHeader,
 10 | 	DialogTitle,
 11 | } from "./ui/dialog";
 12 | import { Input } from "./ui/input";
 13 | import { useRouter } from "next/navigation";
 14 | import Link from "next/link";
 15 | import { PasswordInput } from "./ui/password-input";
 16 | import { Checkbox } from "./ui/checkbox";
 17 | import { Button } from "./ui/button";
 18 | import { Key, Loader2 } from "lucide-react";
 19 | import { toast } from "sonner";
 20 | import { Label } from "./ui/label";
 21 | 
 22 | export function OneTap() {
 23 | 	const [isOpen, setIsOpen] = useState(false);
 24 | 	useEffect(() => {
 25 | 		client.oneTap({
 26 | 			onPromptNotification(notification) {
 27 | 				setIsOpen(true);
 28 | 			},
 29 | 		});
 30 | 	}, []);
 31 | 	return (
 32 | 		<Dialog open={isOpen} onOpenChange={(change) => setIsOpen(change)}>
 33 | 			<DialogContent>
 34 | 				<DialogHeader>
 35 | 					<DialogTitle className="text-lg md:text-xl">Sign In</DialogTitle>
 36 | 					<DialogDescription className="text-xs md:text-sm">
 37 | 						Enter your email below to login to your account
 38 | 					</DialogDescription>
 39 | 				</DialogHeader>
 40 | 				<SignInBox />
 41 | 			</DialogContent>
 42 | 		</Dialog>
 43 | 	);
 44 | }
 45 | 
 46 | function SignInBox() {
 47 | 	const [email, setEmail] = useState("");
 48 | 	const [password, setPassword] = useState("");
 49 | 	const [rememberMe, setRememberMe] = useState(false);
 50 | 	const router = useRouter();
 51 | 	const [loading, setLoading] = useState(false);
 52 | 	return (
 53 | 		<div className="grid gap-4">
 54 | 			<div className="grid gap-2">
 55 | 				<Label htmlFor="email">Email</Label>
 56 | 				<Input
 57 | 					id="email"
 58 | 					type="email"
 59 | 					placeholder="[email protected]"
 60 | 					required
 61 | 					onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
 62 | 						setEmail(e.target.value);
 63 | 					}}
 64 | 					value={email}
 65 | 				/>
 66 | 			</div>
 67 | 			<div className="grid gap-2">
 68 | 				<div className="flex items-center">
 69 | 					<Label htmlFor="password">Password</Label>
 70 | 					<Link
 71 | 						href="/forget-password"
 72 | 						className="ml-auto inline-block text-sm underline"
 73 | 					>
 74 | 						Forgot your password?
 75 | 					</Link>
 76 | 				</div>
 77 | 				<PasswordInput
 78 | 					id="password"
 79 | 					value={password}
 80 | 					onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
 81 | 						setPassword(e.target.value)
 82 | 					}
 83 | 					autoComplete="password"
 84 | 					placeholder="Password"
 85 | 				/>
 86 | 			</div>
 87 | 			<div className="flex items-center gap-2">
 88 | 				<Checkbox
 89 | 					onClick={() => {
 90 | 						setRememberMe(!rememberMe);
 91 | 					}}
 92 | 				/>
 93 | 				<Label>Remember me</Label>
 94 | 			</div>
 95 | 
 96 | 			<Button
 97 | 				type="submit"
 98 | 				className="w-full"
 99 | 				disabled={loading}
100 | 				onClick={async () => {
101 | 					await signIn.email(
102 | 						{
103 | 							email: email,
104 | 							password: password,
105 | 							callbackURL: "/dashboard",
106 | 							rememberMe,
107 | 						},
108 | 						{
109 | 							onRequest: () => {
110 | 								setLoading(true);
111 | 							},
112 | 							onResponse: () => {
113 | 								setLoading(false);
114 | 							},
115 | 							onError: (ctx) => {
116 | 								toast.error(ctx.error.message);
117 | 							},
118 | 						},
119 | 					);
120 | 				}}
121 | 			>
122 | 				{loading ? <Loader2 size={16} className="animate-spin" /> : "Login"}
123 | 			</Button>
124 | 			<Button
125 | 				variant="outline"
126 | 				className=" gap-2"
127 | 				onClick={async () => {
128 | 					await signIn.social({
129 | 						provider: "google",
130 | 						callbackURL: "/dashboard",
131 | 					});
132 | 				}}
133 | 			>
134 | 				<svg
135 | 					xmlns="http://www.w3.org/2000/svg"
136 | 					width="0.98em"
137 | 					height="1em"
138 | 					viewBox="0 0 256 262"
139 | 				>
140 | 					<path
141 | 						fill="#4285F4"
142 | 						d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
143 | 					/>
144 | 					<path
145 | 						fill="#34A853"
146 | 						d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
147 | 					/>
148 | 					<path
149 | 						fill="#FBBC05"
150 | 						d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z"
151 | 					/>
152 | 					<path
153 | 						fill="#EB4335"
154 | 						d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
155 | 					/>
156 | 				</svg>
157 | 				<p>Continue With Google</p>
158 | 			</Button>
159 | 			<Button
160 | 				variant="outline"
161 | 				className="gap-2"
162 | 				onClick={async () => {
163 | 					await signIn.passkey({
164 | 						fetchOptions: {
165 | 							onSuccess(context) {
166 | 								router.push("/dashboard");
167 | 							},
168 | 							onError(context) {
169 | 								toast.error(context.error.message);
170 | 							},
171 | 						},
172 | 					});
173 | 				}}
174 | 			>
175 | 				<Key size={16} />
176 | 				Sign-in with Passkey
177 | 			</Button>
178 | 		</div>
179 | 	);
180 | }
181 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/app/client-test/page.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import { useState, useTransition } from "react";
  4 | import { signIn, client } from "@/lib/auth-client";
  5 | import { Button } from "@/components/ui/button";
  6 | import {
  7 | 	Card,
  8 | 	CardContent,
  9 | 	CardDescription,
 10 | 	CardHeader,
 11 | 	CardTitle,
 12 | 	CardFooter,
 13 | } from "@/components/ui/card";
 14 | import { Input } from "@/components/ui/input";
 15 | import { Label } from "@/components/ui/label";
 16 | import { toast } from "sonner";
 17 | import { Loader2 } from "lucide-react";
 18 | 
 19 | export default function ClientTest() {
 20 | 	const [email, setEmail] = useState("");
 21 | 	const [password, setPassword] = useState("");
 22 | 	const [loading, startTransition] = useTransition();
 23 | 
 24 | 	// Get the session data using the useSession hook
 25 | 	const { data: session, isPending, error } = client.useSession();
 26 | 
 27 | 	const handleLogin = async () => {
 28 | 		startTransition(async () => {
 29 | 			await signIn.email(
 30 | 				{
 31 | 					email,
 32 | 					password,
 33 | 					callbackURL: "/client-test",
 34 | 				},
 35 | 				{
 36 | 					onError: (ctx) => {
 37 | 						toast.error(ctx.error.message);
 38 | 					},
 39 | 					onSuccess: () => {
 40 | 						toast.success("Successfully logged in!");
 41 | 						setEmail("");
 42 | 						setPassword("");
 43 | 					},
 44 | 				},
 45 | 			);
 46 | 		});
 47 | 	};
 48 | 
 49 | 	return (
 50 | 		<div className="container mx-auto py-10 space-y-8">
 51 | 			<h1 className="text-2xl font-bold text-center">
 52 | 				Client Authentication Test
 53 | 			</h1>
 54 | 
 55 | 			<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
 56 | 				{/* Login Form */}
 57 | 				<Card>
 58 | 					<CardHeader>
 59 | 						<CardTitle>Sign In</CardTitle>
 60 | 						<CardDescription>
 61 | 							Enter your email and password to sign in
 62 | 						</CardDescription>
 63 | 					</CardHeader>
 64 | 					<CardContent>
 65 | 						<div className="grid gap-4">
 66 | 							<div className="grid gap-2">
 67 | 								<Label htmlFor="email">Email</Label>
 68 | 								<Input
 69 | 									id="email"
 70 | 									type="email"
 71 | 									placeholder="[email protected]"
 72 | 									value={email}
 73 | 									onChange={(e) => setEmail(e.target.value)}
 74 | 								/>
 75 | 							</div>
 76 | 							<div className="grid gap-2">
 77 | 								<Label htmlFor="password">Password</Label>
 78 | 								<Input
 79 | 									id="password"
 80 | 									type="password"
 81 | 									placeholder="••••••••"
 82 | 									value={password}
 83 | 									onChange={(e) => setPassword(e.target.value)}
 84 | 								/>
 85 | 							</div>
 86 | 						</div>
 87 | 					</CardContent>
 88 | 					<CardFooter>
 89 | 						<Button className="w-full" onClick={handleLogin} disabled={loading}>
 90 | 							{loading ? (
 91 | 								<>
 92 | 									<Loader2 size={16} className="mr-2 animate-spin" />
 93 | 									Signing in...
 94 | 								</>
 95 | 							) : (
 96 | 								"Sign In"
 97 | 							)}
 98 | 						</Button>
 99 | 					</CardFooter>
100 | 				</Card>
101 | 
102 | 				{/* Session Display */}
103 | 				<Card>
104 | 					<CardHeader>
105 | 						<CardTitle>Session Information</CardTitle>
106 | 						<CardDescription>
107 | 							{isPending
108 | 								? "Loading session..."
109 | 								: session
110 | 									? "You are currently logged in"
111 | 									: "You are not logged in"}
112 | 						</CardDescription>
113 | 					</CardHeader>
114 | 					<CardContent>
115 | 						{isPending ? (
116 | 							<div className="flex justify-center py-4">
117 | 								<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
118 | 							</div>
119 | 						) : error ? (
120 | 							<div className="p-4 bg-destructive/10 text-destructive rounded-md">
121 | 								Error: {error.message}
122 | 							</div>
123 | 						) : session ? (
124 | 							<div className="space-y-4">
125 | 								<div className="flex items-center gap-4">
126 | 									{session.user.image ? (
127 | 										<img
128 | 											src={session.user.image}
129 | 											alt="Profile"
130 | 											className="h-12 w-12 rounded-full object-cover"
131 | 										/>
132 | 									) : (
133 | 										<div className="h-12 w-12 rounded-full bg-muted flex items-center justify-center">
134 | 											<span className="text-lg font-medium">
135 | 												{session.user.name?.charAt(0) ||
136 | 													session.user.email?.charAt(0)}
137 | 											</span>
138 | 										</div>
139 | 									)}
140 | 									<div>
141 | 										<p className="font-medium">{session.user.name}</p>
142 | 										<p className="text-sm text-muted-foreground">
143 | 											{session.user.email}
144 | 										</p>
145 | 									</div>
146 | 								</div>
147 | 
148 | 								<div className="rounded-md bg-muted p-4">
149 | 									<p className="text-sm font-medium mb-2">Session Details:</p>
150 | 									<pre className="text-xs overflow-auto max-h-40">
151 | 										{JSON.stringify(session, null, 2)}
152 | 									</pre>
153 | 								</div>
154 | 							</div>
155 | 						) : (
156 | 							<div className="py-8 text-center text-muted-foreground">
157 | 								<p>Sign in to view your session information</p>
158 | 							</div>
159 | 						)}
160 | 					</CardContent>
161 | 					{session && (
162 | 						<CardFooter>
163 | 							<Button
164 | 								variant="outline"
165 | 								className="w-full"
166 | 								onClick={() =>
167 | 									client.signOut({
168 | 										fetchOptions: {
169 | 											onSuccess: () => {
170 | 												toast.success("Successfully signed out!");
171 | 											},
172 | 										},
173 | 									})
174 | 								}
175 | 							>
176 | 								Sign Out
177 | 							</Button>
178 | 						</CardFooter>
179 | 					)}
180 | 				</Card>
181 | 			</div>
182 | 		</div>
183 | 	);
184 | }
185 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/plugins/magic-link.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: Magic link
  3 | description: Magic link plugin
  4 | ---
  5 | 
  6 | Magic link or email link is a way to authenticate users without a password. When a user enters their email, a link is sent to their email. When the user clicks on the link, they are authenticated.
  7 | 
  8 | ## Installation
  9 | 
 10 | <Steps>
 11 |     <Step>
 12 |     ### Add the server Plugin
 13 | 
 14 |     Add the magic link plugin to your server:
 15 | 
 16 |     ```ts title="server.ts"
 17 |     import { betterAuth } from "better-auth";
 18 |     import { magicLink } from "better-auth/plugins";
 19 | 
 20 |     export const auth = betterAuth({
 21 |         plugins: [
 22 |             magicLink({
 23 |                 sendMagicLink: async ({ email, token, url }, request) => {
 24 |                     // send email to user
 25 |                 }
 26 |             })
 27 |         ]
 28 |     })
 29 |     ```
 30 |     </Step>
 31 | 
 32 |     <Step>
 33 |     ### Add the client Plugin
 34 | 
 35 |     Add the magic link plugin to your client:
 36 | 
 37 |     ```ts title="auth-client.ts"
 38 |     import { createAuthClient } from "better-auth/client";
 39 |     import { magicLinkClient } from "better-auth/client/plugins";
 40 |     export const authClient = createAuthClient({
 41 |         plugins: [
 42 |             magicLinkClient()
 43 |         ]
 44 |     });
 45 |     ```
 46 |     </Step>
 47 | 
 48 | </Steps>
 49 | 
 50 | ## Usage
 51 | 
 52 | ### Sign In with Magic Link
 53 | 
 54 | To sign in with a magic link, you need to call `signIn.magicLink` with the user's email address. The `sendMagicLink` function is called to send the magic link to the user's email.
 55 | 
 56 | 
 57 | <APIMethod
 58 |   path="/sign-in/magic-link"
 59 |   method="POST"
 60 |   requireSession
 61 | >
 62 |   
 63 | ```ts
 64 | type signInMagicLink = {
 65 |     /**
 66 |      * Email address to send the magic link. 
 67 |      */
 68 |     email: string = "[email protected]"
 69 |     /**
 70 |      * User display name. Only used if the user is registering for the first time. 
 71 |      */
 72 |     name?: string = "my-name"
 73 |     /**
 74 |      * URL to redirect after magic link verification. 
 75 |      */
 76 |     callbackURL?: string = "/dashboard"
 77 |     /**
 78 |      * URL to redirect after new user signup
 79 |      */
 80 |     newUserCallbackURL?: string = "/welcome"
 81 |     /**
 82 |      * URL to redirect if an error happen on verification
 83 |      * If only callbackURL is provided but without an `errorCallbackURL` then they will be 
 84 |      * redirected to the callbackURL with an `error` query parameter.
 85 |      */
 86 |     errorCallbackURL?: string = "/error"
 87 | }
 88 | ```
 89 | </APIMethod>
 90 | 
 91 | <Callout>
 92 | If the user has not signed up, unless `disableSignUp` is set to `true`, the user will be signed up automatically.
 93 | </Callout>
 94 | 
 95 | ### Verify Magic Link
 96 | 
 97 | When you send the URL generated by the `sendMagicLink` function to a user, clicking the link will authenticate them and redirect them to the `callbackURL` specified in the `signIn.magicLink` function. If an error occurs, the user will be redirected to the `callbackURL` with an error query parameter.
 98 | 
 99 | <Callout type="warn">
100 |   If no `callbackURL` is provided, the user will be redirected to the root URL.
101 | </Callout>
102 | 
103 | If you want to handle the verification manually, (e.g, if you send the user a different URL), you can use the `verify` function.
104 | 
105 | 
106 | <APIMethod
107 |   path="/magic-link/verify"
108 |   method="GET"
109 |   requireSession
110 | >
111 | ```ts
112 | type magicLinkVerify = {
113 |     /**
114 |      * Verification token. 
115 |      */
116 |     token: string = "123456"
117 |     /**
118 |      * URL to redirect after magic link verification, if not provided will return the session. 
119 |      */
120 |     callbackURL?: string = "/dashboard"
121 | }
122 | ```
123 | </APIMethod>
124 | 
125 | ## Configuration Options
126 | 
127 | **sendMagicLink**: The `sendMagicLink` function is called when a user requests a magic link. It takes an object with the following properties:
128 | 
129 | - `email`: The email address of the user.
130 | - `url`: The URL to be sent to the user. This URL contains the token.
131 | - `token`: The token if you want to send the token with custom URL.
132 | 
133 | and a `request` object as the second parameter.
134 | 
135 | **expiresIn**: specifies the time in seconds after which the magic link will expire. The default value is `300` seconds (5 minutes).
136 | 
137 | **disableSignUp**: If set to `true`, the user will not be able to sign up using the magic link. The default value is `false`.
138 | 
139 | **generateToken**: The `generateToken` function is called to generate a token which is used to uniquely identify the user. The default value is a random string. There is one parameter:
140 | 
141 | - `email`: The email address of the user.
142 | 
143 | <Callout type="warn">
144 |   When using `generateToken`, ensure that the returned string is hard to guess
145 |   because it is used to verify who someone actually is in a confidential way. By
146 |   default, we return a long and cryptographically secure string.
147 | </Callout>
148 | 
149 | **storeToken**: The `storeToken` function is called to store the magic link token in the database. The default value is `"plain"`.
150 | 
151 | The `storeToken` function can be one of the following:
152 | 
153 | - `"plain"`: The token is stored in plain text.
154 | - `"hashed"`: The token is hashed using the default hasher.
155 | - `{ type: "custom-hasher", hash: (token: string) => Promise<string> }`: The token is hashed using a custom hasher.
156 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/adapters/tests/auth-flow.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { expect } from "vitest";
  2 | import { createTestSuite } from "../create-test-suite";
  3 | 
  4 | /**
  5 |  * This test suite tests basic authentication flow using the adapter.
  6 |  */
  7 | export const authFlowTestSuite = createTestSuite(
  8 | 	"auth-flow",
  9 | 	{},
 10 | 	(
 11 | 		{ generate, getAuth, modifyBetterAuthOptions, tryCatch },
 12 | 		debug?: { showDB?: () => Promise<void> },
 13 | 	) => ({
 14 | 		"should successfully sign up": async () => {
 15 | 			await modifyBetterAuthOptions(
 16 | 				{
 17 | 					emailAndPassword: {
 18 | 						enabled: true,
 19 | 						password: { hash: async (password) => password },
 20 | 					},
 21 | 				},
 22 | 				false,
 23 | 			);
 24 | 			const auth = await getAuth();
 25 | 			const user = await generate("user");
 26 | 			const start = Date.now();
 27 | 			const result = await auth.api.signUpEmail({
 28 | 				body: {
 29 | 					email: user.email,
 30 | 					password: crypto.randomUUID(),
 31 | 					name: user.name,
 32 | 					image: user.image || "",
 33 | 				},
 34 | 			});
 35 | 			const end = Date.now();
 36 | 			console.log(`signUpEmail took ${end - start}ms (without hashing)`);
 37 | 			expect(result.user).toBeDefined();
 38 | 			expect(result.user.email).toBe(user.email);
 39 | 			expect(result.user.name).toBe(user.name);
 40 | 			expect(result.user.image).toBe(user.image || "");
 41 | 			expect(result.user.emailVerified).toBe(false);
 42 | 			expect(result.user.createdAt).toBeDefined();
 43 | 			expect(result.user.updatedAt).toBeDefined();
 44 | 		},
 45 | 		"should successfully sign in": async () => {
 46 | 			await modifyBetterAuthOptions(
 47 | 				{
 48 | 					emailAndPassword: {
 49 | 						enabled: true,
 50 | 						password: {
 51 | 							hash: async (password) => password,
 52 | 							async verify(data) {
 53 | 								return data.hash === data.password;
 54 | 							},
 55 | 						},
 56 | 					},
 57 | 				},
 58 | 				false,
 59 | 			);
 60 | 			const auth = await getAuth();
 61 | 			const user = await generate("user");
 62 | 			const password = crypto.randomUUID();
 63 | 			const signUpResult = await auth.api.signUpEmail({
 64 | 				body: {
 65 | 					email: user.email,
 66 | 					password: password,
 67 | 					name: user.name,
 68 | 					image: user.image || "",
 69 | 				},
 70 | 			});
 71 | 			const start = Date.now();
 72 | 			const result = await auth.api.signInEmail({
 73 | 				body: { email: user.email, password: password },
 74 | 			});
 75 | 			const end = Date.now();
 76 | 			console.log(`signInEmail took ${end - start}ms (without hashing)`);
 77 | 			expect(result.user).toBeDefined();
 78 | 			expect(result.user.id).toBe(signUpResult.user.id);
 79 | 		},
 80 | 		"should successfully get session": async () => {
 81 | 			await modifyBetterAuthOptions(
 82 | 				{
 83 | 					emailAndPassword: {
 84 | 						enabled: true,
 85 | 						password: { hash: async (password) => password },
 86 | 					},
 87 | 				},
 88 | 				false,
 89 | 			);
 90 | 			const auth = await getAuth();
 91 | 			const user = await generate("user");
 92 | 			const password = crypto.randomUUID();
 93 | 
 94 | 			const { headers, response: signUpResult } = await auth.api.signUpEmail({
 95 | 				body: {
 96 | 					email: user.email,
 97 | 					password: password,
 98 | 					name: user.name,
 99 | 					image: user.image || "",
100 | 				},
101 | 				returnHeaders: true,
102 | 			});
103 | 
104 | 			// Convert set-cookie header to cookie header for getSession call
105 | 			const modifiedHeaders = new Headers(headers);
106 | 			if (headers.has("set-cookie")) {
107 | 				modifiedHeaders.set("cookie", headers.getSetCookie().join("; "));
108 | 				modifiedHeaders.delete("set-cookie");
109 | 			}
110 | 
111 | 			const start = Date.now();
112 | 			const result = await auth.api.getSession({
113 | 				headers: modifiedHeaders,
114 | 			});
115 | 			const end = Date.now();
116 | 			console.log(`getSession took ${end - start}ms`);
117 | 			expect(result?.user).toBeDefined();
118 | 			expect(result?.user).toStrictEqual(signUpResult.user);
119 | 			expect(result?.session).toBeDefined();
120 | 		},
121 | 		"should not sign in with invalid email": async () => {
122 | 			await modifyBetterAuthOptions(
123 | 				{ emailAndPassword: { enabled: true } },
124 | 				false,
125 | 			);
126 | 			const auth = await getAuth();
127 | 			const user = await generate("user");
128 | 			const { data, error } = await tryCatch(
129 | 				auth.api.signInEmail({
130 | 					body: { email: user.email, password: crypto.randomUUID() },
131 | 				}),
132 | 			);
133 | 			expect(data).toBeNull();
134 | 			expect(error).toBeDefined();
135 | 		},
136 | 		"should store and retrieve timestamps correctly across timezones":
137 | 			async () => {
138 | 				using _ = recoverProcessTZ();
139 | 				await modifyBetterAuthOptions(
140 | 					{ emailAndPassword: { enabled: true } },
141 | 					false,
142 | 				);
143 | 				const auth = await getAuth();
144 | 				const user = await generate("user");
145 | 				const password = crypto.randomUUID();
146 | 				const userSignUp = await auth.api.signUpEmail({
147 | 					body: {
148 | 						email: user.email,
149 | 						password: password,
150 | 						name: user.name,
151 | 						image: user.image || "",
152 | 					},
153 | 				});
154 | 				process.env.TZ = "Europe/London";
155 | 				const userSignIn = await auth.api.signInEmail({
156 | 					body: { email: user.email, password: password },
157 | 				});
158 | 				process.env.TZ = "America/Los_Angeles";
159 | 				expect(userSignUp.user.createdAt.toISOString()).toStrictEqual(
160 | 					userSignIn.user.createdAt.toISOString(),
161 | 				);
162 | 			},
163 | 	}),
164 | );
165 | 
166 | function recoverProcessTZ() {
167 | 	const originalTZ = process.env.TZ;
168 | 	return {
169 | 		[Symbol.dispose]: () => {
170 | 			process.env.TZ = originalTZ;
171 | 		},
172 | 	};
173 | }
174 | 
```

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

```markdown
  1 | ---
  2 | title: Remix Integration
  3 | description: Integrate Better Auth with Remix.
  4 | ---
  5 | 
  6 | Better Auth can be easily integrated with Remix. This guide will show you how to integrate Better Auth with Remix.
  7 | 
  8 | You can follow the steps from [installation](/docs/installation) to get started or you can follow this guide to make it the Remix-way.
  9 | 
 10 | If you have followed the installation steps, you can skip the first step.
 11 | 
 12 | ## Create auth instance
 13 | 
 14 | 
 15 | Create a file named `auth.server.ts` in one of these locations:
 16 |    - Project root
 17 |    - `lib/` folder
 18 |    - `utils/` folder
 19 | 
 20 | You can also nest any of these folders under `app/` folder. (e.g. `app/lib/auth.server.ts`)
 21 | 
 22 | And in this file, import Better Auth and create your instance.
 23 | 
 24 | <Callout type="warn">
 25 | Make sure to export the auth instance with the variable name `auth` or as a `default` export.
 26 | </Callout>
 27 | 
 28 | ```ts title="app/lib/auth.server.ts"
 29 | import { betterAuth } from "better-auth"
 30 | 
 31 | export const auth = betterAuth({
 32 |     database: {
 33 |         provider: "postgres", //change this to your database provider
 34 |         url: process.env.DATABASE_URL, // path to your database or connection string
 35 |     }
 36 | })
 37 | ```
 38 | 
 39 | ## Create API Route
 40 | 
 41 | We need to mount the handler to a API route. Create a resource route file `api.auth.$.ts` inside `app/routes/` directory. And add the following code:
 42 | 
 43 | ```ts title="app/routes/api.auth.$.ts"
 44 | import { auth } from '~/lib/auth.server' // Adjust the path as necessary
 45 | import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node"
 46 | 
 47 | export async function loader({ request }: LoaderFunctionArgs) {
 48 |     return auth.handler(request)
 49 | }
 50 | 
 51 | export async function action({ request }: ActionFunctionArgs) {
 52 |     return auth.handler(request)
 53 | }
 54 | ```
 55 | 
 56 | <Callout type="info">
 57 |  You can change the path on your better-auth configuration but it's recommended to keep it as `routes/api.auth.$.ts`
 58 | </Callout>
 59 | 
 60 | ## Create a client
 61 | 
 62 | Create a client instance. Here we are creating `auth-client.ts` file inside the `lib/` directory.
 63 | 
 64 | ```ts title="app/lib/auth-client.ts"
 65 | import { createAuthClient } from "better-auth/react" // make sure to import from better-auth/react
 66 | 
 67 | export const authClient = createAuthClient({
 68 |     //you can pass client configuration here
 69 | })
 70 | ```
 71 | 
 72 | Once you have created the client, you can use it to sign up, sign in, and perform other actions.
 73 | 
 74 | ### Example usage
 75 | 
 76 | #### Sign Up
 77 | 
 78 | ```ts title="app/routes/signup.tsx"
 79 | import { Form } from "@remix-run/react"
 80 | import { useState } from "react"
 81 | import { authClient } from "~/lib/auth-client"
 82 | 
 83 | export default function SignUp() {
 84 |   const [email, setEmail] = useState("")
 85 |   const [name, setName] = useState("")
 86 |   const [password, setPassword] = useState("")
 87 | 
 88 |   const signUp = async () => {
 89 |     await authClient.signUp.email(
 90 |       {
 91 |         email,
 92 |         password,
 93 |         name,
 94 |       },
 95 |       {
 96 |         onRequest: (ctx) => {
 97 |           // show loading state
 98 |         },
 99 |         onSuccess: (ctx) => {
100 |           // redirect to home
101 |         },
102 |         onError: (ctx) => {
103 |           alert(ctx.error)
104 |         },
105 |       },
106 |     )
107 |   }
108 | 
109 |   return (
110 |     <div>
111 |       <h2>
112 |         Sign Up
113 |       </h2>
114 |       <Form
115 |         onSubmit={signUp}
116 |       >
117 |         <input
118 |           type="text"
119 |           value={name}
120 |           onChange={(e) => setName(e.target.value)}
121 |           placeholder="Name"
122 |         />
123 |         <input
124 |           type="email"
125 |           value={email}
126 |           onChange={(e) => setEmail(e.target.value)}
127 |           placeholder="Email"
128 |         />
129 |         <input
130 |           type="password"
131 |           value={password}
132 |           onChange={(e) => setPassword(e.target.value)}
133 |           placeholder="Password"
134 |         />
135 |         <button
136 |           type="submit"
137 |         >
138 |           Sign Up
139 |         </button>
140 |       </Form>
141 |     </div>
142 |   )
143 | }
144 | 
145 | ```
146 | 
147 | #### Sign In
148 | 
149 | ```ts title="app/routes/signin.tsx"
150 | import { Form } from "@remix-run/react"
151 | import { useState } from "react"
152 | import { authClient } from "~/services/auth-client"
153 | 
154 | export default function SignIn() {
155 |   const [email, setEmail] = useState("")
156 |   const [password, setPassword] = useState("")
157 | 
158 |   const signIn = async () => {
159 |     await authClient.signIn.email(
160 |       {
161 |         email,
162 |         password,
163 |       },
164 |       {
165 |         onRequest: (ctx) => {
166 |           // show loading state
167 |         },
168 |         onSuccess: (ctx) => {
169 |           // redirect to home
170 |         },
171 |         onError: (ctx) => {
172 |           alert(ctx.error)
173 |         },
174 |       },
175 |     )
176 |   }
177 | 
178 |   return (
179 |     <div>
180 |       <h2>
181 |         Sign In
182 |       </h2>
183 |       <Form onSubmit={signIn}>
184 |         <input
185 |           type="email"
186 |           value={email}
187 |           onChange={(e) => setEmail(e.target.value)}
188 |         />
189 |         <input
190 |           type="password"
191 |           value={password}
192 |           onChange={(e) => setPassword(e.target.value)}
193 |         />
194 |         <button
195 |           type="submit"
196 |         >
197 |           Sign In
198 |         </button>
199 |       </Form>
200 |     </div>
201 |   )
202 | }
203 | ```
204 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/organization/error-codes.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { defineErrorCodes } from "@better-auth/core/utils";
 2 | 
 3 | export const ORGANIZATION_ERROR_CODES = defineErrorCodes({
 4 | 	YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION:
 5 | 		"You are not allowed to create a new organization",
 6 | 	YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS:
 7 | 		"You have reached the maximum number of organizations",
 8 | 	ORGANIZATION_ALREADY_EXISTS: "Organization already exists",
 9 | 	ORGANIZATION_SLUG_ALREADY_TAKEN: "Organization slug already taken",
10 | 	ORGANIZATION_NOT_FOUND: "Organization not found",
11 | 	USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION:
12 | 		"User is not a member of the organization",
13 | 	YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION:
14 | 		"You are not allowed to update this organization",
15 | 	YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION:
16 | 		"You are not allowed to delete this organization",
17 | 	NO_ACTIVE_ORGANIZATION: "No active organization",
18 | 	USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION:
19 | 		"User is already a member of this organization",
20 | 	MEMBER_NOT_FOUND: "Member not found",
21 | 	ROLE_NOT_FOUND: "Role not found",
22 | 	YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM:
23 | 		"You are not allowed to create a new team",
24 | 	TEAM_ALREADY_EXISTS: "Team already exists",
25 | 	TEAM_NOT_FOUND: "Team not found",
26 | 	YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER:
27 | 		"You cannot leave the organization as the only owner",
28 | 	YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER:
29 | 		"You cannot leave the organization without an owner",
30 | 	YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER:
31 | 		"You are not allowed to delete this member",
32 | 	YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION:
33 | 		"You are not allowed to invite users to this organization",
34 | 	USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION:
35 | 		"User is already invited to this organization",
36 | 	INVITATION_NOT_FOUND: "Invitation not found",
37 | 	YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION:
38 | 		"You are not the recipient of the invitation",
39 | 	EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION:
40 | 		"Email verification required before accepting or rejecting invitation",
41 | 	YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION:
42 | 		"You are not allowed to cancel this invitation",
43 | 	INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION:
44 | 		"Inviter is no longer a member of the organization",
45 | 	YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE:
46 | 		"You are not allowed to invite a user with this role",
47 | 	FAILED_TO_RETRIEVE_INVITATION: "Failed to retrieve invitation",
48 | 	YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS:
49 | 		"You have reached the maximum number of teams",
50 | 	UNABLE_TO_REMOVE_LAST_TEAM: "Unable to remove last team",
51 | 	YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER:
52 | 		"You are not allowed to update this member",
53 | 	ORGANIZATION_MEMBERSHIP_LIMIT_REACHED:
54 | 		"Organization membership limit reached",
55 | 	YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION:
56 | 		"You are not allowed to create teams in this organization",
57 | 	YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION:
58 | 		"You are not allowed to delete teams in this organization",
59 | 	YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM:
60 | 		"You are not allowed to update this team",
61 | 	YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM:
62 | 		"You are not allowed to delete this team",
63 | 	INVITATION_LIMIT_REACHED: "Invitation limit reached",
64 | 	TEAM_MEMBER_LIMIT_REACHED: "Team member limit reached",
65 | 	USER_IS_NOT_A_MEMBER_OF_THE_TEAM: "User is not a member of the team",
66 | 	YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM:
67 | 		"You are not allowed to list the members of this team",
68 | 	YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM: "You do not have an active team",
69 | 	YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM_MEMBER:
70 | 		"You are not allowed to create a new member",
71 | 	YOU_ARE_NOT_ALLOWED_TO_REMOVE_A_TEAM_MEMBER:
72 | 		"You are not allowed to remove a team member",
73 | 	YOU_ARE_NOT_ALLOWED_TO_ACCESS_THIS_ORGANIZATION:
74 | 		"You are not allowed to access this organization as an owner",
75 | 	YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION:
76 | 		"You are not a member of this organization",
77 | 	MISSING_AC_INSTANCE:
78 | 		"Dynamic Access Control requires a pre-defined ac instance on the server auth plugin. Read server logs for more information",
79 | 	YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE:
80 | 		"You must be in an organization to create a role",
81 | 	YOU_ARE_NOT_ALLOWED_TO_CREATE_A_ROLE: "You are not allowed to create a role",
82 | 	YOU_ARE_NOT_ALLOWED_TO_UPDATE_A_ROLE: "You are not allowed to update a role",
83 | 	YOU_ARE_NOT_ALLOWED_TO_DELETE_A_ROLE: "You are not allowed to delete a role",
84 | 	YOU_ARE_NOT_ALLOWED_TO_READ_A_ROLE: "You are not allowed to read a role",
85 | 	YOU_ARE_NOT_ALLOWED_TO_LIST_A_ROLE: "You are not allowed to list a role",
86 | 	YOU_ARE_NOT_ALLOWED_TO_GET_A_ROLE: "You are not allowed to get a role",
87 | 	TOO_MANY_ROLES: "This organization has too many roles",
88 | 	INVALID_RESOURCE: "The provided permission includes an invalid resource",
89 | 	ROLE_NAME_IS_ALREADY_TAKEN: "That role name is already taken",
90 | 	CANNOT_DELETE_A_PRE_DEFINED_ROLE: "Cannot delete a pre-defined role",
91 | });
92 | 
```

--------------------------------------------------------------------------------
/packages/telemetry/src/detectors/detect-system-info.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { env } from "@better-auth/core/env";
  2 | import { importRuntime } from "../utils/import-util";
  3 | 
  4 | function getVendor() {
  5 | 	const hasAny = (...keys: string[]) =>
  6 | 		keys.some((k) => Boolean((env as any)[k]));
  7 | 
  8 | 	if (
  9 | 		hasAny("CF_PAGES", "CF_PAGES_URL", "CF_ACCOUNT_ID") ||
 10 | 		(typeof navigator !== "undefined" &&
 11 | 			navigator.userAgent === "Cloudflare-Workers")
 12 | 	) {
 13 | 		return "cloudflare";
 14 | 	}
 15 | 
 16 | 	if (hasAny("VERCEL", "VERCEL_URL", "VERCEL_ENV")) return "vercel";
 17 | 
 18 | 	if (hasAny("NETLIFY", "NETLIFY_URL")) return "netlify";
 19 | 
 20 | 	if (
 21 | 		hasAny(
 22 | 			"RENDER",
 23 | 			"RENDER_URL",
 24 | 			"RENDER_INTERNAL_HOSTNAME",
 25 | 			"RENDER_SERVICE_ID",
 26 | 		)
 27 | 	) {
 28 | 		return "render";
 29 | 	}
 30 | 
 31 | 	if (
 32 | 		hasAny("AWS_LAMBDA_FUNCTION_NAME", "AWS_EXECUTION_ENV", "LAMBDA_TASK_ROOT")
 33 | 	) {
 34 | 		return "aws";
 35 | 	}
 36 | 
 37 | 	if (
 38 | 		hasAny(
 39 | 			"GOOGLE_CLOUD_FUNCTION_NAME",
 40 | 			"GOOGLE_CLOUD_PROJECT",
 41 | 			"GCP_PROJECT",
 42 | 			"K_SERVICE",
 43 | 		)
 44 | 	) {
 45 | 		return "gcp";
 46 | 	}
 47 | 
 48 | 	if (
 49 | 		hasAny(
 50 | 			"AZURE_FUNCTION_NAME",
 51 | 			"FUNCTIONS_WORKER_RUNTIME",
 52 | 			"WEBSITE_INSTANCE_ID",
 53 | 			"WEBSITE_SITE_NAME",
 54 | 		)
 55 | 	) {
 56 | 		return "azure";
 57 | 	}
 58 | 
 59 | 	if (hasAny("DENO_DEPLOYMENT_ID", "DENO_REGION")) return "deno-deploy";
 60 | 
 61 | 	if (hasAny("FLY_APP_NAME", "FLY_REGION", "FLY_ALLOC_ID")) return "fly-io";
 62 | 
 63 | 	if (hasAny("RAILWAY_STATIC_URL", "RAILWAY_ENVIRONMENT_NAME"))
 64 | 		return "railway";
 65 | 
 66 | 	if (hasAny("DYNO", "HEROKU_APP_NAME")) return "heroku";
 67 | 
 68 | 	if (hasAny("DO_DEPLOYMENT_ID", "DO_APP_NAME", "DIGITALOCEAN"))
 69 | 		return "digitalocean";
 70 | 
 71 | 	if (hasAny("KOYEB", "KOYEB_DEPLOYMENT_ID", "KOYEB_APP_NAME")) return "koyeb";
 72 | 
 73 | 	return null;
 74 | }
 75 | 
 76 | export async function detectSystemInfo() {
 77 | 	try {
 78 | 		//check if it's cloudflare
 79 | 		if (getVendor() === "cloudflare") return "cloudflare";
 80 | 		const os = await importRuntime<typeof import("os")>("os");
 81 | 		const cpus = os.cpus();
 82 | 		return {
 83 | 			deploymentVendor: getVendor(),
 84 | 			systemPlatform: os.platform(),
 85 | 			systemRelease: os.release(),
 86 | 			systemArchitecture: os.arch(),
 87 | 			cpuCount: cpus.length,
 88 | 			cpuModel: cpus.length ? cpus[0]!.model : null,
 89 | 			cpuSpeed: cpus.length ? cpus[0]!.speed : null,
 90 | 			memory: os.totalmem(),
 91 | 			isWSL: await isWsl(),
 92 | 			isDocker: await isDocker(),
 93 | 			isTTY:
 94 | 				typeof process !== "undefined" && (process as any).stdout
 95 | 					? (process as any).stdout.isTTY
 96 | 					: null,
 97 | 		};
 98 | 	} catch (e) {
 99 | 		return {
100 | 			systemPlatform: null,
101 | 			systemRelease: null,
102 | 			systemArchitecture: null,
103 | 			cpuCount: null,
104 | 			cpuModel: null,
105 | 			cpuSpeed: null,
106 | 			memory: null,
107 | 			isWSL: null,
108 | 			isDocker: null,
109 | 			isTTY: null,
110 | 		};
111 | 	}
112 | }
113 | 
114 | let isDockerCached: boolean | undefined;
115 | 
116 | async function hasDockerEnv() {
117 | 	if (getVendor() === "cloudflare") return false;
118 | 
119 | 	try {
120 | 		const fs = await importRuntime<typeof import("fs")>("fs");
121 | 		fs.statSync("/.dockerenv");
122 | 		return true;
123 | 	} catch {
124 | 		return false;
125 | 	}
126 | }
127 | 
128 | async function hasDockerCGroup() {
129 | 	if (getVendor() === "cloudflare") return false;
130 | 	try {
131 | 		const fs = await importRuntime<typeof import("fs")>("fs");
132 | 		return fs.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
133 | 	} catch {
134 | 		return false;
135 | 	}
136 | }
137 | 
138 | async function isDocker() {
139 | 	if (getVendor() === "cloudflare") return false;
140 | 
141 | 	if (isDockerCached === undefined) {
142 | 		isDockerCached = (await hasDockerEnv()) || (await hasDockerCGroup());
143 | 	}
144 | 
145 | 	return isDockerCached;
146 | }
147 | 
148 | async function isWsl() {
149 | 	try {
150 | 		if (getVendor() === "cloudflare") return false;
151 | 		if (typeof process === "undefined" || process?.platform !== "linux") {
152 | 			return false;
153 | 		}
154 | 		const fs = await importRuntime<typeof import("fs")>("fs");
155 | 		const os = await importRuntime<typeof import("os")>("os");
156 | 		if (os.release().toLowerCase().includes("microsoft")) {
157 | 			if (await isInsideContainer()) {
158 | 				return false;
159 | 			}
160 | 
161 | 			return true;
162 | 		}
163 | 
164 | 		return fs
165 | 			.readFileSync("/proc/version", "utf8")
166 | 			.toLowerCase()
167 | 			.includes("microsoft")
168 | 			? !(await isInsideContainer())
169 | 			: false;
170 | 	} catch {
171 | 		return false;
172 | 	}
173 | }
174 | 
175 | let isInsideContainerCached: boolean | undefined;
176 | 
177 | const hasContainerEnv = async () => {
178 | 	if (getVendor() === "cloudflare") return false;
179 | 	try {
180 | 		const fs = await importRuntime<typeof import("fs")>("fs");
181 | 		fs.statSync("/run/.containerenv");
182 | 		return true;
183 | 	} catch {
184 | 		return false;
185 | 	}
186 | };
187 | 
188 | async function isInsideContainer() {
189 | 	if (isInsideContainerCached === undefined) {
190 | 		isInsideContainerCached = (await hasContainerEnv()) || (await isDocker());
191 | 	}
192 | 
193 | 	return isInsideContainerCached;
194 | }
195 | 
196 | export function isCI() {
197 | 	return (
198 | 		env.CI !== "false" &&
199 | 		("BUILD_ID" in env || // Jenkins, Cloudbees
200 | 			"BUILD_NUMBER" in env || // Jenkins, TeamCity (fixed typo: extra space removed)
201 | 			"CI" in env || // Travis CI, CircleCI, Cirrus CI, Gitlab CI, Appveyor, CodeShip, dsari, Cloudflare
202 | 			"CI_APP_ID" in env || // Appflow
203 | 			"CI_BUILD_ID" in env || // Appflow
204 | 			"CI_BUILD_NUMBER" in env || // Appflow
205 | 			"CI_NAME" in env || // Codeship and others
206 | 			"CONTINUOUS_INTEGRATION" in env || // Travis CI, Cirrus CI
207 | 			"RUN_ID" in env) // TaskCluster, dsari
208 | 	);
209 | }
210 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/guides/optimizing-for-performance.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: Optimizing for Performance
  3 | description: A guide to optimizing your Better Auth application for performance.
  4 | ---
  5 | 
  6 | In this guide, we’ll go over some of the ways you can optimize your application for a more performant Better Auth app.
  7 | 
  8 | ## Caching
  9 | 
 10 | Caching is a powerful technique that can significantly improve the performance of your Better Auth application by reducing the number of database queries and speeding up response times.
 11 | 
 12 | ### Cookie Cache
 13 | 
 14 | Calling your database every time `useSession` or `getSession` is invoked isn’t ideal, especially if sessions don’t change frequently. Cookie caching handles this by storing session data in a short-lived, signed cookie similar to how JWT access tokens are used with refresh tokens.
 15 | 
 16 | To turn on cookie caching, just set `session.cookieCache` in your auth config:
 17 | 
 18 | ```ts title="auth.ts"
 19 | import { betterAuth } from "better-auth";
 20 | 
 21 | export const auth = betterAuth({
 22 |   session: {
 23 |     cookieCache: {
 24 |       enabled: true,
 25 |       maxAge: 5 * 60, // Cache duration in seconds
 26 |     },
 27 |   },
 28 | });
 29 | ```
 30 | 
 31 | Read more about [cookie caching](/docs/concepts/session-management#cookie-cache).
 32 | 
 33 | ### Framework Caching
 34 | 
 35 | Here are examples of how you can do caching in different frameworks and environments:
 36 | 
 37 | <Tabs items={["Next", "Remix", "SolidStart", "React Query"]}>
 38 |   <Tab value="Next">
 39 |     Since Next v15, we can use the `"use cache"` directive to cache the response of a server function.
 40 | 
 41 |     ```ts
 42 |     export async function getUsers() {
 43 |         'use cache' // [!code highlight]
 44 |         const { users } = await auth.api.listUsers();
 45 |         return users
 46 |     }
 47 |     ```
 48 | 
 49 |     Learn more about NextJS use cache directive <Link href="https://nextjs.org/docs/app/api-reference/directives/use-cache">here</Link>.
 50 | 
 51 |   </Tab>
 52 |     <Tab value="Remix">
 53 |     In Remix, you can use the `cache` option in the `loader` function to cache responses on the server. Here’s an example:
 54 | 
 55 |     ```ts
 56 |     import { json } from '@remix-run/node';
 57 | 
 58 |     export const loader = async () => {
 59 |     const { users } = await auth.api.listUsers();
 60 |     return json(users, {
 61 |         headers: {
 62 |         'Cache-Control': 'max-age=3600', // Cache for 1 hour
 63 |         },
 64 |     });
 65 |     };
 66 |     ```
 67 | 
 68 | 
 69 |     You can read a nice guide on Loader vs Route Cache Headers in Remix <Link href="https://sergiodxa.com/articles/loader-vs-route-cache-headers-in-remix">here</Link>.
 70 | 
 71 |   </Tab>
 72 | 
 73 |   <Tab value="SolidStart">
 74 |     In SolidStart, you can use the `query` function to cache data. Here’s an example:
 75 | 
 76 |     ```tsx
 77 |     const getUsers = query(
 78 |         async () => (await auth.api.listUsers()).users,
 79 |         "getUsers"
 80 |     );
 81 |     ```
 82 | 
 83 |     Learn more about SolidStart `query` function <Link href="https://docs.solidjs.com/solid-router/reference/data-apis/query">here</Link>.
 84 | 
 85 |   </Tab>
 86 |   <Tab value="React Query">
 87 |     With React Query you can use the `useQuery` hook to cache data. Here’s an example:
 88 | 
 89 |     ```ts
 90 |     import { useQuery } from '@tanstack/react-query';
 91 | 
 92 |     const fetchUsers = async () => {
 93 |         const { users } = await auth.api.listUsers();
 94 |         return users;
 95 |     };
 96 | 
 97 |     export default function Users() {
 98 |         const { data: users, isLoading } = useQuery('users', fetchUsers, {
 99 |             staleTime: 1000 * 60 * 15, // Cache for 15 minutes
100 |         });
101 | 
102 |         if (isLoading) return <div>Loading...</div>;
103 | 
104 |         return (
105 |             <ul>
106 |                 {users.map(user => (
107 |                     <li key={user.id}>{user.name}</li>
108 |                 ))}
109 |             </ul>
110 |         );
111 |     }
112 |     ```
113 | 
114 |     Learn more about React Query use cache directive <Link href="https://react-query.tanstack.com/reference/useQuery#usecache">here</Link>.
115 | 
116 |   </Tab>
117 | </Tabs>
118 | 
119 | ## SSR Optimizations
120 | 
121 | If you're using a framework that supports server-side rendering, it's usually best to pre-fetch the user session on the server and use it as a fallback on the client.
122 | 
123 | ```ts
124 | const session = await auth.api.getSession({
125 |   headers: await headers(),
126 | });
127 | //then pass the session to the client
128 | ```
129 | 
130 | ## Database optimizations
131 | 
132 | Optimizing database performance is essential to get the best out of Better Auth.
133 | 
134 | #### Recommended fields to index
135 | 
136 | | Table         | Fields                     | Plugin       |
137 | | ------------- | -------------------------- | ------------ |
138 | | users         | `email`                    |              |
139 | | accounts      | `userId`                   |              |
140 | | sessions      | `userId`, `token`          |              |
141 | | verifications | `identifier`               |              |
142 | | invitations   | `email`, `organizationId`  | organization |
143 | | members       | `userId`, `organizationId` | organization |
144 | | organizations | `slug`                     | organization |
145 | | passkey       | `userId`                   | passkey      |
146 | | twoFactor     | `secret`                   | twoFactor    |
147 | 
148 | <Callout>
149 |   We intend to add indexing support in our schema generation tool in the future.
150 | </Callout>
151 | 
```

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

```typescript
  1 | import type { BetterAuthPluginDBSchema } from "@better-auth/core/db";
  2 | import type { BetterAuthOptions } from "@better-auth/core";
  3 | import { APIError } from "better-call";
  4 | import type { Account, Session, User } from "../types";
  5 | import type { DBFieldAttribute } from "@better-auth/core/db";
  6 | 
  7 | // Cache for parsed schemas to avoid reparsing on every request
  8 | const cache = new WeakMap<
  9 | 	BetterAuthOptions,
 10 | 	Map<string, Record<string, DBFieldAttribute>>
 11 | >();
 12 | 
 13 | function parseOutputData<T extends Record<string, any>>(
 14 | 	data: T,
 15 | 	schema: {
 16 | 		fields: Record<string, DBFieldAttribute>;
 17 | 	},
 18 | ) {
 19 | 	const fields = schema.fields;
 20 | 	const parsedData: Record<string, any> = {};
 21 | 	for (const key in data) {
 22 | 		const field = fields[key];
 23 | 		if (!field) {
 24 | 			parsedData[key] = data[key];
 25 | 			continue;
 26 | 		}
 27 | 		if (field.returned === false) {
 28 | 			continue;
 29 | 		}
 30 | 		parsedData[key] = data[key];
 31 | 	}
 32 | 	return parsedData as T;
 33 | }
 34 | 
 35 | function getAllFields(options: BetterAuthOptions, table: string) {
 36 | 	if (!cache.has(options)) {
 37 | 		cache.set(options, new Map());
 38 | 	}
 39 | 	const tableCache = cache.get(options)!;
 40 | 	if (tableCache.has(table)) {
 41 | 		return tableCache.get(table)!;
 42 | 	}
 43 | 	let schema: Record<string, DBFieldAttribute> = {
 44 | 		...(table === "user" ? options.user?.additionalFields : {}),
 45 | 		...(table === "session" ? options.session?.additionalFields : {}),
 46 | 	};
 47 | 	for (const plugin of options.plugins || []) {
 48 | 		if (plugin.schema && plugin.schema[table]) {
 49 | 			schema = {
 50 | 				...schema,
 51 | 				...plugin.schema[table].fields,
 52 | 			};
 53 | 		}
 54 | 	}
 55 | 	cache.get(options)!.set(table, schema);
 56 | 	return schema;
 57 | }
 58 | 
 59 | export function parseUserOutput(options: BetterAuthOptions, user: User) {
 60 | 	const schema = getAllFields(options, "user");
 61 | 	return parseOutputData(user, { fields: schema });
 62 | }
 63 | 
 64 | export function parseAccountOutput(
 65 | 	options: BetterAuthOptions,
 66 | 	account: Account,
 67 | ) {
 68 | 	const schema = getAllFields(options, "account");
 69 | 	return parseOutputData(account, { fields: schema });
 70 | }
 71 | 
 72 | export function parseSessionOutput(
 73 | 	options: BetterAuthOptions,
 74 | 	session: Session,
 75 | ) {
 76 | 	const schema = getAllFields(options, "session");
 77 | 	return parseOutputData(session, { fields: schema });
 78 | }
 79 | 
 80 | export function parseInputData<T extends Record<string, any>>(
 81 | 	data: T,
 82 | 	schema: {
 83 | 		fields: Record<string, DBFieldAttribute>;
 84 | 		action?: "create" | "update";
 85 | 	},
 86 | ) {
 87 | 	const action = schema.action || "create";
 88 | 	const fields = schema.fields;
 89 | 	const parsedData: Record<string, any> = Object.assign(
 90 | 		Object.create(null),
 91 | 		null,
 92 | 	);
 93 | 	for (const key in fields) {
 94 | 		if (key in data) {
 95 | 			if (fields[key]!.input === false) {
 96 | 				if (fields[key]!.defaultValue !== undefined) {
 97 | 					parsedData[key] = fields[key]!.defaultValue;
 98 | 					continue;
 99 | 				}
100 | 				if (parsedData[key]) {
101 | 					throw new APIError("BAD_REQUEST", {
102 | 						message: `${key} is not allowed to be set`,
103 | 					});
104 | 				}
105 | 				continue;
106 | 			}
107 | 			if (fields[key]!.validator?.input && data[key] !== undefined) {
108 | 				parsedData[key] = fields[key]!.validator.input.parse(data[key]);
109 | 				continue;
110 | 			}
111 | 			if (fields[key]!.transform?.input && data[key] !== undefined) {
112 | 				parsedData[key] = fields[key]!.transform?.input(data[key]);
113 | 				continue;
114 | 			}
115 | 			parsedData[key] = data[key];
116 | 			continue;
117 | 		}
118 | 
119 | 		if (fields[key]!.defaultValue !== undefined && action === "create") {
120 | 			parsedData[key] = fields[key]!.defaultValue;
121 | 			continue;
122 | 		}
123 | 
124 | 		if (fields[key]!.required && action === "create") {
125 | 			throw new APIError("BAD_REQUEST", {
126 | 				message: `${key} is required`,
127 | 			});
128 | 		}
129 | 	}
130 | 	return parsedData as Partial<T>;
131 | }
132 | 
133 | export function parseUserInput(
134 | 	options: BetterAuthOptions,
135 | 	user: Record<string, any> = {},
136 | 	action: "create" | "update",
137 | ) {
138 | 	const schema = getAllFields(options, "user");
139 | 	return parseInputData(user, { fields: schema, action });
140 | }
141 | 
142 | export function parseAdditionalUserInput(
143 | 	options: BetterAuthOptions,
144 | 	user?: Record<string, any>,
145 | ) {
146 | 	const schema = getAllFields(options, "user");
147 | 	return parseInputData(user || {}, { fields: schema });
148 | }
149 | 
150 | export function parseAccountInput(
151 | 	options: BetterAuthOptions,
152 | 	account: Partial<Account>,
153 | ) {
154 | 	const schema = getAllFields(options, "account");
155 | 	return parseInputData(account, { fields: schema });
156 | }
157 | 
158 | export function parseSessionInput(
159 | 	options: BetterAuthOptions,
160 | 	session: Partial<Session>,
161 | ) {
162 | 	const schema = getAllFields(options, "session");
163 | 	return parseInputData(session, { fields: schema });
164 | }
165 | 
166 | export function mergeSchema<S extends BetterAuthPluginDBSchema>(
167 | 	schema: S,
168 | 	newSchema?: {
169 | 		[K in keyof S]?: {
170 | 			modelName?: string;
171 | 			fields?: {
172 | 				[P: string]: string;
173 | 			};
174 | 		};
175 | 	},
176 | ) {
177 | 	if (!newSchema) {
178 | 		return schema;
179 | 	}
180 | 	for (const table in newSchema) {
181 | 		const newModelName = newSchema[table]?.modelName;
182 | 		if (newModelName) {
183 | 			schema[table]!.modelName = newModelName;
184 | 		}
185 | 		for (const field in schema[table]!.fields) {
186 | 			const newField = newSchema[table]?.fields?.[field];
187 | 			if (!newField) {
188 | 				continue;
189 | 			}
190 | 			schema[table]!.fields[field]!.fieldName = newField;
191 | 		}
192 | 	}
193 | 	return schema;
194 | }
195 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/one-tap/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as z from "zod";
  2 | import { APIError } from "../../api";
  3 | import { createAuthEndpoint } from "@better-auth/core/api";
  4 | import { setSessionCookie } from "../../cookies";
  5 | import type { BetterAuthPlugin } from "@better-auth/core";
  6 | import { jwtVerify, createRemoteJWKSet } from "jose";
  7 | import { toBoolean } from "../../utils/boolean";
  8 | 
  9 | interface OneTapOptions {
 10 | 	/**
 11 | 	 * Disable the signup flow
 12 | 	 *
 13 | 	 * @default false
 14 | 	 */
 15 | 	disableSignup?: boolean;
 16 | 	/**
 17 | 	 * Google Client ID
 18 | 	 *
 19 | 	 * If a client ID is provided in the social provider configuration,
 20 | 	 * it will be used.
 21 | 	 */
 22 | 	clientId?: string;
 23 | }
 24 | 
 25 | export const oneTap = (options?: OneTapOptions) =>
 26 | 	({
 27 | 		id: "one-tap",
 28 | 		endpoints: {
 29 | 			oneTapCallback: createAuthEndpoint(
 30 | 				"/one-tap/callback",
 31 | 				{
 32 | 					method: "POST",
 33 | 					body: z.object({
 34 | 						idToken: z.string().meta({
 35 | 							description:
 36 | 								"Google ID token, which the client obtains from the One Tap API",
 37 | 						}),
 38 | 					}),
 39 | 					metadata: {
 40 | 						openapi: {
 41 | 							summary: "One tap callback",
 42 | 							description:
 43 | 								"Use this endpoint to authenticate with Google One Tap",
 44 | 							responses: {
 45 | 								200: {
 46 | 									description: "Successful response",
 47 | 									content: {
 48 | 										"application/json": {
 49 | 											schema: {
 50 | 												type: "object",
 51 | 												properties: {
 52 | 													session: {
 53 | 														$ref: "#/components/schemas/Session",
 54 | 													},
 55 | 													user: {
 56 | 														$ref: "#/components/schemas/User",
 57 | 													},
 58 | 												},
 59 | 											},
 60 | 										},
 61 | 									},
 62 | 								},
 63 | 								400: {
 64 | 									description: "Invalid token",
 65 | 								},
 66 | 							},
 67 | 						},
 68 | 					},
 69 | 				},
 70 | 				async (ctx) => {
 71 | 					const { idToken } = ctx.body;
 72 | 					let payload: any;
 73 | 					try {
 74 | 						const JWKS = createRemoteJWKSet(
 75 | 							new URL("https://www.googleapis.com/oauth2/v3/certs"),
 76 | 						);
 77 | 						const { payload: verifiedPayload } = await jwtVerify(
 78 | 							idToken,
 79 | 							JWKS,
 80 | 							{
 81 | 								issuer: ["https://accounts.google.com", "accounts.google.com"],
 82 | 								audience:
 83 | 									options?.clientId ||
 84 | 									ctx.context.options.socialProviders?.google?.clientId,
 85 | 							},
 86 | 						);
 87 | 						payload = verifiedPayload;
 88 | 					} catch (error) {
 89 | 						throw new APIError("BAD_REQUEST", {
 90 | 							message: "invalid id token",
 91 | 						});
 92 | 					}
 93 | 					const { email, email_verified, name, picture, sub } = payload;
 94 | 					if (!email) {
 95 | 						return ctx.json({ error: "Email not available in token" });
 96 | 					}
 97 | 
 98 | 					const user = await ctx.context.internalAdapter.findUserByEmail(email);
 99 | 					if (!user) {
100 | 						if (options?.disableSignup) {
101 | 							throw new APIError("BAD_GATEWAY", {
102 | 								message: "User not found",
103 | 							});
104 | 						}
105 | 						const newUser = await ctx.context.internalAdapter.createOAuthUser(
106 | 							{
107 | 								email,
108 | 								emailVerified:
109 | 									typeof email_verified === "boolean"
110 | 										? email_verified
111 | 										: toBoolean(email_verified),
112 | 								name,
113 | 								image: picture,
114 | 							},
115 | 							{
116 | 								providerId: "google",
117 | 								accountId: sub,
118 | 							},
119 | 						);
120 | 						if (!newUser) {
121 | 							throw new APIError("INTERNAL_SERVER_ERROR", {
122 | 								message: "Could not create user",
123 | 							});
124 | 						}
125 | 						const session = await ctx.context.internalAdapter.createSession(
126 | 							newUser.user.id,
127 | 						);
128 | 						await setSessionCookie(ctx, {
129 | 							user: newUser.user,
130 | 							session,
131 | 						});
132 | 						return ctx.json({
133 | 							token: session.token,
134 | 							user: {
135 | 								id: newUser.user.id,
136 | 								email: newUser.user.email,
137 | 								emailVerified: newUser.user.emailVerified,
138 | 								name: newUser.user.name,
139 | 								image: newUser.user.image,
140 | 								createdAt: newUser.user.createdAt,
141 | 								updatedAt: newUser.user.updatedAt,
142 | 							},
143 | 						});
144 | 					}
145 | 					const account = await ctx.context.internalAdapter.findAccount(sub);
146 | 					if (!account) {
147 | 						const accountLinking = ctx.context.options.account?.accountLinking;
148 | 						const shouldLinkAccount =
149 | 							accountLinking?.enabled &&
150 | 							(accountLinking.trustedProviders?.includes("google") ||
151 | 								email_verified);
152 | 						if (shouldLinkAccount) {
153 | 							await ctx.context.internalAdapter.linkAccount({
154 | 								userId: user.user.id,
155 | 								providerId: "google",
156 | 								accountId: sub,
157 | 								scope: "openid,profile,email",
158 | 								idToken,
159 | 							});
160 | 						} else {
161 | 							throw new APIError("UNAUTHORIZED", {
162 | 								message: "Google sub doesn't match",
163 | 							});
164 | 						}
165 | 					}
166 | 					const session = await ctx.context.internalAdapter.createSession(
167 | 						user.user.id,
168 | 					);
169 | 
170 | 					await setSessionCookie(ctx, {
171 | 						user: user.user,
172 | 						session,
173 | 					});
174 | 					return ctx.json({
175 | 						token: session.token,
176 | 						user: {
177 | 							id: user.user.id,
178 | 							email: user.user.email,
179 | 							emailVerified: user.user.emailVerified,
180 | 							name: user.user.name,
181 | 							image: user.user.image,
182 | 							createdAt: user.user.createdAt,
183 | 							updatedAt: user.user.updatedAt,
184 | 						},
185 | 					});
186 | 				},
187 | 			),
188 | 		},
189 | 	}) satisfies BetterAuthPlugin;
190 | 
```

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

```markdown
  1 | ---
  2 | title: Google
  3 | description: Google provider setup and usage.
  4 | ---
  5 | 
  6 | <Steps>
  7 |     <Step> 
  8 |         ### Get your Google credentials
  9 |         To use Google as a social provider, you need to get your Google credentials. You can get them by creating a new project in the [Google Cloud Console](https://console.cloud.google.com/apis/dashboard).
 10 | 
 11 |         In the Google Cloud Console > Credentials > Authorized redirect URIs, make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/google` for local development. For production, make sure to set the redirect URL as your application domain, e.g. `https://example.com/api/auth/callback/google`. If you change the base path of the auth routes, you should update the redirect URL accordingly.
 12 |     </Step>
 13 | 
 14 |   <Step>
 15 |         ### Configure the provider
 16 |         To configure the provider, you need to pass the `clientId` and `clientSecret` to `socialProviders.google` in your auth configuration.
 17 | 
 18 |         ```ts title="auth.ts"
 19 |         import { betterAuth } from "better-auth"
 20 | 
 21 |         export const auth = betterAuth({
 22 |             socialProviders: {
 23 |                 google: { // [!code highlight]
 24 |                     clientId: process.env.GOOGLE_CLIENT_ID as string, // [!code highlight]
 25 |                     clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, // [!code highlight]
 26 |                 }, // [!code highlight]
 27 |             },
 28 |         })
 29 |         ```
 30 |     </Step>
 31 | 
 32 | </Steps>
 33 | 
 34 | ## Usage
 35 | 
 36 | ### Sign In with Google
 37 | 
 38 | To sign in with Google, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
 39 | 
 40 | - `provider`: The provider to use. It should be set to `google`.
 41 | 
 42 | ```ts title="auth-client.ts"  /
 43 | import { createAuthClient } from "better-auth/client";
 44 | const authClient = createAuthClient();
 45 | 
 46 | const signIn = async () => {
 47 |   const data = await authClient.signIn.social({
 48 |     provider: "google",
 49 |   });
 50 | };
 51 | ```
 52 | 
 53 | ### Sign In with Google With ID Token
 54 | 
 55 | To sign in with Google using the ID Token, you can use the `signIn.social` function to pass the ID Token.
 56 | 
 57 | This is useful when you have the ID Token from Google on the client-side and want to use it to sign in on the server.
 58 | 
 59 | <Callout>
 60 |   If ID token is provided no redirection will happen, and the user will be
 61 |   signed in directly.
 62 | </Callout>
 63 | 
 64 | ```ts title="auth-client.ts"
 65 | const data = await authClient.signIn.social({
 66 |     provider: "google",
 67 |     idToken: {
 68 |         token: // Google ID Token,
 69 |         accessToken: // Google Access Token
 70 |     }
 71 | })
 72 | ```
 73 | 
 74 | <Callout>
 75 |   If you want to use google one tap, you can use the [One Tap
 76 |   Plugin](/docs/plugins/one-tap) guide.
 77 | </Callout>
 78 | 
 79 | ### Always ask to select an account
 80 | 
 81 | If you want to always ask the user to select an account, you pass the `prompt` parameter to the provider, setting it to `select_account`.
 82 | 
 83 | ```ts
 84 | socialProviders: {
 85 |     google: {
 86 |         prompt: "select_account", // [!code highlight]
 87 |         clientId: process.env.GOOGLE_CLIENT_ID as string,
 88 |         clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
 89 |     },
 90 | }
 91 | ```
 92 | 
 93 | ### Requesting Additional Google Scopes
 94 | 
 95 | If your application needs additional Google scopes after the user has already signed up (e.g., for Google Drive, Gmail, or other Google services), you can request them using the `linkSocial` method with the same Google provider.
 96 | 
 97 | ```tsx title="auth-client.ts"
 98 | const requestGoogleDriveAccess = async () => {
 99 |   await authClient.linkSocial({
100 |     provider: "google",
101 |     scopes: ["https://www.googleapis.com/auth/drive.file"],
102 |   });
103 | };
104 | 
105 | // Example usage in a React component
106 | return (
107 |   <button onClick={requestGoogleDriveAccess}>
108 |     Add Google Drive Permissions
109 |   </button>
110 | );
111 | ```
112 | 
113 | This will trigger a new OAuth flow that requests the additional scopes. After completion, your account will have the new scope in the database, and the access token will give you access to the requested Google APIs.
114 | 
115 | <Callout>
116 |   Ensure you're using Better Auth version 1.2.7 or later to avoid "Social
117 |   account already linked" errors when requesting additional scopes from the same
118 |   provider.
119 | </Callout>
120 | 
121 | ### Always get refresh token
122 | 
123 | Google only issues a refresh token the first time a user consents to your app.
124 | If the user has already authorized your app, subsequent OAuth flows will only return an access token, not a refresh token.
125 | 
126 | To always get a refresh token, you can set the `accessType` to `offline`, and `prompt` to `select_account consent` in the provider options.
127 | 
128 | ```ts
129 | socialProviders: {
130 |     google: {
131 |         clientId: process.env.GOOGLE_CLIENT_ID as string,
132 |         clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
133 |         accessType: "offline", // [!code highlight]
134 |         prompt: "select_account consent", // [!code highlight]
135 |     },
136 | }
137 | ```
138 | 
139 | <Callout>
140 |   **Revoking Access:** If you want to get a new refresh token for a user who has
141 |   already authorized your app, you must have them revoke your app's access in
142 |   their Google account settings, then re-authorize.
143 | </Callout>
144 | 
```

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

```typescript
  1 | import * as React from "react";
  2 | import { ChevronDownIcon } from "@radix-ui/react-icons";
  3 | import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
  4 | import { cva } from "class-variance-authority";
  5 | 
  6 | import { cn } from "@/lib/utils";
  7 | 
  8 | const NavigationMenu = ({
  9 | 	ref,
 10 | 	className,
 11 | 	children,
 12 | 	...props
 13 | }: React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root> & {
 14 | 	ref: React.RefObject<React.ElementRef<typeof NavigationMenuPrimitive.Root>>;
 15 | }) => (
 16 | 	<NavigationMenuPrimitive.Root
 17 | 		ref={ref}
 18 | 		className={cn(
 19 | 			"relative z-10 flex max-w-max flex-1 items-center justify-center",
 20 | 			className,
 21 | 		)}
 22 | 		{...props}
 23 | 	>
 24 | 		{children}
 25 | 		<NavigationMenuViewport />
 26 | 	</NavigationMenuPrimitive.Root>
 27 | );
 28 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
 29 | 
 30 | const NavigationMenuList = ({
 31 | 	ref,
 32 | 	className,
 33 | 	...props
 34 | }: React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List> & {
 35 | 	ref: React.RefObject<React.ElementRef<typeof NavigationMenuPrimitive.List>>;
 36 | }) => (
 37 | 	<NavigationMenuPrimitive.List
 38 | 		ref={ref}
 39 | 		className={cn(
 40 | 			"group flex flex-1 list-none items-center justify-center space-x-1",
 41 | 			className,
 42 | 		)}
 43 | 		{...props}
 44 | 	/>
 45 | );
 46 | NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
 47 | 
 48 | const NavigationMenuItem = NavigationMenuPrimitive.Item;
 49 | 
 50 | const navigationMenuTriggerStyle = cva(
 51 | 	"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-active:bg-accent/50 data-[state=open]:bg-accent/50",
 52 | );
 53 | 
 54 | const NavigationMenuTrigger = ({
 55 | 	ref,
 56 | 	className,
 57 | 	children,
 58 | 	...props
 59 | }: React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger> & {
 60 | 	ref: React.RefObject<
 61 | 		React.ElementRef<typeof NavigationMenuPrimitive.Trigger>
 62 | 	>;
 63 | }) => (
 64 | 	<NavigationMenuPrimitive.Trigger
 65 | 		ref={ref}
 66 | 		className={cn(navigationMenuTriggerStyle(), "group", className)}
 67 | 		{...props}
 68 | 	>
 69 | 		{children}{" "}
 70 | 		<ChevronDownIcon
 71 | 			className="relative top-px ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
 72 | 			aria-hidden="true"
 73 | 		/>
 74 | 	</NavigationMenuPrimitive.Trigger>
 75 | );
 76 | NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
 77 | 
 78 | const NavigationMenuContent = ({
 79 | 	ref,
 80 | 	className,
 81 | 	...props
 82 | }: React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content> & {
 83 | 	ref: React.RefObject<
 84 | 		React.ElementRef<typeof NavigationMenuPrimitive.Content>
 85 | 	>;
 86 | }) => (
 87 | 	<NavigationMenuPrimitive.Content
 88 | 		ref={ref}
 89 | 		className={cn(
 90 | 			"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
 91 | 			className,
 92 | 		)}
 93 | 		{...props}
 94 | 	/>
 95 | );
 96 | NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
 97 | 
 98 | const NavigationMenuLink = NavigationMenuPrimitive.Link;
 99 | 
100 | const NavigationMenuViewport = ({
101 | 	ref,
102 | 	className,
103 | 	...props
104 | }: React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport> & {
105 | 	ref: React.RefObject<
106 | 		React.ElementRef<typeof NavigationMenuPrimitive.Viewport>
107 | 	>;
108 | }) => (
109 | 	<div className={cn("absolute left-0 top-full flex justify-center")}>
110 | 		<NavigationMenuPrimitive.Viewport
111 | 			className={cn(
112 | 				"origin-top-center relative mt-1.5 h-(--radix-navigation-menu-viewport-height) w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-(--radix-navigation-menu-viewport-width)",
113 | 				className,
114 | 			)}
115 | 			ref={ref}
116 | 			{...props}
117 | 		/>
118 | 	</div>
119 | );
120 | NavigationMenuViewport.displayName =
121 | 	NavigationMenuPrimitive.Viewport.displayName;
122 | 
123 | const NavigationMenuIndicator = ({
124 | 	ref,
125 | 	className,
126 | 	...props
127 | }: React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator> & {
128 | 	ref: React.RefObject<
129 | 		React.ElementRef<typeof NavigationMenuPrimitive.Indicator>
130 | 	>;
131 | }) => (
132 | 	<NavigationMenuPrimitive.Indicator
133 | 		ref={ref}
134 | 		className={cn(
135 | 			"top-full z-1 flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
136 | 			className,
137 | 		)}
138 | 		{...props}
139 | 	>
140 | 		<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
141 | 	</NavigationMenuPrimitive.Indicator>
142 | );
143 | NavigationMenuIndicator.displayName =
144 | 	NavigationMenuPrimitive.Indicator.displayName;
145 | 
146 | export {
147 | 	navigationMenuTriggerStyle,
148 | 	NavigationMenu,
149 | 	NavigationMenuList,
150 | 	NavigationMenuItem,
151 | 	NavigationMenuContent,
152 | 	NavigationMenuTrigger,
153 | 	NavigationMenuLink,
154 | 	NavigationMenuIndicator,
155 | 	NavigationMenuViewport,
156 | };
157 | 
```

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

```typescript
  1 | import { Command } from "commander";
  2 | import * as z from "zod/v4";
  3 | import { existsSync } from "fs";
  4 | import path from "path";
  5 | import yoctoSpinner from "yocto-spinner";
  6 | import chalk from "chalk";
  7 | import prompts from "prompts";
  8 | import { logger, createTelemetry, getTelemetryAuthConfig } from "better-auth";
  9 | import { getAdapter, getMigrations } from "better-auth/db";
 10 | import { getConfig } from "../utils/get-config";
 11 | 
 12 | export async function migrateAction(opts: any) {
 13 | 	const options = z
 14 | 		.object({
 15 | 			cwd: z.string(),
 16 | 			config: z.string().optional(),
 17 | 			y: z.boolean().optional(),
 18 | 			yes: z.boolean().optional(),
 19 | 		})
 20 | 		.parse(opts);
 21 | 
 22 | 	const cwd = path.resolve(options.cwd);
 23 | 	if (!existsSync(cwd)) {
 24 | 		logger.error(`The directory "${cwd}" does not exist.`);
 25 | 		process.exit(1);
 26 | 	}
 27 | 
 28 | 	const config = await getConfig({
 29 | 		cwd,
 30 | 		configPath: options.config,
 31 | 	});
 32 | 	if (!config) {
 33 | 		logger.error(
 34 | 			"No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag.",
 35 | 		);
 36 | 		return;
 37 | 	}
 38 | 
 39 | 	const db = await getAdapter(config);
 40 | 
 41 | 	if (!db) {
 42 | 		logger.error(
 43 | 			"Invalid database configuration. Make sure you're not using adapters. Migrate command only works with built-in Kysely adapter.",
 44 | 		);
 45 | 		process.exit(1);
 46 | 	}
 47 | 
 48 | 	if (db.id !== "kysely") {
 49 | 		if (db.id === "prisma") {
 50 | 			logger.error(
 51 | 				"The migrate command only works with the built-in Kysely adapter. For Prisma, run `npx @better-auth/cli generate` to create the schema, then use Prisma’s migrate or push to apply it.",
 52 | 			);
 53 | 			try {
 54 | 				const telemetry = await createTelemetry(config);
 55 | 				await telemetry.publish({
 56 | 					type: "cli_migrate",
 57 | 					payload: {
 58 | 						outcome: "unsupported_adapter",
 59 | 						adapter: "prisma",
 60 | 						config: getTelemetryAuthConfig(config),
 61 | 					},
 62 | 				});
 63 | 			} catch {}
 64 | 			process.exit(0);
 65 | 		}
 66 | 		if (db.id === "drizzle") {
 67 | 			logger.error(
 68 | 				"The migrate command only works with the built-in Kysely adapter. For Drizzle, run `npx @better-auth/cli generate` to create the schema, then use Drizzle’s migrate or push to apply it.",
 69 | 			);
 70 | 			try {
 71 | 				const telemetry = await createTelemetry(config);
 72 | 				await telemetry.publish({
 73 | 					type: "cli_migrate",
 74 | 					payload: {
 75 | 						outcome: "unsupported_adapter",
 76 | 						adapter: "drizzle",
 77 | 						config: getTelemetryAuthConfig(config),
 78 | 					},
 79 | 				});
 80 | 			} catch {}
 81 | 			process.exit(0);
 82 | 		}
 83 | 		logger.error("Migrate command isn't supported for this adapter.");
 84 | 		try {
 85 | 			const telemetry = await createTelemetry(config);
 86 | 			await telemetry.publish({
 87 | 				type: "cli_migrate",
 88 | 				payload: {
 89 | 					outcome: "unsupported_adapter",
 90 | 					adapter: db.id,
 91 | 					config: getTelemetryAuthConfig(config),
 92 | 				},
 93 | 			});
 94 | 		} catch {}
 95 | 		process.exit(1);
 96 | 	}
 97 | 
 98 | 	const spinner = yoctoSpinner({ text: "preparing migration..." }).start();
 99 | 
100 | 	const { toBeAdded, toBeCreated, runMigrations } = await getMigrations(config);
101 | 
102 | 	if (!toBeAdded.length && !toBeCreated.length) {
103 | 		spinner.stop();
104 | 		logger.info("🚀 No migrations needed.");
105 | 		try {
106 | 			const telemetry = await createTelemetry(config);
107 | 			await telemetry.publish({
108 | 				type: "cli_migrate",
109 | 				payload: {
110 | 					outcome: "no_changes",
111 | 					config: getTelemetryAuthConfig(config),
112 | 				},
113 | 			});
114 | 		} catch {}
115 | 		process.exit(0);
116 | 	}
117 | 
118 | 	spinner.stop();
119 | 	logger.info(`🔑 The migration will affect the following:`);
120 | 
121 | 	for (const table of [...toBeCreated, ...toBeAdded]) {
122 | 		console.log(
123 | 			"->",
124 | 			chalk.magenta(Object.keys(table.fields).join(", ")),
125 | 			chalk.white("fields on"),
126 | 			chalk.yellow(`${table.table}`),
127 | 			chalk.white("table."),
128 | 		);
129 | 	}
130 | 
131 | 	if (options.y) {
132 | 		console.warn("WARNING: --y is deprecated. Consider -y or --yes");
133 | 		options.yes = true;
134 | 	}
135 | 
136 | 	let migrate = options.yes;
137 | 	if (!migrate) {
138 | 		const response = await prompts({
139 | 			type: "confirm",
140 | 			name: "migrate",
141 | 			message: "Are you sure you want to run these migrations?",
142 | 			initial: false,
143 | 		});
144 | 		migrate = response.migrate;
145 | 	}
146 | 
147 | 	if (!migrate) {
148 | 		logger.info("Migration cancelled.");
149 | 		try {
150 | 			const telemetry = await createTelemetry(config);
151 | 			await telemetry.publish({
152 | 				type: "cli_migrate",
153 | 				payload: { outcome: "aborted", config: getTelemetryAuthConfig(config) },
154 | 			});
155 | 		} catch {}
156 | 		process.exit(0);
157 | 	}
158 | 
159 | 	spinner?.start("migrating...");
160 | 	await runMigrations();
161 | 	spinner.stop();
162 | 	logger.info("🚀 migration was completed successfully!");
163 | 	try {
164 | 		const telemetry = await createTelemetry(config);
165 | 		await telemetry.publish({
166 | 			type: "cli_migrate",
167 | 			payload: { outcome: "migrated", config: getTelemetryAuthConfig(config) },
168 | 		});
169 | 	} catch {}
170 | 	process.exit(0);
171 | }
172 | 
173 | export const migrate = new Command("migrate")
174 | 	.option(
175 | 		"-c, --cwd <cwd>",
176 | 		"the working directory. defaults to the current directory.",
177 | 		process.cwd(),
178 | 	)
179 | 	.option(
180 | 		"--config <config>",
181 | 		"the path to the configuration file. defaults to the first configuration file found.",
182 | 	)
183 | 	.option(
184 | 		"-y, --yes",
185 | 		"automatically accept and run migrations without prompting",
186 | 		false,
187 | 	)
188 | 	.option("--y", "(deprecated) same as --yes", false)
189 | 	.action(migrateAction);
190 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/app/accept-invitation/[id]/page.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import { Button } from "@/components/ui/button";
  4 | import {
  5 | 	Card,
  6 | 	CardContent,
  7 | 	CardDescription,
  8 | 	CardFooter,
  9 | 	CardHeader,
 10 | 	CardTitle,
 11 | } from "@/components/ui/card";
 12 | import { CheckIcon, XIcon } from "lucide-react";
 13 | import { useEffect, useState } from "react";
 14 | import { useParams, useRouter } from "next/navigation";
 15 | import { Skeleton } from "@/components/ui/skeleton";
 16 | import { client, organization } from "@/lib/auth-client";
 17 | import { InvitationError } from "./invitation-error";
 18 | 
 19 | export default function InvitationPage() {
 20 | 	const params = useParams<{
 21 | 		id: string;
 22 | 	}>();
 23 | 	const router = useRouter();
 24 | 	const [invitationStatus, setInvitationStatus] = useState<
 25 | 		"pending" | "accepted" | "rejected"
 26 | 	>("pending");
 27 | 
 28 | 	const handleAccept = async () => {
 29 | 		await organization
 30 | 			.acceptInvitation({
 31 | 				invitationId: params.id,
 32 | 			})
 33 | 			.then((res) => {
 34 | 				if (res.error) {
 35 | 					setError(res.error.message || "An error occurred");
 36 | 				} else {
 37 | 					setInvitationStatus("accepted");
 38 | 					router.push(`/dashboard`);
 39 | 				}
 40 | 			});
 41 | 	};
 42 | 
 43 | 	const handleReject = async () => {
 44 | 		await organization
 45 | 			.rejectInvitation({
 46 | 				invitationId: params.id,
 47 | 			})
 48 | 			.then((res) => {
 49 | 				if (res.error) {
 50 | 					setError(res.error.message || "An error occurred");
 51 | 				} else {
 52 | 					setInvitationStatus("rejected");
 53 | 				}
 54 | 			});
 55 | 	};
 56 | 
 57 | 	const [invitation, setInvitation] = useState<{
 58 | 		organizationName: string;
 59 | 		organizationSlug: string;
 60 | 		inviterEmail: string;
 61 | 		id: string;
 62 | 		status: "pending" | "accepted" | "rejected" | "canceled";
 63 | 		email: string;
 64 | 		expiresAt: Date;
 65 | 		organizationId: string;
 66 | 		role: string;
 67 | 		inviterId: string;
 68 | 	} | null>(null);
 69 | 
 70 | 	const [error, setError] = useState<string | null>(null);
 71 | 
 72 | 	useEffect(() => {
 73 | 		client.organization
 74 | 			.getInvitation({
 75 | 				query: {
 76 | 					id: params.id,
 77 | 				},
 78 | 			})
 79 | 			.then((res) => {
 80 | 				if (res.error) {
 81 | 					setError(res.error.message || "An error occurred");
 82 | 				} else {
 83 | 					setInvitation(res.data);
 84 | 				}
 85 | 			});
 86 | 	}, []);
 87 | 
 88 | 	return (
 89 | 		<div className="min-h-[80vh] flex items-center justify-center">
 90 | 			<div className="absolute pointer-events-none inset-0 flex items-center justify-center dark:bg-black bg-white mask-[radial-gradient(ellipse_at_center,transparent_20%,black)]"></div>
 91 | 			{invitation ? (
 92 | 				<Card className="w-full max-w-md">
 93 | 					<CardHeader>
 94 | 						<CardTitle>Organization Invitation</CardTitle>
 95 | 						<CardDescription>
 96 | 							You've been invited to join an organization
 97 | 						</CardDescription>
 98 | 					</CardHeader>
 99 | 					<CardContent>
100 | 						{invitationStatus === "pending" && (
101 | 							<div className="space-y-4">
102 | 								<p>
103 | 									<strong>{invitation?.inviterEmail}</strong> has invited you to
104 | 									join <strong>{invitation?.organizationName}</strong>.
105 | 								</p>
106 | 								<p>
107 | 									This invitation was sent to{" "}
108 | 									<strong>{invitation?.email}</strong>.
109 | 								</p>
110 | 							</div>
111 | 						)}
112 | 						{invitationStatus === "accepted" && (
113 | 							<div className="space-y-4">
114 | 								<div className="flex items-center justify-center w-16 h-16 mx-auto bg-green-100 rounded-full">
115 | 									<CheckIcon className="w-8 h-8 text-green-600" />
116 | 								</div>
117 | 								<h2 className="text-2xl font-bold text-center">
118 | 									Welcome to {invitation?.organizationName}!
119 | 								</h2>
120 | 								<p className="text-center">
121 | 									You've successfully joined the organization. We're excited to
122 | 									have you on board!
123 | 								</p>
124 | 							</div>
125 | 						)}
126 | 						{invitationStatus === "rejected" && (
127 | 							<div className="space-y-4">
128 | 								<div className="flex items-center justify-center w-16 h-16 mx-auto bg-red-100 rounded-full">
129 | 									<XIcon className="w-8 h-8 text-red-600" />
130 | 								</div>
131 | 								<h2 className="text-2xl font-bold text-center">
132 | 									Invitation Declined
133 | 								</h2>
134 | 								<p className="text-center">
135 | 									You&lsquo;ve declined the invitation to join{" "}
136 | 									{invitation?.organizationName}.
137 | 								</p>
138 | 							</div>
139 | 						)}
140 | 					</CardContent>
141 | 					{invitationStatus === "pending" && (
142 | 						<CardFooter className="flex justify-between">
143 | 							<Button variant="outline" onClick={handleReject}>
144 | 								Decline
145 | 							</Button>
146 | 							<Button onClick={handleAccept}>Accept Invitation</Button>
147 | 						</CardFooter>
148 | 					)}
149 | 				</Card>
150 | 			) : error ? (
151 | 				<InvitationError />
152 | 			) : (
153 | 				<InvitationSkeleton />
154 | 			)}
155 | 		</div>
156 | 	);
157 | }
158 | 
159 | function InvitationSkeleton() {
160 | 	return (
161 | 		<Card className="w-full max-w-md mx-auto">
162 | 			<CardHeader>
163 | 				<div className="flex items-center space-x-2">
164 | 					<Skeleton className="w-6 h-6 rounded-full" />
165 | 					<Skeleton className="h-6 w-24" />
166 | 				</div>
167 | 				<Skeleton className="h-4 w-full mt-2" />
168 | 				<Skeleton className="h-4 w-2/3 mt-2" />
169 | 			</CardHeader>
170 | 			<CardContent>
171 | 				<div className="space-y-2">
172 | 					<Skeleton className="h-4 w-full" />
173 | 					<Skeleton className="h-4 w-full" />
174 | 					<Skeleton className="h-4 w-2/3" />
175 | 				</div>
176 | 			</CardContent>
177 | 			<CardFooter className="flex justify-end">
178 | 				<Skeleton className="h-10 w-24" />
179 | 			</CardFooter>
180 | 		</Card>
181 | 	);
182 | }
183 | 
```

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

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  3 | import {
  4 | 	createAuthorizationURL,
  5 | 	validateAuthorizationCode,
  6 | 	refreshAccessToken,
  7 | } from "../oauth2";
  8 | 
  9 | interface Partner {
 10 | 	/** Partner-specific ID (consent required: kakaotalk_message) */
 11 | 	uuid?: string;
 12 | }
 13 | 
 14 | interface Profile {
 15 | 	/** Nickname (consent required: profile/nickname) */
 16 | 	nickname?: string;
 17 | 	/** Thumbnail image URL (consent required: profile/profile image) */
 18 | 	thumbnail_image_url?: string;
 19 | 	/** Profile image URL (consent required: profile/profile image) */
 20 | 	profile_image_url?: string;
 21 | 	/** Whether the profile image is the default */
 22 | 	is_default_image?: boolean;
 23 | 	/** Whether the nickname is the default */
 24 | 	is_default_nickname?: boolean;
 25 | }
 26 | 
 27 | interface KakaoAccount {
 28 | 	/** Consent required: profile info (nickname/profile image) */
 29 | 	profile_needs_agreement?: boolean;
 30 | 	/** Consent required: nickname */
 31 | 	profile_nickname_needs_agreement?: boolean;
 32 | 	/** Consent required: profile image */
 33 | 	profile_image_needs_agreement?: boolean;
 34 | 	/** Profile info */
 35 | 	profile?: Profile;
 36 | 	/** Consent required: name */
 37 | 	name_needs_agreement?: boolean;
 38 | 	/** Name */
 39 | 	name?: string;
 40 | 	/** Consent required: email */
 41 | 	email_needs_agreement?: boolean;
 42 | 	/** Email valid */
 43 | 	is_email_valid?: boolean;
 44 | 	/** Email verified */
 45 | 	is_email_verified?: boolean;
 46 | 	/** Email */
 47 | 	email?: string;
 48 | 	/** Consent required: age range */
 49 | 	age_range_needs_agreement?: boolean;
 50 | 	/** Age range */
 51 | 	age_range?: string;
 52 | 	/** Consent required: birth year */
 53 | 	birthyear_needs_agreement?: boolean;
 54 | 	/** Birth year (YYYY) */
 55 | 	birthyear?: string;
 56 | 	/** Consent required: birthday */
 57 | 	birthday_needs_agreement?: boolean;
 58 | 	/** Birthday (MMDD) */
 59 | 	birthday?: string;
 60 | 	/** Birthday type (SOLAR/LUNAR) */
 61 | 	birthday_type?: string;
 62 | 	/** Whether birthday is in a leap month */
 63 | 	is_leap_month?: boolean;
 64 | 	/** Consent required: gender */
 65 | 	gender_needs_agreement?: boolean;
 66 | 	/** Gender (male/female) */
 67 | 	gender?: string;
 68 | 	/** Consent required: phone number */
 69 | 	phone_number_needs_agreement?: boolean;
 70 | 	/** Phone number */
 71 | 	phone_number?: string;
 72 | 	/** Consent required: CI */
 73 | 	ci_needs_agreement?: boolean;
 74 | 	/** CI (unique identifier) */
 75 | 	ci?: string;
 76 | 	/** CI authentication time (UTC) */
 77 | 	ci_authenticated_at?: string;
 78 | }
 79 | 
 80 | export interface KakaoProfile {
 81 | 	/** Kakao user ID */
 82 | 	id: number;
 83 | 	/**
 84 | 	 * Whether the user has signed up (only present if auto-connection is disabled)
 85 | 	 * false: preregistered, true: registered
 86 | 	 */
 87 | 	has_signed_up?: boolean;
 88 | 	/** UTC datetime when the user connected the service */
 89 | 	connected_at?: string;
 90 | 	/** UTC datetime when the user signed up via Kakao Sync */
 91 | 	synched_at?: string;
 92 | 	/** Custom user properties */
 93 | 	properties?: Record<string, any>;
 94 | 	/** Kakao account info */
 95 | 	kakao_account: KakaoAccount;
 96 | 	/** Partner info */
 97 | 	for_partner?: Partner;
 98 | }
 99 | 
100 | export interface KakaoOptions extends ProviderOptions<KakaoProfile> {
101 | 	clientId: string;
102 | }
103 | 
104 | export const kakao = (options: KakaoOptions) => {
105 | 	return {
106 | 		id: "kakao",
107 | 		name: "Kakao",
108 | 		createAuthorizationURL({ state, scopes, redirectURI }) {
109 | 			const _scopes = options.disableDefaultScope
110 | 				? []
111 | 				: ["account_email", "profile_image", "profile_nickname"];
112 | 			options.scope && _scopes.push(...options.scope);
113 | 			scopes && _scopes.push(...scopes);
114 | 			return createAuthorizationURL({
115 | 				id: "kakao",
116 | 				options,
117 | 				authorizationEndpoint: "https://kauth.kakao.com/oauth/authorize",
118 | 				scopes: _scopes,
119 | 				state,
120 | 				redirectURI,
121 | 			});
122 | 		},
123 | 		validateAuthorizationCode: async ({ code, redirectURI }) => {
124 | 			return validateAuthorizationCode({
125 | 				code,
126 | 				redirectURI,
127 | 				options,
128 | 				tokenEndpoint: "https://kauth.kakao.com/oauth/token",
129 | 			});
130 | 		},
131 | 		refreshAccessToken: options.refreshAccessToken
132 | 			? options.refreshAccessToken
133 | 			: async (refreshToken) => {
134 | 					return refreshAccessToken({
135 | 						refreshToken,
136 | 						options: {
137 | 							clientId: options.clientId,
138 | 							clientKey: options.clientKey,
139 | 							clientSecret: options.clientSecret,
140 | 						},
141 | 						tokenEndpoint: "https://kauth.kakao.com/oauth/token",
142 | 					});
143 | 				},
144 | 		async getUserInfo(token) {
145 | 			if (options.getUserInfo) {
146 | 				return options.getUserInfo(token);
147 | 			}
148 | 			const { data: profile, error } = await betterFetch<KakaoProfile>(
149 | 				"https://kapi.kakao.com/v2/user/me",
150 | 				{
151 | 					headers: {
152 | 						Authorization: `Bearer ${token.accessToken}`,
153 | 					},
154 | 				},
155 | 			);
156 | 			if (error || !profile) {
157 | 				return null;
158 | 			}
159 | 			const userMap = await options.mapProfileToUser?.(profile);
160 | 			const account = profile.kakao_account || {};
161 | 			const kakaoProfile = account.profile || {};
162 | 			const user = {
163 | 				id: String(profile.id),
164 | 				name: kakaoProfile.nickname || account.name || undefined,
165 | 				email: account.email,
166 | 				image:
167 | 					kakaoProfile.profile_image_url || kakaoProfile.thumbnail_image_url,
168 | 				emailVerified: !!account.is_email_valid && !!account.is_email_verified,
169 | 				...userMap,
170 | 			};
171 | 			return {
172 | 				user,
173 | 				data: profile,
174 | 			};
175 | 		},
176 | 		options,
177 | 	} satisfies OAuthProvider<KakaoProfile>;
178 | };
179 | 
```

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

```typescript
  1 | "use client";
  2 | 
  3 | import {
  4 | 	Globe2Icon,
  5 | 	PlugIcon,
  6 | 	PlugZap2Icon,
  7 | 	Plus,
  8 | 	RabbitIcon,
  9 | 	ShieldCheckIcon,
 10 | 	Webhook,
 11 | } from "lucide-react";
 12 | import { LockClosedIcon } from "@radix-ui/react-icons";
 13 | 
 14 | import { TechStackDisplay } from "./display-techstack";
 15 | import { Ripple } from "./ripple";
 16 | import { GithubStat } from "./github-stat";
 17 | import { cn } from "@/lib/utils";
 18 | import { Testimonial } from "./landing/testimonials";
 19 | const features = [
 20 | 	{
 21 | 		id: 1,
 22 | 		label: "Framework Agnostic",
 23 | 		title: "Support for popular <strong>frameworks</strong>.",
 24 | 		description:
 25 | 			"Supports popular frameworks, including React, Vue, Svelte, Astro, Solid, Next.js, Nuxt, Tanstack Start, Hono, and more.",
 26 | 		icon: PlugZap2Icon,
 27 | 	},
 28 | 	{
 29 | 		id: 2,
 30 | 		label: "Authentication",
 31 | 		title: "Email & Password <strong>Authentication</strong>.",
 32 | 		description:
 33 | 			"Built-in support for email and password authentication, with session and account management features.",
 34 | 		icon: LockClosedIcon,
 35 | 	},
 36 | 	{
 37 | 		id: 3,
 38 | 		label: "Social Sign-on",
 39 | 		title: "Support multiple <strong>OAuth providers</strong>.",
 40 | 		description:
 41 | 			"Allow users to sign in with their accounts, including GitHub, Google, Discord, Twitter, and more.",
 42 | 		icon: Webhook,
 43 | 	},
 44 | 	{
 45 | 		id: 4,
 46 | 		label: "Two Factor",
 47 | 		title: "Multi Factor <strong>Authentication</strong>.",
 48 | 		description:
 49 | 			"Secure your users accounts with two factor authentication with a few lines of code.",
 50 | 		icon: ShieldCheckIcon,
 51 | 	},
 52 | 	{
 53 | 		id: 5,
 54 | 		label: "Multi Tenant",
 55 | 		title: "<strong>Organization</strong> Members and Invitation.",
 56 | 		description:
 57 | 			"Multi tenant support with members, organization, teams and invitation with access control.",
 58 | 
 59 | 		icon: RabbitIcon,
 60 | 	},
 61 | 
 62 | 	{
 63 | 		id: 6,
 64 | 		label: "Plugin Ecosystem",
 65 | 		title: "A lot more features with <strong>plugins</strong>.",
 66 | 		description:
 67 | 			"Improve your application experience with our official plugins and those created by the community.",
 68 | 		icon: PlugIcon,
 69 | 	},
 70 | ];
 71 | 
 72 | export default function Features({ stars }: { stars: string | null }) {
 73 | 	return (
 74 | 		<div className="md:w-10/12 mt-10 mx-auto font-geist relative md:border-l-0 md:border-b-0 md:border-[1.2px] rounded-none -pr-2 dark:bg-black/[0.95] ">
 75 | 			<div className="w-full md:mx-0">
 76 | 				<div className="grid grid-cols-1 relative md:grid-rows-2 md:grid-cols-3 border-b-[1.2px]">
 77 | 					<div className="hidden md:grid top-1/2 left-0 -translate-y-1/2 w-full grid-cols-3 z-10 pointer-events-none select-none absolute">
 78 | 						<Plus className="w-8 h-8 text-neutral-300 translate-x-[16.5px] translate-y-[.5px] ml-auto dark:text-neutral-600" />
 79 | 						<Plus className="w-8 h-8 text-neutral-300 ml-auto translate-x-[16.5px] translate-y-[.5px] dark:text-neutral-600" />
 80 | 					</div>
 81 | 					{features.map((feature, index) => (
 82 | 						<div
 83 | 							key={feature.id}
 84 | 							className={cn(
 85 | 								"justify-center border-l-[1.2px] md:min-h-[240px] border-t-[1.2px] md:border-t-0 transform-gpu flex flex-col p-10 2xl:p-12",
 86 | 								index >= 3 && "md:border-t-[1.2px]",
 87 | 							)}
 88 | 						>
 89 | 							<div className="flex items-center gap-2 my-1">
 90 | 								<feature.icon className="w-4 h-4" />
 91 | 								<p className="text-gray-600 dark:text-gray-400">
 92 | 									{feature.label}
 93 | 								</p>
 94 | 							</div>
 95 | 							<div className="mt-2">
 96 | 								<div className="max-w-full">
 97 | 									<div className="flex gap-3 ">
 98 | 										<p
 99 | 											className="max-w-lg text-xl font-normal tracking-tighter md:text-2xl"
100 | 											dangerouslySetInnerHTML={{
101 | 												__html: feature.title,
102 | 											}}
103 | 										/>
104 | 									</div>
105 | 								</div>
106 | 								<p className="mt-2 text-sm text-left text-muted-foreground">
107 | 									{feature.description}
108 | 									<a className="ml-2 underline" href="/docs" target="_blank">
109 | 										Learn more
110 | 									</a>
111 | 								</p>
112 | 							</div>
113 | 						</div>
114 | 					))}
115 | 				</div>
116 | 				<div className="w-full border-l-2 hidden md:block">
117 | 					<Testimonial />
118 | 				</div>
119 | 				<div className="relative col-span-3 border-t-[1.2px] border-l-[1.2px] md:border-b-[1.2px] dark:border-b-0  h-full py-20">
120 | 					<div className="w-full h-full p-16 pt-10 md:px-10 2xl:px-16">
121 | 						<div className="flex flex-col items-center justify-center w-full h-full gap-3">
122 | 							<div className="flex items-center gap-2">
123 | 								<Globe2Icon className="w-4 h-4" />
124 | 								<p className="text-gray-600 dark:text-gray-400">
125 | 									Own your auth
126 | 								</p>
127 | 							</div>
128 | 							<p className="max-w-md mx-auto mt-4 text-4xl font-normal tracking-tighter text-center md:text-4xl">
129 | 								<strong>Roll your own auth with confidence in minutes!</strong>
130 | 							</p>
131 | 							<div className="flex mt-[10px] z-20 justify-center items-start">
132 | 								<TechStackDisplay
133 | 									skills={[
134 | 										"nextJs",
135 | 										"nuxt",
136 | 										"svelteKit",
137 | 										"astro",
138 | 										"solidStart",
139 | 										// "react",
140 | 										// "hono",
141 | 										"expo",
142 | 										"tanstack",
143 | 									]}
144 | 								/>
145 | 							</div>
146 | 							<div className="flex items-center gap-2">
147 | 								<GithubStat stars={stars} />
148 | 							</div>
149 | 							<Ripple />
150 | 						</div>
151 | 					</div>
152 | 				</div>
153 | 			</div>
154 | 		</div>
155 | 	);
156 | }
157 | 
```

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

```typescript
  1 | import type {
  2 | 	DBFieldAttribute,
  3 | 	DBFieldAttributeConfig,
  4 | 	DBFieldType,
  5 | } from "@better-auth/core/db";
  6 | import type { BetterAuthOptions } from "@better-auth/core";
  7 | 
  8 | export const createFieldAttribute = <
  9 | 	T extends DBFieldType,
 10 | 	C extends DBFieldAttributeConfig,
 11 | >(
 12 | 	type: T,
 13 | 	config?: C,
 14 | ) => {
 15 | 	return {
 16 | 		type,
 17 | 		...config,
 18 | 	} satisfies DBFieldAttribute<T>;
 19 | };
 20 | 
 21 | export type InferValueType<T extends DBFieldType> = T extends "string"
 22 | 	? string
 23 | 	: T extends "number"
 24 | 		? number
 25 | 		: T extends "boolean"
 26 | 			? boolean
 27 | 			: T extends "date"
 28 | 				? Date
 29 | 				: T extends `${infer T}[]`
 30 | 					? T extends "string"
 31 | 						? string[]
 32 | 						: number[]
 33 | 					: T extends Array<any>
 34 | 						? T[number]
 35 | 						: never;
 36 | 
 37 | export type InferFieldsOutput<Field> = Field extends Record<
 38 | 	infer Key,
 39 | 	DBFieldAttribute
 40 | >
 41 | 	? {
 42 | 			[key in Key as Field[key]["required"] extends false
 43 | 				? Field[key]["defaultValue"] extends boolean | string | number | Date
 44 | 					? key
 45 | 					: never
 46 | 				: key]: InferFieldOutput<Field[key]>;
 47 | 		} & {
 48 | 			[key in Key as Field[key]["returned"] extends false
 49 | 				? never
 50 | 				: key]?: InferFieldOutput<Field[key]> | null;
 51 | 		}
 52 | 	: {};
 53 | 
 54 | export type InferFieldsInput<Field> = Field extends Record<
 55 | 	infer Key,
 56 | 	DBFieldAttribute
 57 | >
 58 | 	? {
 59 | 			[key in Key as Field[key]["required"] extends false
 60 | 				? never
 61 | 				: Field[key]["defaultValue"] extends string | number | boolean | Date
 62 | 					? never
 63 | 					: Field[key]["input"] extends false
 64 | 						? never
 65 | 						: key]: InferFieldInput<Field[key]>;
 66 | 		} & {
 67 | 			[key in Key as Field[key]["input"] extends false ? never : key]?:
 68 | 				| InferFieldInput<Field[key]>
 69 | 				| undefined
 70 | 				| null;
 71 | 		}
 72 | 	: {};
 73 | 
 74 | /**
 75 |  * For client will add "?" on optional fields
 76 |  */
 77 | export type InferFieldsInputClient<Field> = Field extends Record<
 78 | 	infer Key,
 79 | 	DBFieldAttribute
 80 | >
 81 | 	? {
 82 | 			[key in Key as Field[key]["required"] extends false
 83 | 				? never
 84 | 				: Field[key]["defaultValue"] extends string | number | boolean | Date
 85 | 					? never
 86 | 					: Field[key]["input"] extends false
 87 | 						? never
 88 | 						: key]: InferFieldInput<Field[key]>;
 89 | 		} & {
 90 | 			[key in Key as Field[key]["input"] extends false
 91 | 				? never
 92 | 				: Field[key]["required"] extends false
 93 | 					? key
 94 | 					: Field[key]["defaultValue"] extends string | number | boolean | Date
 95 | 						? key
 96 | 						: never]?: InferFieldInput<Field[key]> | undefined | null;
 97 | 		}
 98 | 	: {};
 99 | 
100 | type InferFieldOutput<T extends DBFieldAttribute> = T["returned"] extends false
101 | 	? never
102 | 	: T["required"] extends false
103 | 		? InferValueType<T["type"]> | undefined | null
104 | 		: InferValueType<T["type"]>;
105 | 
106 | /**
107 |  * Converts a Record<string, DBFieldAttribute> to an object type
108 |  * with keys and value types inferred from DBFieldAttribute["type"].
109 |  */
110 | export type FieldAttributeToObject<
111 | 	Fields extends Record<string, DBFieldAttribute>,
112 | > = AddOptionalFields<
113 | 	{
114 | 		[K in keyof Fields]: InferValueType<Fields[K]["type"]>;
115 | 	},
116 | 	Fields
117 | >;
118 | 
119 | type AddOptionalFields<
120 | 	T extends Record<string, any>,
121 | 	Fields extends Record<keyof T, DBFieldAttribute>,
122 | > = {
123 | 	// Required fields: required === true
124 | 	[K in keyof T as Fields[K] extends { required: true } ? K : never]: T[K];
125 | } & {
126 | 	// Optional fields: required !== true
127 | 	[K in keyof T as Fields[K] extends { required: true } ? never : K]?: T[K];
128 | };
129 | 
130 | /**
131 |  * Infer the additional fields from the plugin options.
132 |  * For example, you can infer the additional fields of the org plugin's organization schema like this:
133 |  * ```ts
134 |  * type AdditionalFields = InferAdditionalFieldsFromPluginOptions<"organization", OrganizationOptions>
135 |  * ```
136 |  */
137 | export type InferAdditionalFieldsFromPluginOptions<
138 | 	SchemaName extends string,
139 | 	Options extends {
140 | 		schema?: {
141 | 			[key in SchemaName]?: {
142 | 				additionalFields?: Record<string, DBFieldAttribute>;
143 | 			};
144 | 		};
145 | 	},
146 | 	isClientSide extends boolean = true,
147 | > = Options["schema"] extends {
148 | 	[key in SchemaName]?: {
149 | 		additionalFields: infer Field extends Record<string, DBFieldAttribute>;
150 | 	};
151 | }
152 | 	? isClientSide extends true
153 | 		? FieldAttributeToObject<RemoveFieldsWithInputFalse<Field>>
154 | 		: FieldAttributeToObject<Field>
155 | 	: {};
156 | 
157 | type RemoveFieldsWithInputFalse<T extends Record<string, DBFieldAttribute>> = {
158 | 	[K in keyof T as T[K]["input"] extends false ? never : K]: T[K];
159 | };
160 | 
161 | type InferFieldInput<T extends DBFieldAttribute> = InferValueType<T["type"]>;
162 | 
163 | export type PluginFieldAttribute = Omit<
164 | 	DBFieldAttribute,
165 | 	"transform" | "defaultValue" | "hashValue"
166 | >;
167 | 
168 | export type InferFieldsFromPlugins<
169 | 	Options extends BetterAuthOptions,
170 | 	Key extends string,
171 | 	Format extends "output" | "input" = "output",
172 | > = Options["plugins"] extends []
173 | 	? {}
174 | 	: Options["plugins"] extends Array<infer T>
175 | 		? T extends {
176 | 				schema: {
177 | 					[key in Key]: {
178 | 						fields: infer Field;
179 | 					};
180 | 				};
181 | 			}
182 | 			? Format extends "output"
183 | 				? InferFieldsOutput<Field>
184 | 				: InferFieldsInput<Field>
185 | 			: {}
186 | 		: {};
187 | 
188 | export type InferFieldsFromOptions<
189 | 	Options extends BetterAuthOptions,
190 | 	Key extends "session" | "user",
191 | 	Format extends "output" | "input" = "output",
192 | > = Options[Key] extends {
193 | 	additionalFields: infer Field;
194 | }
195 | 	? Format extends "output"
196 | 		? InferFieldsOutput<Field>
197 | 		: InferFieldsInput<Field>
198 | 	: {};
199 | 
```

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

```markdown
  1 | ---
  2 | title: Hono Integration
  3 | description: Integrate Better Auth with Hono.
  4 | ---
  5 | 
  6 | Before you start, make sure you have a Better Auth instance configured. If you haven't done that yet, check out the [installation](/docs/installation).
  7 | 
  8 | ### Mount the handler
  9 | 
 10 | We need to mount the handler to Hono endpoint.
 11 | 
 12 | ```ts
 13 | import { Hono } from "hono";
 14 | import { auth } from "./auth";
 15 | import { serve } from "@hono/node-server";
 16 | 
 17 | const app = new Hono();
 18 | 
 19 | app.on(["POST", "GET"], "/api/auth/*", (c) => {
 20 | 	return auth.handler(c.req.raw);
 21 | });
 22 | 
 23 | serve(app);
 24 | ```
 25 | 
 26 | ### Cors
 27 | 
 28 | To configure cors, you need to use the `cors` plugin from `hono/cors`.
 29 | 
 30 | ```ts
 31 | import { Hono } from "hono";
 32 | import { auth } from "./auth";
 33 | import { serve } from "@hono/node-server";
 34 | import { cors } from "hono/cors";
 35 |  
 36 | const app = new Hono();
 37 | 
 38 | app.use(
 39 | 	"/api/auth/*", // or replace with "*" to enable cors for all routes
 40 | 	cors({
 41 | 		origin: "http://localhost:3001", // replace with your origin
 42 | 		allowHeaders: ["Content-Type", "Authorization"],
 43 | 		allowMethods: ["POST", "GET", "OPTIONS"],
 44 | 		exposeHeaders: ["Content-Length"],
 45 | 		maxAge: 600,
 46 | 		credentials: true,
 47 | 	}),
 48 | );
 49 | 
 50 | app.on(["POST", "GET"], "/api/auth/*", (c) => {
 51 | 	return auth.handler(c.req.raw);
 52 | });
 53 | 
 54 | serve(app);
 55 | ```
 56 | 
 57 | > **Important:** CORS middleware must be registered before your routes. This ensures that cross-origin requests are properly handled before they reach your authentication endpoints.
 58 | 
 59 | ### Middleware
 60 | 
 61 | You can add a middleware to save the `session` and `user` in a `context` and also add validations for every route.
 62 | 
 63 | ```ts
 64 | import { Hono } from "hono";
 65 | import { auth } from "./auth";
 66 | import { serve } from "@hono/node-server";
 67 | import { cors } from "hono/cors";
 68 |  
 69 | const app = new Hono<{
 70 | 	Variables: {
 71 | 		user: typeof auth.$Infer.Session.user | null;
 72 | 		session: typeof auth.$Infer.Session.session | null
 73 | 	}
 74 | }>();
 75 | 
 76 | app.use("*", async (c, next) => {
 77 | 	const session = await auth.api.getSession({ headers: c.req.raw.headers });
 78 | 
 79 |   	if (!session) {
 80 |     	c.set("user", null);
 81 |     	c.set("session", null);
 82 |     	return next();
 83 |   	}
 84 | 
 85 |   	c.set("user", session.user);
 86 |   	c.set("session", session.session);
 87 |   	return next();
 88 | });
 89 | 
 90 | app.on(["POST", "GET"], "/api/auth/*", (c) => {
 91 | 	return auth.handler(c.req.raw);
 92 | });
 93 | 
 94 | 
 95 | serve(app);
 96 | ```
 97 | 
 98 | This will allow you to access the `user` and `session` object in all of your routes.
 99 | 
100 | ```ts
101 | app.get("/session", (c) => {
102 | 	const session = c.get("session")
103 | 	const user = c.get("user")
104 | 	
105 | 	if(!user) return c.body(null, 401);
106 | 
107 |   	return c.json({
108 | 	  session,
109 | 	  user
110 | 	});
111 | });
112 | ```
113 | 
114 | ### Cross-Domain Cookies
115 | 
116 | By default, all Better Auth cookies are set with `SameSite=Lax`. If you need to use cookies across different domains, you’ll need to set `SameSite=None` and `Secure=true`. However, we recommend using subdomains whenever possible, as this allows you to keep `SameSite=Lax`. To enable cross-subdomain cookies, simply turn on `crossSubDomainCookies` in your auth config.
117 | 
118 | ```ts title="auth.ts"
119 | export const auth = createAuth({
120 |   advanced: {
121 |     crossSubDomainCookies: {
122 |       enabled: true
123 |     }
124 |   }
125 | })
126 | ```
127 | 
128 | If you still need to set `SameSite=None` and `Secure=true`, you can adjust these attributes globally through `cookieOptions` in the `createAuth` configuration.
129 | 
130 | ```ts title="auth.ts"
131 | export const auth = createAuth({
132 |   advanced: {
133 |     defaultCookieAttributes: {
134 |       sameSite: "none",
135 |       secure: true,
136 |       partitioned: true // New browser standards will mandate this for foreign cookies
137 |     }
138 |   }
139 | })
140 | ```
141 | 
142 | You can also customize cookie attributes individually by setting them within `cookies` in your auth config.
143 | 
144 | ```ts title="auth.ts"
145 | export const auth = createAuth({
146 |   advanced: {
147 |     cookies: {
148 |       sessionToken: {
149 |         attributes: {
150 |           sameSite: "none",
151 |           secure: true,
152 |           partitioned: true // New browser standards will mandate this for foreign cookies
153 |         }
154 |       }
155 |     }
156 |   }
157 | })
158 | ```
159 | 
160 | ### Client-Side Configuration
161 | 
162 | When using the Hono client (`@hono/client`) to make requests to your Better Auth-protected endpoints, you need to configure it to send credentials (cookies) with cross-origin requests.
163 | 
164 | ```ts title="api.ts"
165 | import { hc } from "hono/client";
166 | import type { AppType } from "./server"; // Your Hono app type
167 | 
168 | const client = hc<AppType>("http://localhost:8787/", {
169 |   init: {
170 |     credentials: "include", // Required for sending cookies cross-origin
171 |   },
172 | });
173 | 
174 | // Now your client requests will include credentials
175 | const response = await client.someProtectedEndpoint.$get();
176 | ```
177 | 
178 | This configuration is necessary when:
179 | - Your client and server are on different domains/ports during development
180 | - You're making cross-origin requests in production
181 | - You need to send authentication cookies with your requests
182 | 
183 | The `credentials: "include"` option tells the fetch client to send cookies even for cross-origin requests. This works in conjunction with the CORS configuration on your server that has `credentials: true`.
184 | 
185 | > **Note:** Make sure your CORS configuration on the server matches your client's domain, and that `credentials: true` is set in both the server's CORS config and the client's fetch config.
186 | 
```
Page 16/69FirstPrevNextLast