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

# Directory Structure

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

# Files

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

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

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

```markdown
 1 | ---
 2 | title: LinkedIn
 3 | description: LinkedIn Provider
 4 | ---
 5 | 
 6 | <Steps>
 7 |     <Step> 
 8 |         ### Get your LinkedIn credentials
 9 |         To use LinkedIn sign in, you need a client ID and client secret. You can get them from the [LinkedIn Developer Portal](https://www.linkedin.com/developers/).
10 |         
11 |         Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/linkedin` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly.
12 |     </Step>
13 | 
14 |     <Callout type="info">
15 |      In the LinkedIn portal under products you need the **Sign In with LinkedIn using OpenID Connect** product.
16 |     </Callout>
17 | 
18 |     There are some different Guides here:
19 |         [Authorization Code Flow (3-legged OAuth) (Outdated)](https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow)
20 |         [Sign In with LinkedIn using OpenID Connect](https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2?context=linkedin%2Fconsumer%2Fcontext)
21 | 
22 |   <Step>
23 |         ### Configure the provider
24 |         To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
25 | 
26 |         ```ts title="auth.ts"
27 |         import { betterAuth } from "better-auth"
28 | 
29 |         export const auth = betterAuth({
30 |             socialProviders: {
31 |                 linkedin: { // [!code highlight]
32 |                     clientId: process.env.LINKEDIN_CLIENT_ID as string, // [!code highlight]
33 |                     clientSecret: process.env.LINKEDIN_CLIENT_SECRET as string, // [!code highlight]
34 |                 }, // [!code highlight]
35 |             },
36 |         })
37 |         ```
38 |     </Step>
39 |        <Step>
40 |         ### Sign In with LinkedIn
41 |         To sign in with LinkedIn, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
42 |         - `provider`: The provider to use. It should be set to `linkedin`.
43 | 
44 |         ```ts title="auth-client.ts"
45 |         import { createAuthClient } from "better-auth/client"
46 |         const authClient =  createAuthClient()
47 | 
48 |         const signIn = async () => {
49 |             const data = await authClient.signIn.social({
50 |                 provider: "linkedin"
51 |             })
52 |         }
53 |         ```
54 |     </Step>
55 | 
56 | </Steps>
57 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/client/test-plugin.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { atom, computed } from "nanostores";
  2 | import type { BetterAuthClientPlugin } from "@better-auth/core";
  3 | import type { BetterAuthPlugin } from "@better-auth/core";
  4 | import { createAuthEndpoint } from "@better-auth/core/api";
  5 | import { useAuthQuery } from "./query";
  6 | import z from "zod";
  7 | 
  8 | const serverPlugin = {
  9 | 	id: "test",
 10 | 	endpoints: {
 11 | 		test: createAuthEndpoint(
 12 | 			"/test",
 13 | 			{
 14 | 				method: "GET",
 15 | 				error: z.object({
 16 | 					code: z.number(),
 17 | 					message: z.string(),
 18 | 					test: z.boolean(),
 19 | 				}),
 20 | 			},
 21 | 			async (c) => {
 22 | 				return {
 23 | 					data: "test",
 24 | 				};
 25 | 			},
 26 | 		),
 27 | 		testSignOut2: createAuthEndpoint(
 28 | 			"/test-2/sign-out",
 29 | 			{
 30 | 				method: "POST",
 31 | 			},
 32 | 			async (c) => {
 33 | 				return null;
 34 | 			},
 35 | 		),
 36 | 	},
 37 | 	schema: {
 38 | 		user: {
 39 | 			fields: {
 40 | 				testField: {
 41 | 					type: "string",
 42 | 					required: false,
 43 | 				},
 44 | 				testField2: {
 45 | 					type: "number",
 46 | 					required: false,
 47 | 				},
 48 | 				testField3: {
 49 | 					type: "string",
 50 | 					returned: false,
 51 | 				},
 52 | 				testField4: {
 53 | 					type: "string",
 54 | 					defaultValue: "test",
 55 | 				},
 56 | 			},
 57 | 		},
 58 | 	},
 59 | } satisfies BetterAuthPlugin;
 60 | 
 61 | export const testClientPlugin = () => {
 62 | 	const $test = atom(false);
 63 | 	let testValue = 0;
 64 | 	const computedAtom = computed($test, () => {
 65 | 		return testValue++;
 66 | 	});
 67 | 	return {
 68 | 		id: "test" as const,
 69 | 		getActions($fetch) {
 70 | 			return {
 71 | 				setTestAtom(value: boolean) {
 72 | 					$test.set(value);
 73 | 				},
 74 | 				test: {
 75 | 					signOut: async () => {},
 76 | 				},
 77 | 			};
 78 | 		},
 79 | 		getAtoms($fetch) {
 80 | 			const $signal = atom(false);
 81 | 			const queryAtom = useAuthQuery<any>($signal, "/test", $fetch, {
 82 | 				method: "GET",
 83 | 			});
 84 | 			return {
 85 | 				$test,
 86 | 				$signal,
 87 | 				computedAtom,
 88 | 				queryAtom,
 89 | 			};
 90 | 		},
 91 | 		$InferServerPlugin: {} as typeof serverPlugin,
 92 | 		atomListeners: [
 93 | 			{
 94 | 				matcher: (path) => path === "/test",
 95 | 				signal: "$test",
 96 | 			},
 97 | 			{
 98 | 				matcher: (path) => path === "/test2/sign-out",
 99 | 				signal: "$sessionSignal",
100 | 			},
101 | 		],
102 | 	} satisfies BetterAuthClientPlugin;
103 | };
104 | export const testClientPlugin2 = () => {
105 | 	const $test2 = atom(false);
106 | 	let testValue = 0;
107 | 	const anotherAtom = computed($test2, () => {
108 | 		return testValue++;
109 | 	});
110 | 	return {
111 | 		id: "test",
112 | 		getAtoms($fetch) {
113 | 			return {
114 | 				$test2,
115 | 				anotherAtom,
116 | 			};
117 | 		},
118 | 		atomListeners: [
119 | 			{
120 | 				matcher: (path) => path === "/test",
121 | 				signal: "$test",
122 | 			},
123 | 			{
124 | 				matcher: (path) => path === "/test2/sign-out",
125 | 				signal: "$sessionSignal",
126 | 			},
127 | 		],
128 | 	} satisfies BetterAuthClientPlugin;
129 | };
130 | 
```

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

```markdown
 1 | ---
 2 | title: LINE
 3 | description: LINE provider setup and usage.
 4 | ---
 5 | 
 6 | <Steps>
 7 |     <Step>
 8 |         ### Get your LINE credentials
 9 | 
10 |         1. Create a channel in the LINE Developers Console.
11 |         2. Note your Channel ID (client_id) and Channel secret (client_secret).
12 |         3. In the channel settings, add your Redirect URI, e.g. `http://localhost:3000/api/auth/callback/line` for local development.
13 |         4. Enable required scopes (at least `openid`; add `profile`, `email` if you need name, avatar, email).
14 | 
15 |         See LINE Login v2.1 reference for details: [`https://developers.line.biz/en/reference/line-login/#issue-access-token`]
16 |     </Step>
17 | 
18 |     <Step>
19 |         ### Configure the provider
20 | 
21 |         Add your LINE credentials to `socialProviders.line` in your auth configuration.
22 | 
23 |         ```ts title="auth.ts"
24 |         import { betterAuth } from "better-auth";
25 | 
26 |         export const auth = betterAuth({
27 |           socialProviders: {
28 |             line: {
29 |               clientId: process.env.LINE_CLIENT_ID as string,
30 |               clientSecret: process.env.LINE_CLIENT_SECRET as string,
31 |               // Optional: override redirect if needed
32 |               // redirectURI: "https://your.app/api/auth/callback/line",
33 |               // scopes are prefilled: ["openid","profile","email"]. Append if needed
34 |             },
35 |           },
36 |         });
37 |         ```
38 |     </Step>
39 | </Steps>
40 | 
41 | ## Usage
42 | 
43 | ### Sign In with LINE
44 | 
45 | Use the client `signIn.social` with `provider: "line"`.
46 | 
47 | ```ts title="auth-client.ts"
48 | import { createAuthClient } from "better-auth/client";
49 | const authClient = createAuthClient();
50 | 
51 | async function signInWithLINE() {
52 |   const res = await authClient.signIn.social({ provider: "line" });
53 | }
54 | ```
55 | 
56 | ### Sign In with LINE using ID Token (optional)
57 | 
58 | If you obtain the LINE ID token on the client, you can sign in directly without redirection.
59 | 
60 | ```ts title="auth-client.ts"
61 | await authClient.signIn.social({
62 |   provider: "line",
63 |   idToken: {
64 |     token: "<LINE_ID_TOKEN>",
65 |     accessToken: "<LINE_ACCESS_TOKEN>",
66 |   },
67 | });
68 | ```
69 | 
70 | ### Notes
71 | 
72 | - Default scopes include `openid profile email`. Adjust as needed via provider options.
73 | - Verify redirect URI exactly matches the value configured in LINE Developers Console.
74 | - LINE ID token verification uses the official endpoint and checks audience and optional nonce per spec.
75 | 
76 | Designing a login button? Follow LINE's button [guidelines](https://developers.line.biz/en/docs/line-login/login-button/).
77 | 
78 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/db/to-zod.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as z from "zod";
  2 | import type { ZodType } from "zod";
  3 | import type { DBFieldAttribute } from "@better-auth/core/db";
  4 | 
  5 | export function toZodSchema<
  6 | 	Fields extends Record<string, DBFieldAttribute | never>,
  7 | 	IsClientSide extends boolean,
  8 | >({
  9 | 	fields,
 10 | 	isClientSide,
 11 | }: {
 12 | 	fields: Fields;
 13 | 	/**
 14 | 	 * If true, then any fields that have `input: false` will be removed from the schema to prevent user input.
 15 | 	 */
 16 | 	isClientSide: IsClientSide;
 17 | }) {
 18 | 	const zodFields = Object.keys(fields).reduce((acc, key) => {
 19 | 		const field = fields[key];
 20 | 		if (!field) {
 21 | 			return acc;
 22 | 		}
 23 | 		if (isClientSide && field.input === false) {
 24 | 			return acc;
 25 | 		}
 26 | 
 27 | 		let schema: ZodType;
 28 | 		if (field.type === "json") {
 29 | 			schema = (z as any).json ? (z as any).json() : z.any();
 30 | 		} else if (field.type === "string[]" || field.type === "number[]") {
 31 | 			schema = z.array(field.type === "string[]" ? z.string() : z.number());
 32 | 		} else if (Array.isArray(field.type)) {
 33 | 			schema = z.any();
 34 | 		} else {
 35 | 			schema = z[field.type]();
 36 | 		}
 37 | 
 38 | 		if (field?.required === false) {
 39 | 			schema = schema.optional();
 40 | 		}
 41 | 		if (field?.returned === false) {
 42 | 			return acc;
 43 | 		}
 44 | 		return {
 45 | 			...acc,
 46 | 			[key]: schema,
 47 | 		};
 48 | 	}, {});
 49 | 	const schema = z.object(zodFields);
 50 | 	return schema as z.ZodObject<
 51 | 		RemoveNeverProps<{
 52 | 			[key in keyof Fields]: FieldAttributeToSchema<Fields[key], IsClientSide>;
 53 | 		}>,
 54 | 		z.core.$strip
 55 | 	>;
 56 | }
 57 | 
 58 | export type FieldAttributeToSchema<
 59 | 	Field extends DBFieldAttribute | Record<string, never>,
 60 | 	// if it's client side, then field attributes of `input` that are false should be removed
 61 | 	isClientSide extends boolean = false,
 62 | > = Field extends { type: any }
 63 | 	? GetInput<isClientSide, Field, GetRequired<Field, GetType<Field>>>
 64 | 	: Record<string, never>;
 65 | 
 66 | type GetType<F extends DBFieldAttribute> = F extends {
 67 | 	type: "string";
 68 | }
 69 | 	? z.ZodString
 70 | 	: F extends { type: "number" }
 71 | 		? z.ZodNumber
 72 | 		: F extends { type: "boolean" }
 73 | 			? z.ZodBoolean
 74 | 			: F extends { type: "date" }
 75 | 				? z.ZodDate
 76 | 				: z.ZodAny;
 77 | 
 78 | type GetRequired<
 79 | 	F extends DBFieldAttribute,
 80 | 	Schema extends z.core.SomeType,
 81 | > = F extends {
 82 | 	required: true;
 83 | }
 84 | 	? Schema
 85 | 	: z.ZodOptional<Schema>;
 86 | 
 87 | type GetInput<
 88 | 	isClientSide extends boolean,
 89 | 	Field extends DBFieldAttribute,
 90 | 	Schema extends z.core.SomeType,
 91 | > = Field extends {
 92 | 	input: false;
 93 | }
 94 | 	? isClientSide extends true
 95 | 		? never
 96 | 		: Schema
 97 | 	: Schema;
 98 | 
 99 | type RemoveNeverProps<T> = {
100 | 	[K in keyof T as [T[K]] extends [never] ? never : K]: T[K];
101 | };
102 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/utils/url.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { env } from "@better-auth/core/env";
  2 | import { BetterAuthError } from "@better-auth/core/error";
  3 | 
  4 | function checkHasPath(url: string): boolean {
  5 | 	try {
  6 | 		const parsedUrl = new URL(url);
  7 | 		const pathname = parsedUrl.pathname.replace(/\/+$/, "") || "/";
  8 | 		return pathname !== "/";
  9 | 	} catch (error) {
 10 | 		throw new BetterAuthError(
 11 | 			`Invalid base URL: ${url}. Please provide a valid base URL.`,
 12 | 		);
 13 | 	}
 14 | }
 15 | 
 16 | function withPath(url: string, path = "/api/auth") {
 17 | 	const hasPath = checkHasPath(url);
 18 | 	if (hasPath) {
 19 | 		return url;
 20 | 	}
 21 | 
 22 | 	const trimmedUrl = url.replace(/\/+$/, "");
 23 | 
 24 | 	if (!path || path === "/") {
 25 | 		return trimmedUrl;
 26 | 	}
 27 | 
 28 | 	path = path.startsWith("/") ? path : `/${path}`;
 29 | 	return `${trimmedUrl}${path}`;
 30 | }
 31 | 
 32 | export function getBaseURL(
 33 | 	url?: string,
 34 | 	path?: string,
 35 | 	request?: Request,
 36 | 	loadEnv?: boolean,
 37 | ) {
 38 | 	if (url) {
 39 | 		return withPath(url, path);
 40 | 	}
 41 | 
 42 | 	if (loadEnv !== false) {
 43 | 		const fromEnv =
 44 | 			env.BETTER_AUTH_URL ||
 45 | 			env.NEXT_PUBLIC_BETTER_AUTH_URL ||
 46 | 			env.PUBLIC_BETTER_AUTH_URL ||
 47 | 			env.NUXT_PUBLIC_BETTER_AUTH_URL ||
 48 | 			env.NUXT_PUBLIC_AUTH_URL ||
 49 | 			(env.BASE_URL !== "/" ? env.BASE_URL : undefined);
 50 | 
 51 | 		if (fromEnv) {
 52 | 			return withPath(fromEnv, path);
 53 | 		}
 54 | 	}
 55 | 
 56 | 	const fromRequest = request?.headers.get("x-forwarded-host");
 57 | 	const fromRequestProto = request?.headers.get("x-forwarded-proto");
 58 | 	if (fromRequest && fromRequestProto) {
 59 | 		return withPath(`${fromRequestProto}://${fromRequest}`, path);
 60 | 	}
 61 | 
 62 | 	if (request) {
 63 | 		const url = getOrigin(request.url);
 64 | 		if (!url) {
 65 | 			throw new BetterAuthError(
 66 | 				"Could not get origin from request. Please provide a valid base URL.",
 67 | 			);
 68 | 		}
 69 | 		return withPath(url, path);
 70 | 	}
 71 | 
 72 | 	if (typeof window !== "undefined" && window.location) {
 73 | 		return withPath(window.location.origin, path);
 74 | 	}
 75 | 	return undefined;
 76 | }
 77 | 
 78 | export function getOrigin(url: string) {
 79 | 	try {
 80 | 		const parsedUrl = new URL(url);
 81 | 		// For custom URL schemes (like exp://), the origin property returns the string "null"
 82 | 		// instead of null. We need to handle this case and return null so the fallback logic works.
 83 | 		return parsedUrl.origin === "null" ? null : parsedUrl.origin;
 84 | 	} catch (error) {
 85 | 		return null;
 86 | 	}
 87 | }
 88 | 
 89 | export function getProtocol(url: string) {
 90 | 	try {
 91 | 		const parsedUrl = new URL(url);
 92 | 		return parsedUrl.protocol;
 93 | 	} catch (error) {
 94 | 		return null;
 95 | 	}
 96 | }
 97 | 
 98 | export function getHost(url: string) {
 99 | 	try {
100 | 		const parsedUrl = new URL(url);
101 | 		return parsedUrl.host;
102 | 	} catch (error) {
103 | 		return url;
104 | 	}
105 | }
106 | 
```

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

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

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

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

--------------------------------------------------------------------------------
/demo/expo-example/src/app/sign-up.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import { Button } from "@/components/ui/button";
  2 | import { Card, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
  3 | import { Input } from "@/components/ui/input";
  4 | import { Text } from "@/components/ui/text";
  5 | import { authClient } from "@/lib/auth-client";
  6 | import { KeyboardAvoidingView, View } from "react-native";
  7 | import { Image } from "react-native";
  8 | import { useRouter } from "expo-router";
  9 | import { useState } from "react";
 10 | 
 11 | export default function SignUp() {
 12 | 	const router = useRouter();
 13 | 	const [email, setEmail] = useState("");
 14 | 	const [password, setPassword] = useState("");
 15 | 	const [name, setName] = useState("");
 16 | 	return (
 17 | 		<Card className="z-50 mx-6">
 18 | 			<CardHeader className="flex items-center justify-center gap-8">
 19 | 				<Image
 20 | 					source={require("../../assets/images/logo.png")}
 21 | 					style={{
 22 | 						width: 40,
 23 | 						height: 40,
 24 | 					}}
 25 | 				/>
 26 | 				<CardTitle>Create new Account</CardTitle>
 27 | 			</CardHeader>
 28 | 			<View className="px-6">
 29 | 				<KeyboardAvoidingView>
 30 | 					<Input
 31 | 						placeholder="Name"
 32 | 						className="rounded-b-none border-b-0"
 33 | 						value={name}
 34 | 						onChangeText={(text) => {
 35 | 							setName(text);
 36 | 						}}
 37 | 					/>
 38 | 				</KeyboardAvoidingView>
 39 | 				<KeyboardAvoidingView>
 40 | 					<Input
 41 | 						placeholder="Email"
 42 | 						className="rounded-b-none border-b-0"
 43 | 						value={email}
 44 | 						onChangeText={(text) => {
 45 | 							setEmail(text);
 46 | 						}}
 47 | 						autoCapitalize="none"
 48 | 					/>
 49 | 				</KeyboardAvoidingView>
 50 | 
 51 | 				<KeyboardAvoidingView>
 52 | 					<Input
 53 | 						placeholder="Password"
 54 | 						secureTextEntry
 55 | 						className="rounded-t-none"
 56 | 						value={password}
 57 | 						onChangeText={(text) => {
 58 | 							setPassword(text);
 59 | 						}}
 60 | 					/>
 61 | 				</KeyboardAvoidingView>
 62 | 			</View>
 63 | 			<CardFooter>
 64 | 				<View className="w-full mt-2">
 65 | 					<Button
 66 | 						onPress={async () => {
 67 | 							const res = await authClient.signUp.email(
 68 | 								{
 69 | 									email,
 70 | 									password,
 71 | 									name,
 72 | 								},
 73 | 								{
 74 | 									onError: (ctx) => {
 75 | 										alert(ctx.error.message);
 76 | 									},
 77 | 									onSuccess: (ctx) => {
 78 | 										router.push("/dashboard");
 79 | 									},
 80 | 								},
 81 | 							);
 82 | 							console.log(res);
 83 | 						}}
 84 | 					>
 85 | 						<Text>Sign Up</Text>
 86 | 					</Button>
 87 | 					<Text className="text-center mt-2">
 88 | 						Already have an account?{" "}
 89 | 						<Text
 90 | 							className="underline"
 91 | 							onPress={() => {
 92 | 								router.push("/");
 93 | 							}}
 94 | 						>
 95 | 							Sign In
 96 | 						</Text>
 97 | 					</Text>
 98 | 				</View>
 99 | 			</CardFooter>
100 | 		</Card>
101 | 	);
102 | }
103 | 
```

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

```markdown
 1 | ---
 2 | title: Figma
 3 | description: Figma provider setup and usage.
 4 | ---
 5 | 
 6 | <Steps>
 7 |     <Step>
 8 |         ### Get your Credentials
 9 |         1. Sign in to your Figma account and go to the [Developer Apps page](https://www.figma.com/developers/apps)
10 |         2. Click "Create new app"
11 |         3. Fill out the app details (name, description, etc.)
12 |         4. Configure your redirect URI (e.g., `https://yourdomain.com/api/auth/callback/figma`)
13 |         5. Note your Client ID and Client Secret
14 | 
15 |         <Callout type="info">
16 |             - The default scope is `file_read`. For additional scopes like `file_write`, refer to the [Figma OAuth documentation](https://www.figma.com/developers/api#oauth2).
17 |         </Callout>
18 | 
19 |         Make sure to set the redirect URI to match your application's callback URL. If you change the base path of the auth routes, you should update the redirect URI accordingly.
20 |     </Step>
21 | 
22 |   <Step>
23 |         ### Configure the provider
24 |         To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
25 | 
26 |         ```ts title="auth.ts"
27 |         import { betterAuth } from "better-auth"
28 | 
29 |         export const auth = betterAuth({
30 |             socialProviders: {
31 |                 figma: { // [!code highlight]
32 |                     clientId: process.env.FIGMA_CLIENT_ID as string, // [!code highlight]
33 |                     clientSecret: process.env.FIGMA_CLIENT_SECRET as string, // [!code highlight]
34 |                     clientKey: process.env.FIGMA_CLIENT_KEY as string, // [!code highlight]
35 |                 }, // [!code highlight]
36 |             },
37 |         })
38 |         ```
39 |     </Step>
40 |        <Step>
41 |         ### Sign In with Figma
42 |         To sign in with Figma, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
43 |         - `provider`: The provider to use. It should be set to `figma`.
44 | 
45 |         ```ts title="auth-client.ts"
46 |         import { createAuthClient } from "better-auth/client"
47 |         const authClient =  createAuthClient()
48 | 
49 |         const signIn = async () => {
50 |             const data = await authClient.signIn.social({
51 |                 provider: "figma"
52 |             })
53 |         }
54 |         ```
55 |         <Callout type="info">
56 |         For more information about Figma's OAuth scopes and API capabilities, refer to the [official Figma API documentation](https://www.figma.com/developers/api).
57 |         </Callout>
58 |     </Step>
59 | 
60 | </Steps>
61 | 
```

--------------------------------------------------------------------------------
/docs/components/ui/dynamic-code-block.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | import {
  3 | 	CodeBlock,
  4 | 	type CodeBlockProps,
  5 | 	Pre,
  6 | } from "@/components/ui/code-block";
  7 | import type {
  8 | 	HighlightOptions,
  9 | 	HighlightOptionsCommon,
 10 | 	HighlightOptionsThemes,
 11 | } from "fumadocs-core/highlight";
 12 | import { useShiki } from "fumadocs-core/highlight/client";
 13 | import { cn } from "@/lib/utils";
 14 | import {
 15 | 	type ComponentProps,
 16 | 	createContext,
 17 | 	type FC,
 18 | 	Suspense,
 19 | 	use,
 20 | } from "react";
 21 | 
 22 | export interface DynamicCodeblockProps {
 23 | 	lang: string;
 24 | 	code: string;
 25 | 	/**
 26 | 	 * Extra props for the underlying `<CodeBlock />` component.
 27 | 	 *
 28 | 	 * Ignored if you defined your own `pre` component in `options.components`.
 29 | 	 */
 30 | 	codeblock?: CodeBlockProps;
 31 | 	/**
 32 | 	 * Wrap in React `<Suspense />` and provide a fallback.
 33 | 	 *
 34 | 	 * @defaultValue true
 35 | 	 */
 36 | 	wrapInSuspense?: boolean;
 37 | 	/**
 38 | 	 * Allow to copy code with copy button
 39 | 	 *
 40 | 	 * @defaultValue true
 41 | 	 */
 42 | 	allowCopy?: boolean;
 43 | 	options?: Omit<HighlightOptionsCommon, "lang"> & HighlightOptionsThemes;
 44 | }
 45 | 
 46 | const PropsContext = createContext<CodeBlockProps | undefined>(undefined);
 47 | 
 48 | function DefaultPre(props: ComponentProps<"pre">) {
 49 | 	const extraProps = use(PropsContext);
 50 | 
 51 | 	return (
 52 | 		<CodeBlock
 53 | 			{...props}
 54 | 			{...extraProps}
 55 | 			className={cn(
 56 | 				"my-0 border-t-0 rounded-none",
 57 | 				props.className,
 58 | 				extraProps?.className,
 59 | 			)}
 60 | 		>
 61 | 			<Pre className="py-2">{props.children}</Pre>
 62 | 		</CodeBlock>
 63 | 	);
 64 | }
 65 | 
 66 | export function DynamicCodeBlock({
 67 | 	lang,
 68 | 	code,
 69 | 	codeblock,
 70 | 	options,
 71 | 	wrapInSuspense = true,
 72 | 	allowCopy = true,
 73 | }: DynamicCodeblockProps) {
 74 | 	const shikiOptions = {
 75 | 		lang,
 76 | 		...options,
 77 | 		components: {
 78 | 			pre: DefaultPre,
 79 | 			...options?.components,
 80 | 		},
 81 | 	} satisfies HighlightOptions;
 82 | 	let children = <Internal code={code} options={shikiOptions} />;
 83 | 
 84 | 	if (wrapInSuspense)
 85 | 		children = (
 86 | 			<Suspense
 87 | 				fallback={
 88 | 					<Placeholder code={code} components={shikiOptions.components} />
 89 | 				}
 90 | 			>
 91 | 				{children}
 92 | 			</Suspense>
 93 | 		);
 94 | 
 95 | 	return (
 96 | 		<PropsContext value={{ ...codeblock, allowCopy }}>{children}</PropsContext>
 97 | 	);
 98 | }
 99 | 
100 | function Placeholder({
101 | 	code,
102 | 	components = {},
103 | }: {
104 | 	code: string;
105 | 	components: HighlightOptions["components"];
106 | }) {
107 | 	const { pre: Pre = "pre", code: Code = "code" } = components as Record<
108 | 		string,
109 | 		FC
110 | 	>;
111 | 
112 | 	return (
113 | 		<Pre>
114 | 			<Code>
115 | 				{code.split("\n").map((line, i) => (
116 | 					<span key={i} className="line">
117 | 						{line}
118 | 					</span>
119 | 				))}
120 | 			</Code>
121 | 		</Pre>
122 | 	);
123 | }
124 | 
125 | function Internal({
126 | 	code,
127 | 	options,
128 | }: {
129 | 	code: string;
130 | 	options: HighlightOptions;
131 | }) {
132 | 	return useShiki(code, options);
133 | }
134 | 
```

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

```typescript
  1 | import type { BetterAuthOptions } from "@better-auth/core";
  2 | import type { DBAdapter } from "@better-auth/core/db/adapter";
  3 | import { drizzleAdapter } from "../drizzle-adapter";
  4 | import fs from "fs/promises";
  5 | import { join } from "path";
  6 | 
  7 | let generationCount = 0;
  8 | 
  9 | const schemaCache = new Map<string, { count: number; schema: any }>();
 10 | 
 11 | /**
 12 |  * generates a drizzle schema based on BetterAuthOptions & a given dialect.
 13 |  *
 14 |  * Useful for testing the Drizzle adapter.
 15 |  */
 16 | export const generateDrizzleSchema = async (
 17 | 	db: any,
 18 | 	options: BetterAuthOptions,
 19 | 	dialect: "sqlite" | "mysql" | "pg",
 20 | ) => {
 21 | 	const cacheKey = `${dialect}-${JSON.stringify(options)}`;
 22 | 	if (schemaCache.has(cacheKey)) {
 23 | 		const { count, schema } = schemaCache.get(cacheKey)!;
 24 | 		return {
 25 | 			schema,
 26 | 			fileName: `./.tmp/generated-${dialect}-schema-${count}`,
 27 | 		};
 28 | 	}
 29 | 	generationCount++;
 30 | 	let thisCount = generationCount;
 31 | 	const i = async (x: string) => {
 32 | 		// Clear the Node.js module cache for the generated schema file to ensure fresh import
 33 | 		try {
 34 | 			const resolvedPath =
 35 | 				require?.resolve?.(x) ||
 36 | 				(import.meta && new URL(x, import.meta.url).pathname);
 37 | 			if (resolvedPath && typeof resolvedPath === "string" && require?.cache) {
 38 | 				delete require.cache[resolvedPath];
 39 | 			}
 40 | 		} catch (error) {}
 41 | 		return await import(x);
 42 | 	};
 43 | 
 44 | 	const { generateSchema } = (await i(
 45 | 		"./../../../../../cli/src/generators/index",
 46 | 	)) as {
 47 | 		generateSchema: (opts: {
 48 | 			adapter: DBAdapter<BetterAuthOptions>;
 49 | 			file?: string;
 50 | 			options: BetterAuthOptions;
 51 | 		}) => Promise<{
 52 | 			code: string | undefined;
 53 | 			fileName: string;
 54 | 			overwrite: boolean | undefined;
 55 | 		}>;
 56 | 	};
 57 | 
 58 | 	const exists = await fs
 59 | 		.access(join(import.meta.dirname, `/.tmp`))
 60 | 		.then(() => true)
 61 | 		.catch(() => false);
 62 | 	if (!exists) {
 63 | 		await fs.mkdir(join(import.meta.dirname, `/.tmp`), { recursive: true });
 64 | 	}
 65 | 
 66 | 	let adapter = drizzleAdapter(db, { provider: dialect })(options);
 67 | 
 68 | 	let { code } = await generateSchema({
 69 | 		adapter,
 70 | 		options,
 71 | 	});
 72 | 
 73 | 	await fs.writeFile(
 74 | 		join(
 75 | 			import.meta.dirname,
 76 | 			`/.tmp/generated-${dialect}-schema-${thisCount}.ts`,
 77 | 		),
 78 | 		code || "",
 79 | 		"utf-8",
 80 | 	);
 81 | 
 82 | 	const res = await i(`./.tmp/generated-${dialect}-schema-${thisCount}`);
 83 | 	schemaCache.set(cacheKey, {
 84 | 		count: thisCount,
 85 | 		schema: res,
 86 | 	});
 87 | 	return {
 88 | 		schema: res,
 89 | 		fileName: `./.tmp/generated-${dialect}-schema-${thisCount}`,
 90 | 	};
 91 | };
 92 | 
 93 | export const clearSchemaCache = () => {
 94 | 	schemaCache.clear();
 95 | };
 96 | 
 97 | export const resetGenerationCount = () => {
 98 | 	generationCount = 0;
 99 | };
100 | 
```

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

```markdown
 1 | ---
 2 | title: Atlassian
 3 | description: Atlassian provider setup and usage.
 4 | ---
 5 | 
 6 | <Steps>
 7 |     <Step>
 8 |         ### Get your Credentials
 9 |         1. Sign in to your Atlassian account and go to the [Atlassian Developer Console](https://developer.atlassian.com/console/myapps/)
10 |         2. Click "Create new app"
11 |         3. Fill out the app details
12 |         4. Configure your redirect URI (e.g., `https://yourdomain.com/api/auth/callback/atlassian`)
13 |         5. Note your Client ID and Client Secret
14 | 
15 |         <Callout type="info">
16 |             - The default scope is `read:jira-user` and `offline_access`. For additional scopes, refer to the [Atlassian OAuth documentation](https://developer.atlassian.com/cloud/confluence/oauth-2-3lo-apps/).
17 |         </Callout>
18 | 
19 |         Make sure to set the redirect URI to match your application's callback URL. If you change the base path of the auth routes, you should update the redirect URI accordingly.
20 |     </Step>
21 | 
22 |   <Step>
23 |         ### Configure the provider
24 |         To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
25 | 
26 |         ```ts title="auth.ts"
27 |         import { betterAuth } from "better-auth"
28 | 
29 |         export const auth = betterAuth({
30 |             socialProviders: {
31 |                 atlassian: { // [!code highlight]
32 |                     clientId: process.env.ATLASSIAN_CLIENT_ID as string, // [!code highlight]
33 |                     clientSecret: process.env.ATLASSIAN_CLIENT_SECRET as string, // [!code highlight]
34 |                 }, // [!code highlight]
35 |             },
36 |         })
37 |         ```
38 |     </Step>
39 |        <Step>
40 |         ### Sign In with Atlassian
41 |         To sign in with Atlassian, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
42 |         - `provider`: The provider to use. It should be set to `atlassian`.
43 | 
44 |         ```ts title="auth-client.ts"
45 |         import { createAuthClient } from "better-auth/client"
46 |         const authClient =  createAuthClient()
47 | 
48 |         const signIn = async () => {
49 |             const data = await authClient.signIn.social({
50 |                 provider: "atlassian"
51 |             })
52 |         }
53 |         ```
54 |         <Callout type="info">
55 |         For more information about Atlassian's OAuth scopes and API capabilities, refer to the [official Atlassian OAuth 2.0 (3LO) apps documentation](https://developer.atlassian.com/cloud/confluence/oauth-2-3lo-apps/).
56 |         </Callout>
57 |     </Step>
58 | 
59 | </Steps>
60 | 
```

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

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

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

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  3 | import {
  4 | 	createAuthorizationURL,
  5 | 	validateAuthorizationCode,
  6 | 	refreshAccessToken,
  7 | } from "../oauth2";
  8 | 
  9 | export interface LinkedInProfile {
 10 | 	sub: string;
 11 | 	name: string;
 12 | 	given_name: string;
 13 | 	family_name: string;
 14 | 	picture: string;
 15 | 	locale: {
 16 | 		country: string;
 17 | 		language: string;
 18 | 	};
 19 | 	email: string;
 20 | 	email_verified: boolean;
 21 | }
 22 | 
 23 | export interface LinkedInOptions extends ProviderOptions<LinkedInProfile> {
 24 | 	clientId: string;
 25 | }
 26 | 
 27 | export const linkedin = (options: LinkedInOptions) => {
 28 | 	const authorizationEndpoint =
 29 | 		"https://www.linkedin.com/oauth/v2/authorization";
 30 | 	const tokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken";
 31 | 
 32 | 	return {
 33 | 		id: "linkedin",
 34 | 		name: "Linkedin",
 35 | 		createAuthorizationURL: async ({
 36 | 			state,
 37 | 			scopes,
 38 | 			redirectURI,
 39 | 			loginHint,
 40 | 		}) => {
 41 | 			const _scopes = options.disableDefaultScope
 42 | 				? []
 43 | 				: ["profile", "email", "openid"];
 44 | 			options.scope && _scopes.push(...options.scope);
 45 | 			scopes && _scopes.push(...scopes);
 46 | 			return await createAuthorizationURL({
 47 | 				id: "linkedin",
 48 | 				options,
 49 | 				authorizationEndpoint,
 50 | 				scopes: _scopes,
 51 | 				state,
 52 | 				loginHint,
 53 | 				redirectURI,
 54 | 			});
 55 | 		},
 56 | 		validateAuthorizationCode: async ({ code, redirectURI }) => {
 57 | 			return await validateAuthorizationCode({
 58 | 				code,
 59 | 				redirectURI,
 60 | 				options,
 61 | 				tokenEndpoint,
 62 | 			});
 63 | 		},
 64 | 		refreshAccessToken: options.refreshAccessToken
 65 | 			? options.refreshAccessToken
 66 | 			: async (refreshToken) => {
 67 | 					return refreshAccessToken({
 68 | 						refreshToken,
 69 | 						options: {
 70 | 							clientId: options.clientId,
 71 | 							clientKey: options.clientKey,
 72 | 							clientSecret: options.clientSecret,
 73 | 						},
 74 | 						tokenEndpoint,
 75 | 					});
 76 | 				},
 77 | 		async getUserInfo(token) {
 78 | 			if (options.getUserInfo) {
 79 | 				return options.getUserInfo(token);
 80 | 			}
 81 | 			const { data: profile, error } = await betterFetch<LinkedInProfile>(
 82 | 				"https://api.linkedin.com/v2/userinfo",
 83 | 				{
 84 | 					method: "GET",
 85 | 					headers: {
 86 | 						Authorization: `Bearer ${token.accessToken}`,
 87 | 					},
 88 | 				},
 89 | 			);
 90 | 
 91 | 			if (error) {
 92 | 				return null;
 93 | 			}
 94 | 
 95 | 			const userMap = await options.mapProfileToUser?.(profile);
 96 | 			return {
 97 | 				user: {
 98 | 					id: profile.sub,
 99 | 					name: profile.name,
100 | 					email: profile.email,
101 | 					emailVerified: profile.email_verified || false,
102 | 					image: profile.picture,
103 | 					...userMap,
104 | 				},
105 | 				data: profile,
106 | 			};
107 | 		},
108 | 		options,
109 | 	} satisfies OAuthProvider<LinkedInProfile>;
110 | };
111 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/client/svelte/index.ts:
--------------------------------------------------------------------------------

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

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

```typescript
  1 | import * as React from "react";
  2 | import {
  3 | 	ChevronLeftIcon,
  4 | 	ChevronRightIcon,
  5 | 	MoreHorizontalIcon,
  6 | } from "lucide-react";
  7 | 
  8 | import { cn } from "@/lib/utils";
  9 | import { Button, buttonVariants } from "@/components/ui/button";
 10 | 
 11 | function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
 12 | 	return (
 13 | 		<nav
 14 | 			role="navigation"
 15 | 			aria-label="pagination"
 16 | 			data-slot="pagination"
 17 | 			className={cn("mx-auto flex w-full justify-center", className)}
 18 | 			{...props}
 19 | 		/>
 20 | 	);
 21 | }
 22 | 
 23 | function PaginationContent({
 24 | 	className,
 25 | 	...props
 26 | }: React.ComponentProps<"ul">) {
 27 | 	return (
 28 | 		<ul
 29 | 			data-slot="pagination-content"
 30 | 			className={cn("flex flex-row items-center gap-1", className)}
 31 | 			{...props}
 32 | 		/>
 33 | 	);
 34 | }
 35 | 
 36 | function PaginationItem({ ...props }: React.ComponentProps<"li">) {
 37 | 	return <li data-slot="pagination-item" {...props} />;
 38 | }
 39 | 
 40 | type PaginationLinkProps = {
 41 | 	isActive?: boolean;
 42 | } & Pick<React.ComponentProps<typeof Button>, "size"> &
 43 | 	React.ComponentProps<"a">;
 44 | 
 45 | function PaginationLink({
 46 | 	className,
 47 | 	isActive,
 48 | 	size = "icon",
 49 | 	...props
 50 | }: PaginationLinkProps) {
 51 | 	return (
 52 | 		<a
 53 | 			aria-current={isActive ? "page" : undefined}
 54 | 			data-slot="pagination-link"
 55 | 			data-active={isActive}
 56 | 			className={cn(
 57 | 				buttonVariants({
 58 | 					variant: isActive ? "outline" : "ghost",
 59 | 					size,
 60 | 				}),
 61 | 				className,
 62 | 			)}
 63 | 			{...props}
 64 | 		/>
 65 | 	);
 66 | }
 67 | 
 68 | function PaginationPrevious({
 69 | 	className,
 70 | 	...props
 71 | }: React.ComponentProps<typeof PaginationLink>) {
 72 | 	return (
 73 | 		<PaginationLink
 74 | 			aria-label="Go to previous page"
 75 | 			size="default"
 76 | 			className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
 77 | 			{...props}
 78 | 		>
 79 | 			<ChevronLeftIcon />
 80 | 			<span className="hidden sm:block">Previous</span>
 81 | 		</PaginationLink>
 82 | 	);
 83 | }
 84 | 
 85 | function PaginationNext({
 86 | 	className,
 87 | 	...props
 88 | }: React.ComponentProps<typeof PaginationLink>) {
 89 | 	return (
 90 | 		<PaginationLink
 91 | 			aria-label="Go to next page"
 92 | 			size="default"
 93 | 			className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
 94 | 			{...props}
 95 | 		>
 96 | 			<span className="hidden sm:block">Next</span>
 97 | 			<ChevronRightIcon />
 98 | 		</PaginationLink>
 99 | 	);
100 | }
101 | 
102 | function PaginationEllipsis({
103 | 	className,
104 | 	...props
105 | }: React.ComponentProps<"span">) {
106 | 	return (
107 | 		<span
108 | 			aria-hidden
109 | 			data-slot="pagination-ellipsis"
110 | 			className={cn("flex size-9 items-center justify-center", className)}
111 | 			{...props}
112 | 		>
113 | 			<MoreHorizontalIcon className="size-4" />
114 | 			<span className="sr-only">More pages</span>
115 | 		</span>
116 | 	);
117 | }
118 | 
119 | export {
120 | 	Pagination,
121 | 	PaginationContent,
122 | 	PaginationLink,
123 | 	PaginationItem,
124 | 	PaginationPrevious,
125 | 	PaginationNext,
126 | 	PaginationEllipsis,
127 | };
128 | 
```

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

```markdown
 1 | ---
 2 | title: PostgreSQL
 3 | description: Integrate Better Auth with PostgreSQL.
 4 | ---
 5 | 
 6 | PostgreSQL is a powerful, open-source relational database management system known for its advanced features, extensibility, and support for complex queries and large datasets.
 7 | Read more [here](https://www.postgresql.org/).
 8 | 
 9 | ## Example Usage
10 | 
11 | Make sure you have PostgreSQL installed and configured.
12 | Then, you can connect it straight into Better Auth.
13 | 
14 | ```ts title="auth.ts"
15 | import { betterAuth } from "better-auth";
16 | import { Pool } from "pg";
17 | 
18 | export const auth = betterAuth({
19 |   database: new Pool({
20 |     connectionString: "postgres://user:password@localhost:5432/database",
21 |   }),
22 | });
23 | ```
24 | 
25 | <Callout>
26 |   For more information, read Kysely's documentation to the
27 |   [PostgresDialect](https://kysely-org.github.io/kysely-apidoc/classes/PostgresDialect.html).
28 | </Callout>
29 | 
30 | ## Schema generation & migration
31 | 
32 | The [Better Auth CLI](/docs/concepts/cli) allows you to generate or migrate
33 | your database schema based on your Better Auth configuration and plugins.
34 | 
35 | <table>
36 |   <thead>
37 |     <tr className="border-b">
38 |       <th>
39 |         <p className="font-bold text-[16px] mb-1">PostgreSQL Schema Generation</p>
40 |       </th>
41 |       <th>
42 |         <p className="font-bold text-[16px] mb-1">PostgreSQL Schema Migration</p>
43 |       </th>
44 |     </tr>
45 |   </thead>
46 |   <tbody>
47 |     <tr className="h-10">
48 |       <td>✅ Supported</td>
49 |       <td>✅ Supported</td>
50 |     </tr>
51 |   </tbody>
52 | </table>
53 | 
54 | ```bash title="Schema Generation"
55 | npx @better-auth/cli@latest generate
56 | ```
57 | 
58 | ```bash title="Schema Migration"
59 | npx @better-auth/cli@latest migrate
60 | ```
61 | 
62 | ## Use a non-default schema
63 | 
64 | In most cases, the default schema is `public`. To have Better Auth use a
65 | non-default schema (e.g., `auth`) for its tables,
66 | set the PostgreSQL user's default schema before generating or migrating:
67 | 
68 | ```sql
69 | ALTER USER authuser SET SEARCH_PATH TO auth;
70 | ```
71 | 
72 | alternatively, append the option to your connection URI, for example:
73 | 
74 | ```
75 | postgres://<DATABASE_URL>?option=-c search_path=auth
76 | ```
77 | URL-encode if needed: `?option=-c%20search_path%3Dauth`.
78 | 
79 | Ensure the target schema exists and the database user has the required permissions.
80 | 
81 | ## Additional Information
82 | 
83 | PostgreSQL is supported under the hood via the [Kysely](https://kysely.dev/) adapter, any database supported by Kysely would also be supported. (<Link href="/docs/adapters/other-relational-databases">Read more here</Link>)
84 | 
85 | If you're looking for performance improvements or tips, take a look at our guide to <Link href="/docs/guides/optimizing-for-performance">performance optimizations</Link>.
86 | 
87 | 
```

--------------------------------------------------------------------------------
/packages/telemetry/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ENV, getBooleanEnvVar, isTest } from "@better-auth/core/env";
 2 | import { getProjectId } from "./project-id";
 3 | import type { BetterAuthOptions } from "@better-auth/core";
 4 | import { detectEnvironment, detectRuntime } from "./detectors/detect-runtime";
 5 | import { detectDatabase } from "./detectors/detect-database";
 6 | import { detectFramework } from "./detectors/detect-framework";
 7 | import { detectSystemInfo } from "./detectors/detect-system-info";
 8 | import { detectPackageManager } from "./detectors/detect-project-info";
 9 | import { betterFetch } from "@better-fetch/fetch";
10 | import type { TelemetryContext, TelemetryEvent } from "./types";
11 | import { logger } from "@better-auth/core/env";
12 | import { getTelemetryAuthConfig } from "./detectors/detect-auth-config";
13 | export { getTelemetryAuthConfig };
14 | export type { TelemetryEvent } from "./types";
15 | export async function createTelemetry(
16 | 	options: BetterAuthOptions,
17 | 	context?: TelemetryContext,
18 | ) {
19 | 	const debugEnabled =
20 | 		options.telemetry?.debug ||
21 | 		getBooleanEnvVar("BETTER_AUTH_TELEMETRY_DEBUG", false);
22 | 
23 | 	const TELEMETRY_ENDPOINT = ENV.BETTER_AUTH_TELEMETRY_ENDPOINT;
24 | 	const track = async (event: TelemetryEvent) => {
25 | 		if (context?.customTrack) {
26 | 			await context.customTrack(event).catch(logger.error);
27 | 		} else {
28 | 			if (debugEnabled) {
29 | 				logger.info("telemetry event", JSON.stringify(event, null, 2));
30 | 			} else {
31 | 				await betterFetch(TELEMETRY_ENDPOINT, {
32 | 					method: "POST",
33 | 					body: event,
34 | 				}).catch(logger.error);
35 | 			}
36 | 		}
37 | 	};
38 | 
39 | 	const isEnabled = async () => {
40 | 		const telemetryEnabled =
41 | 			options.telemetry?.enabled !== undefined
42 | 				? options.telemetry.enabled
43 | 				: false;
44 | 		const envEnabled = getBooleanEnvVar("BETTER_AUTH_TELEMETRY", false);
45 | 		return (
46 | 			(envEnabled || telemetryEnabled) && (context?.skipTestCheck || !isTest())
47 | 		);
48 | 	};
49 | 
50 | 	const enabled = await isEnabled();
51 | 	let anonymousId: string | undefined;
52 | 
53 | 	if (enabled) {
54 | 		anonymousId = await getProjectId(options.baseURL);
55 | 
56 | 		const payload = {
57 | 			config: getTelemetryAuthConfig(options),
58 | 			runtime: detectRuntime(),
59 | 			database: await detectDatabase(),
60 | 			framework: await detectFramework(),
61 | 			environment: detectEnvironment(),
62 | 			systemInfo: await detectSystemInfo(),
63 | 			packageManager: detectPackageManager(),
64 | 		};
65 | 
66 | 		void track({ type: "init", payload, anonymousId });
67 | 	}
68 | 
69 | 	return {
70 | 		publish: async (event: TelemetryEvent) => {
71 | 			if (!enabled) return;
72 | 			if (!anonymousId) {
73 | 				anonymousId = await getProjectId(options.baseURL);
74 | 			}
75 | 			await track({
76 | 				type: event.type,
77 | 				payload: event.payload,
78 | 				anonymousId,
79 | 			});
80 | 		},
81 | 	};
82 | }
83 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/integrations/svelte-kit.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { BetterAuthOptions } from "../types";
  2 | import type { BetterAuthPlugin } from "../types";
  3 | import { createAuthMiddleware } from "@better-auth/core/api";
  4 | import { parseSetCookieHeader } from "../cookies";
  5 | import type { RequestEvent } from "@sveltejs/kit";
  6 | 
  7 | export const toSvelteKitHandler = (auth: {
  8 | 	handler: (request: Request) => Response | Promise<Response>;
  9 | 	options: BetterAuthOptions;
 10 | }) => {
 11 | 	return (event: { request: Request }) => auth.handler(event.request);
 12 | };
 13 | 
 14 | export const svelteKitHandler = async ({
 15 | 	auth,
 16 | 	event,
 17 | 	resolve,
 18 | 	building,
 19 | }: {
 20 | 	auth: {
 21 | 		handler: (request: Request) => Response | Promise<Response>;
 22 | 		options: BetterAuthOptions;
 23 | 	};
 24 | 	event: RequestEvent;
 25 | 	resolve: (event: RequestEvent) => Response | Promise<Response>;
 26 | 	building: boolean;
 27 | }) => {
 28 | 	if (building) {
 29 | 		return resolve(event);
 30 | 	}
 31 | 	const { request, url } = event;
 32 | 	if (isAuthPath(url.toString(), auth.options)) {
 33 | 		return auth.handler(request);
 34 | 	}
 35 | 	return resolve(event);
 36 | };
 37 | 
 38 | export function isAuthPath(url: string, options: BetterAuthOptions) {
 39 | 	const _url = new URL(url);
 40 | 	const baseURL = new URL(
 41 | 		`${options.baseURL || _url.origin}${options.basePath || "/api/auth"}`,
 42 | 	);
 43 | 	if (_url.origin !== baseURL.origin) return false;
 44 | 	if (
 45 | 		!_url.pathname.startsWith(
 46 | 			baseURL.pathname.endsWith("/")
 47 | 				? baseURL.pathname
 48 | 				: `${baseURL.pathname}/`,
 49 | 		)
 50 | 	)
 51 | 		return false;
 52 | 	return true;
 53 | }
 54 | 
 55 | export const sveltekitCookies = (
 56 | 	getRequestEvent: () => RequestEvent<any, any>,
 57 | ) => {
 58 | 	return {
 59 | 		id: "sveltekit-cookies",
 60 | 		hooks: {
 61 | 			after: [
 62 | 				{
 63 | 					matcher() {
 64 | 						return true;
 65 | 					},
 66 | 					handler: createAuthMiddleware(async (ctx) => {
 67 | 						const returned = ctx.context.responseHeaders;
 68 | 						if ("_flag" in ctx && ctx._flag === "router") {
 69 | 							return;
 70 | 						}
 71 | 						if (returned instanceof Headers) {
 72 | 							const setCookies = returned?.get("set-cookie");
 73 | 							if (!setCookies) return;
 74 | 							const event = getRequestEvent();
 75 | 							if (!event) return;
 76 | 							const parsed = parseSetCookieHeader(setCookies);
 77 | 
 78 | 							for (const [name, { value, ...ops }] of parsed) {
 79 | 								try {
 80 | 									event.cookies.set(name, decodeURIComponent(value), {
 81 | 										sameSite: ops.samesite,
 82 | 										path: ops.path || "/",
 83 | 										expires: ops.expires,
 84 | 										secure: ops.secure,
 85 | 										httpOnly: ops.httponly,
 86 | 										domain: ops.domain,
 87 | 										maxAge: ops["max-age"],
 88 | 									});
 89 | 								} catch (e) {
 90 | 									// this will avoid any issue related to already streamed response
 91 | 								}
 92 | 							}
 93 | 						}
 94 | 					}),
 95 | 				},
 96 | 			],
 97 | 		},
 98 | 	} satisfies BetterAuthPlugin;
 99 | };
100 | 
```

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

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

--------------------------------------------------------------------------------
/demo/nextjs/app/(auth)/two-factor/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 { Input } from "@/components/ui/input";
13 | import { Label } from "@/components/ui/label";
14 | import { client } from "@/lib/auth-client";
15 | import { AlertCircle, CheckCircle2 } from "lucide-react";
16 | import Link from "next/link";
17 | import { useState } from "react";
18 | 
19 | export default function Component() {
20 | 	const [totpCode, setTotpCode] = useState("");
21 | 	const [error, setError] = useState("");
22 | 	const [success, setSuccess] = useState(false);
23 | 
24 | 	const handleSubmit = (e: React.FormEvent) => {
25 | 		e.preventDefault();
26 | 		if (totpCode.length !== 6 || !/^\d+$/.test(totpCode)) {
27 | 			setError("TOTP code must be 6 digits");
28 | 			return;
29 | 		}
30 | 		client.twoFactor
31 | 			.verifyTotp({
32 | 				code: totpCode,
33 | 			})
34 | 			.then((res) => {
35 | 				if (res.data?.token) {
36 | 					setSuccess(true);
37 | 					setError("");
38 | 				} else {
39 | 					setError("Invalid TOTP code");
40 | 				}
41 | 			});
42 | 	};
43 | 
44 | 	return (
45 | 		<main className="flex flex-col items-center justify-center min-h-[calc(100vh-10rem)]">
46 | 			<Card className="w-[350px]">
47 | 				<CardHeader>
48 | 					<CardTitle>TOTP Verification</CardTitle>
49 | 					<CardDescription>
50 | 						Enter your 6-digit TOTP code to authenticate
51 | 					</CardDescription>
52 | 				</CardHeader>
53 | 				<CardContent>
54 | 					{!success ? (
55 | 						<form onSubmit={handleSubmit}>
56 | 							<div className="space-y-2">
57 | 								<Label htmlFor="totp">TOTP Code</Label>
58 | 								<Input
59 | 									id="totp"
60 | 									type="text"
61 | 									inputMode="numeric"
62 | 									pattern="\d{6}"
63 | 									maxLength={6}
64 | 									value={totpCode}
65 | 									onChange={(e) => setTotpCode(e.target.value)}
66 | 									placeholder="Enter 6-digit code"
67 | 									required
68 | 								/>
69 | 							</div>
70 | 							{error && (
71 | 								<div className="flex items-center mt-2 text-red-500">
72 | 									<AlertCircle className="w-4 h-4 mr-2" />
73 | 									<span className="text-sm">{error}</span>
74 | 								</div>
75 | 							)}
76 | 							<Button type="submit" className="w-full mt-4">
77 | 								Verify
78 | 							</Button>
79 | 						</form>
80 | 					) : (
81 | 						<div className="flex flex-col items-center justify-center space-y-2">
82 | 							<CheckCircle2 className="w-12 h-12 text-green-500" />
83 | 							<p className="text-lg font-semibold">Verification Successful</p>
84 | 						</div>
85 | 					)}
86 | 				</CardContent>
87 | 				<CardFooter className="text-sm text-muted-foreground gap-2">
88 | 					<Link href="/two-factor/otp">
89 | 						<Button variant="link" size="sm">
90 | 							Switch to Email Verification
91 | 						</Button>
92 | 					</Link>
93 | 				</CardFooter>
94 | 			</Card>
95 | 		</main>
96 | 	);
97 | }
98 | 
```

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

```typescript
 1 | import { getAuthTables } from ".";
 2 | import { BetterAuthError } from "@better-auth/core/error";
 3 | import type { BetterAuthOptions } from "@better-auth/core";
 4 | import { createKyselyAdapter } from "../adapters/kysely-adapter/dialect";
 5 | import { kyselyAdapter } from "../adapters/kysely-adapter";
 6 | import { memoryAdapter, type MemoryDB } from "../adapters/memory-adapter";
 7 | import { logger } from "@better-auth/core/env";
 8 | import type { DBFieldAttribute } from "@better-auth/core/db";
 9 | import type { DBAdapter } from "@better-auth/core/db/adapter";
10 | 
11 | export async function getAdapter(
12 | 	options: BetterAuthOptions,
13 | ): Promise<DBAdapter<BetterAuthOptions>> {
14 | 	let adapter: DBAdapter<BetterAuthOptions>;
15 | 	if (!options.database) {
16 | 		const tables = getAuthTables(options);
17 | 		const memoryDB = Object.keys(tables).reduce<MemoryDB>((acc, key) => {
18 | 			acc[key] = [];
19 | 			return acc;
20 | 		}, {});
21 | 		logger.warn(
22 | 			"No database configuration provided. Using memory adapter in development",
23 | 		);
24 | 		adapter = memoryAdapter(memoryDB)(options);
25 | 	} else if (typeof options.database === "function") {
26 | 		adapter = options.database(options);
27 | 	} else {
28 | 		const { kysely, databaseType, transaction } =
29 | 			await createKyselyAdapter(options);
30 | 		if (!kysely) {
31 | 			throw new BetterAuthError("Failed to initialize database adapter");
32 | 		}
33 | 		adapter = kyselyAdapter(kysely, {
34 | 			type: databaseType || "sqlite",
35 | 			debugLogs:
36 | 				"debugLogs" in options.database ? options.database.debugLogs : false,
37 | 			transaction: transaction,
38 | 		})(options);
39 | 	}
40 | 	// patch for 1.3.x to ensure we have a transaction function in the adapter
41 | 	if (!adapter.transaction) {
42 | 		logger.warn(
43 | 			"Adapter does not correctly implement transaction function, patching it automatically. Please update your adapter implementation.",
44 | 		);
45 | 		adapter.transaction = async (cb) => {
46 | 			return cb(adapter);
47 | 		};
48 | 	}
49 | 	return adapter;
50 | }
51 | 
52 | export function convertToDB<T extends Record<string, any>>(
53 | 	fields: Record<string, DBFieldAttribute>,
54 | 	values: T,
55 | ) {
56 | 	let result: Record<string, any> = values.id
57 | 		? {
58 | 				id: values.id,
59 | 			}
60 | 		: {};
61 | 	for (const key in fields) {
62 | 		const field = fields[key]!;
63 | 		const value = values[key];
64 | 		if (value === undefined) {
65 | 			continue;
66 | 		}
67 | 		result[field.fieldName || key] = value;
68 | 	}
69 | 	return result as T;
70 | }
71 | 
72 | export function convertFromDB<T extends Record<string, any>>(
73 | 	fields: Record<string, DBFieldAttribute>,
74 | 	values: T | null,
75 | ) {
76 | 	if (!values) {
77 | 		return null;
78 | 	}
79 | 	let result: Record<string, any> = {
80 | 		id: values.id,
81 | 	};
82 | 	for (const [key, value] of Object.entries(fields)) {
83 | 		result[key] = values[value.fieldName || key];
84 | 	}
85 | 	return result as T;
86 | }
87 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/client/solid/index.ts:
--------------------------------------------------------------------------------

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

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

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  3 | import {
  4 | 	createAuthorizationURL,
  5 | 	refreshAccessToken,
  6 | 	validateAuthorizationCode,
  7 | } from "../oauth2";
  8 | 
  9 | export interface NotionProfile {
 10 | 	object: "user";
 11 | 	id: string;
 12 | 	type: "person" | "bot";
 13 | 	name?: string;
 14 | 	avatar_url?: string;
 15 | 	person?: {
 16 | 		email?: string;
 17 | 	};
 18 | }
 19 | 
 20 | export interface NotionOptions extends ProviderOptions<NotionProfile> {
 21 | 	clientId: string;
 22 | }
 23 | 
 24 | export const notion = (options: NotionOptions) => {
 25 | 	const tokenEndpoint = "https://api.notion.com/v1/oauth/token";
 26 | 	return {
 27 | 		id: "notion",
 28 | 		name: "Notion",
 29 | 		createAuthorizationURL({ state, scopes, loginHint, redirectURI }) {
 30 | 			const _scopes: string[] = options.disableDefaultScope ? [] : [];
 31 | 			options.scope && _scopes.push(...options.scope);
 32 | 			scopes && _scopes.push(...scopes);
 33 | 			return createAuthorizationURL({
 34 | 				id: "notion",
 35 | 				options,
 36 | 				authorizationEndpoint: "https://api.notion.com/v1/oauth/authorize",
 37 | 				scopes: _scopes,
 38 | 				state,
 39 | 				redirectURI,
 40 | 				loginHint,
 41 | 				additionalParams: {
 42 | 					owner: "user",
 43 | 				},
 44 | 			});
 45 | 		},
 46 | 		validateAuthorizationCode: async ({ code, redirectURI }) => {
 47 | 			return validateAuthorizationCode({
 48 | 				code,
 49 | 				redirectURI,
 50 | 				options,
 51 | 				tokenEndpoint,
 52 | 				authentication: "basic",
 53 | 			});
 54 | 		},
 55 | 		refreshAccessToken: options.refreshAccessToken
 56 | 			? options.refreshAccessToken
 57 | 			: async (refreshToken) => {
 58 | 					return refreshAccessToken({
 59 | 						refreshToken,
 60 | 						options: {
 61 | 							clientId: options.clientId,
 62 | 							clientKey: options.clientKey,
 63 | 							clientSecret: options.clientSecret,
 64 | 						},
 65 | 						tokenEndpoint,
 66 | 					});
 67 | 				},
 68 | 		async getUserInfo(token) {
 69 | 			if (options.getUserInfo) {
 70 | 				return options.getUserInfo(token);
 71 | 			}
 72 | 			const { data: profile, error } = await betterFetch<{
 73 | 				bot: {
 74 | 					owner: {
 75 | 						user: NotionProfile;
 76 | 					};
 77 | 				};
 78 | 			}>("https://api.notion.com/v1/users/me", {
 79 | 				headers: {
 80 | 					Authorization: `Bearer ${token.accessToken}`,
 81 | 					"Notion-Version": "2022-06-28",
 82 | 				},
 83 | 			});
 84 | 			if (error || !profile) {
 85 | 				return null;
 86 | 			}
 87 | 			const userProfile = profile.bot?.owner?.user;
 88 | 			if (!userProfile) {
 89 | 				return null;
 90 | 			}
 91 | 			const userMap = await options.mapProfileToUser?.(userProfile);
 92 | 			return {
 93 | 				user: {
 94 | 					id: userProfile.id,
 95 | 					name: userProfile.name || "Notion User",
 96 | 					email: userProfile.person?.email || null,
 97 | 					image: userProfile.avatar_url,
 98 | 					emailVerified: !!userProfile.person?.email,
 99 | 					...userMap,
100 | 				},
101 | 				data: userProfile,
102 | 			};
103 | 		},
104 | 		options,
105 | 	} satisfies OAuthProvider<NotionProfile>;
106 | };
107 | 
```

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

```markdown
  1 | ---
  2 | title: MS SQL
  3 | description: Integrate Better Auth with MS SQL.
  4 | ---
  5 | 
  6 | Microsoft SQL Server is a relational database management system developed by Microsoft, designed for enterprise-level data storage, management, and analytics with robust security and scalability features.
  7 | Read more [here](https://en.wikipedia.org/wiki/Microsoft_SQL_Server).
  8 | 
  9 | ## Example Usage
 10 | 
 11 | Make sure you have MS SQL installed and configured.
 12 | Then, you can connect it straight into Better Auth.
 13 | 
 14 | ```ts title="auth.ts"
 15 | import { betterAuth } from "better-auth";
 16 | import { MssqlDialect } from "kysely";
 17 | import * as Tedious from 'tedious'
 18 | import * as Tarn from 'tarn'
 19 | 
 20 | const dialect = new MssqlDialect({
 21 |   tarn: {
 22 |     ...Tarn,
 23 |     options: {
 24 |       min: 0,
 25 |       max: 10,
 26 |     },
 27 |   },
 28 |   tedious: {
 29 |     ...Tedious,
 30 |     connectionFactory: () => new Tedious.Connection({
 31 |       authentication: {
 32 |         options: {
 33 |           password: 'password',
 34 |           userName: 'username',
 35 |         },
 36 |         type: 'default',
 37 |       },
 38 |       options: {
 39 |         database: 'some_db',
 40 |         port: 1433,
 41 |         trustServerCertificate: true,
 42 |       },
 43 |       server: 'localhost',
 44 |     }),
 45 |   },
 46 |   TYPES: {
 47 | 		...Tedious.TYPES,
 48 | 		DateTime: Tedious.TYPES.DateTime2,
 49 | 	},
 50 | })
 51 | 
 52 | export const auth = betterAuth({
 53 |   database: {
 54 |     dialect,
 55 |     type: "mssql"
 56 |   }
 57 | });
 58 | 
 59 | 
 60 | ```
 61 | <Callout>
 62 |     For more information, read Kysely's documentation to the [MssqlDialect](https://kysely-org.github.io/kysely-apidoc/classes/MssqlDialect.html).
 63 | </Callout>
 64 | 
 65 | ## Schema generation & migration
 66 | 
 67 | The [Better Auth CLI](/docs/concepts/cli) allows you to generate or migrate
 68 | your database schema based on your Better Auth configuration and plugins.
 69 | 
 70 | <table>
 71 |   <thead>
 72 |     <tr className="border-b">
 73 |       <th>
 74 |         <p className="font-bold text-[16px] mb-1">MS SQL Schema Generation</p>
 75 |       </th>
 76 |       <th>
 77 |         <p className="font-bold text-[16px] mb-1">MS SQL Schema Migration</p>
 78 |       </th>
 79 |     </tr>
 80 |   </thead>
 81 |   <tbody>
 82 |     <tr className="h-10">
 83 |       <td>✅ Supported</td>
 84 |       <td>✅ Supported</td>
 85 |     </tr>
 86 |   </tbody>
 87 | </table>
 88 | 
 89 | ```bash title="Schema Generation"
 90 | npx @better-auth/cli@latest generate
 91 | ```
 92 | 
 93 | ```bash title="Schema Migration"
 94 | npx @better-auth/cli@latest migrate
 95 | ```
 96 | 
 97 | ## Additional Information
 98 | 
 99 | MS SQL is supported under the hood via the [Kysely](https://kysely.dev/) adapter, any database supported by Kysely would also be supported. (<Link href="/docs/adapters/other-relational-databases">Read more here</Link>)
100 | 
101 | If you're looking for performance improvements or tips, take a look at our guide to <Link href="/docs/guides/optimizing-for-performance">performance optimizations</Link>.
102 | 
```

--------------------------------------------------------------------------------
/packages/cli/test/__snapshots__/auth-schema-number-id.txt:
--------------------------------------------------------------------------------

```
 1 | import {
 2 |   pgTable,
 3 |   text,
 4 |   timestamp,
 5 |   boolean,
 6 |   integer,
 7 |   serial,
 8 | } from "drizzle-orm/pg-core";
 9 | 
10 | export const custom_user = pgTable("custom_user", {
11 |   id: serial("id").primaryKey(),
12 |   name: text("name").notNull(),
13 |   email: text("email").notNull().unique(),
14 |   emailVerified: boolean("email_verified").default(false).notNull(),
15 |   image: text("image"),
16 |   createdAt: timestamp("created_at").defaultNow().notNull(),
17 |   updatedAt: timestamp("updated_at")
18 |     .defaultNow()
19 |     .$onUpdate(() => /* @__PURE__ */ new Date())
20 |     .notNull(),
21 |   twoFactorEnabled: boolean("two_factor_enabled").default(false),
22 |   username: text("username").unique(),
23 |   displayUsername: text("display_username"),
24 | });
25 | 
26 | export const custom_session = pgTable("custom_session", {
27 |   id: serial("id").primaryKey(),
28 |   expiresAt: timestamp("expires_at").notNull(),
29 |   token: text("token").notNull().unique(),
30 |   createdAt: timestamp("created_at").defaultNow().notNull(),
31 |   updatedAt: timestamp("updated_at")
32 |     .$onUpdate(() => /* @__PURE__ */ new Date())
33 |     .notNull(),
34 |   ipAddress: text("ip_address"),
35 |   userAgent: text("user_agent"),
36 |   userId: integer("user_id")
37 |     .notNull()
38 |     .references(() => custom_user.id, { onDelete: "cascade" }),
39 | });
40 | 
41 | export const custom_account = pgTable("custom_account", {
42 |   id: serial("id").primaryKey(),
43 |   accountId: text("account_id").notNull(),
44 |   providerId: text("provider_id").notNull(),
45 |   userId: integer("user_id")
46 |     .notNull()
47 |     .references(() => custom_user.id, { onDelete: "cascade" }),
48 |   accessToken: text("access_token"),
49 |   refreshToken: text("refresh_token"),
50 |   idToken: text("id_token"),
51 |   accessTokenExpiresAt: timestamp("access_token_expires_at"),
52 |   refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
53 |   scope: text("scope"),
54 |   password: text("password"),
55 |   createdAt: timestamp("created_at").defaultNow().notNull(),
56 |   updatedAt: timestamp("updated_at")
57 |     .$onUpdate(() => /* @__PURE__ */ new Date())
58 |     .notNull(),
59 | });
60 | 
61 | export const custom_verification = pgTable("custom_verification", {
62 |   id: serial("id").primaryKey(),
63 |   identifier: text("identifier").notNull(),
64 |   value: text("value").notNull(),
65 |   expiresAt: timestamp("expires_at").notNull(),
66 |   createdAt: timestamp("created_at").defaultNow().notNull(),
67 |   updatedAt: timestamp("updated_at")
68 |     .defaultNow()
69 |     .$onUpdate(() => /* @__PURE__ */ new Date())
70 |     .notNull(),
71 | });
72 | 
73 | export const twoFactor = pgTable("two_factor", {
74 |   id: serial("id").primaryKey(),
75 |   secret: text("secret").notNull(),
76 |   backupCodes: text("backup_codes").notNull(),
77 |   userId: integer("user_id")
78 |     .notNull()
79 |     .references(() => custom_user.id, { onDelete: "cascade" }),
80 | });
81 | 
```

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

```typescript
 1 | import { getEndpoints, router } from "./api";
 2 | import { init } from "./init";
 3 | import type { BetterAuthOptions } from "@better-auth/core";
 4 | import type {
 5 | 	InferPluginErrorCodes,
 6 | 	InferPluginTypes,
 7 | 	InferSession,
 8 | 	InferUser,
 9 | 	InferAPI,
10 | } from "./types";
11 | import type { PrettifyDeep, Expand } from "./types/helper";
12 | import { getBaseURL, getOrigin } from "./utils/url";
13 | import { BASE_ERROR_CODES } from "@better-auth/core/error";
14 | import { BetterAuthError } from "@better-auth/core/error";
15 | import { runWithAdapter } from "@better-auth/core/context";
16 | import type { AuthContext } from "@better-auth/core";
17 | 
18 | export type WithJsDoc<T, D> = Expand<T & D>;
19 | 
20 | export const betterAuth = <Options extends BetterAuthOptions>(
21 | 	options: Options &
22 | 		// fixme(alex): do we need Record<never, never> here?
23 | 		Record<never, never>,
24 | ): Auth<Options> => {
25 | 	const authContext = init(options);
26 | 	const { api } = getEndpoints(authContext, options);
27 | 	const errorCodes = options.plugins?.reduce((acc, plugin) => {
28 | 		if (plugin.$ERROR_CODES) {
29 | 			return {
30 | 				...acc,
31 | 				...plugin.$ERROR_CODES,
32 | 			};
33 | 		}
34 | 		return acc;
35 | 	}, {});
36 | 	return {
37 | 		handler: async (request: Request) => {
38 | 			const ctx = await authContext;
39 | 			const basePath = ctx.options.basePath || "/api/auth";
40 | 			if (!ctx.options.baseURL) {
41 | 				const baseURL = getBaseURL(undefined, basePath, request);
42 | 				if (baseURL) {
43 | 					ctx.baseURL = baseURL;
44 | 					ctx.options.baseURL = getOrigin(ctx.baseURL) || undefined;
45 | 				} else {
46 | 					throw new BetterAuthError(
47 | 						"Could not get base URL from request. Please provide a valid base URL.",
48 | 					);
49 | 				}
50 | 			}
51 | 			ctx.trustedOrigins = [
52 | 				...(options.trustedOrigins
53 | 					? Array.isArray(options.trustedOrigins)
54 | 						? options.trustedOrigins
55 | 						: await options.trustedOrigins(request)
56 | 					: []),
57 | 				ctx.options.baseURL!,
58 | 			];
59 | 			const { handler } = router(ctx, options);
60 | 			return runWithAdapter(ctx.adapter, () => handler(request));
61 | 		},
62 | 		api,
63 | 		options: options,
64 | 		$context: authContext,
65 | 		$ERROR_CODES: {
66 | 			...errorCodes,
67 | 			...BASE_ERROR_CODES,
68 | 		},
69 | 	} as any;
70 | };
71 | 
72 | export type Auth<Options extends BetterAuthOptions = BetterAuthOptions> = {
73 | 	handler: (request: Request) => Promise<Response>;
74 | 	api: InferAPI<ReturnType<typeof router<Options>>["endpoints"]>;
75 | 	options: Options;
76 | 	$ERROR_CODES: InferPluginErrorCodes<Options> & typeof BASE_ERROR_CODES;
77 | 	$context: Promise<AuthContext>;
78 | 	/**
79 | 	 * Share types
80 | 	 */
81 | 	$Infer: InferPluginTypes<Options> extends {
82 | 		Session: any;
83 | 	}
84 | 		? InferPluginTypes<Options>
85 | 		: {
86 | 				Session: {
87 | 					session: PrettifyDeep<InferSession<Options>>;
88 | 					user: PrettifyDeep<InferUser<Options>>;
89 | 				};
90 | 			} & InferPluginTypes<Options>;
91 | };
92 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/app/device/page.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | "use client";
 2 | 
 3 | import { useState, useTransition } from "react";
 4 | import { useRouter, useSearchParams } from "next/navigation";
 5 | import { client } from "@/lib/auth-client";
 6 | import { Card } from "@/components/ui/card";
 7 | import { Input } from "@/components/ui/input";
 8 | import { Button } from "@/components/ui/button";
 9 | import { Label } from "@/components/ui/label";
10 | import { Alert, AlertDescription } from "@/components/ui/alert";
11 | import { Loader2 } from "lucide-react";
12 | 
13 | export default function DeviceAuthorizationPage() {
14 | 	const router = useRouter();
15 | 	const params = useSearchParams();
16 | 	const user_code = params.get("user_code");
17 | 	const [userCode, setUserCode] = useState<string>(user_code ? user_code : "");
18 | 	const [isPending, startTransition] = useTransition();
19 | 	const [error, setError] = useState<string | null>(null);
20 | 
21 | 	const handleSubmit = (e: React.FormEvent) => {
22 | 		e.preventDefault();
23 | 		setError(null);
24 | 
25 | 		startTransition(async () => {
26 | 			try {
27 | 				const finalCode = userCode.trim().replaceAll(/-/g, "").toUpperCase();
28 | 				// Get the device authorization status
29 | 				const response = await client.device({
30 | 					query: {
31 | 						user_code: finalCode,
32 | 					},
33 | 				});
34 | 
35 | 				if (response.data) {
36 | 					router.push(`/device/approve?user_code=${finalCode}`);
37 | 				}
38 | 			} catch (err: any) {
39 | 				setError(
40 | 					err.error?.message || "Invalid code. Please check and try again.",
41 | 				);
42 | 			}
43 | 		});
44 | 	};
45 | 
46 | 	return (
47 | 		<div className="flex min-h-screen items-center justify-center p-4">
48 | 			<Card className="w-full max-w-md p-6">
49 | 				<div className="space-y-4">
50 | 					<div className="text-center">
51 | 						<h1 className="text-2xl font-bold">Device Authorization</h1>
52 | 						<p className="text-muted-foreground mt-2">
53 | 							Enter the code displayed on your device
54 | 						</p>
55 | 					</div>
56 | 
57 | 					<form onSubmit={handleSubmit} className="space-y-4">
58 | 						<div className="space-y-2">
59 | 							<Label htmlFor="userCode">Device Code</Label>
60 | 							<Input
61 | 								id="userCode"
62 | 								type="text"
63 | 								placeholder="XXXX-XXXX"
64 | 								value={userCode}
65 | 								onChange={(e) => setUserCode(e.target.value)}
66 | 								className="text-center text-lg font-mono uppercase"
67 | 								maxLength={9}
68 | 								disabled={isPending}
69 | 								required
70 | 							/>
71 | 						</div>
72 | 
73 | 						{error && (
74 | 							<Alert variant="destructive">
75 | 								<AlertDescription>{error}</AlertDescription>
76 | 							</Alert>
77 | 						)}
78 | 
79 | 						<Button type="submit" className="w-full" disabled={isPending}>
80 | 							{isPending ? (
81 | 								<>
82 | 									<Loader2 className="mr-2 h-4 w-4 animate-spin" />
83 | 									Verifying...
84 | 								</>
85 | 							) : (
86 | 								"Continue"
87 | 							)}
88 | 						</Button>
89 | 					</form>
90 | 				</div>
91 | 			</Card>
92 | 		</div>
93 | 	);
94 | }
95 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/api-key/routes/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { apiKeySchema } from "../schema";
  2 | import type { ApiKey, ApiKeyOptions } from "../types";
  3 | import { createApiKey } from "./create-api-key";
  4 | import { deleteApiKey } from "./delete-api-key";
  5 | import { getApiKey } from "./get-api-key";
  6 | import { updateApiKey } from "./update-api-key";
  7 | import { verifyApiKey } from "./verify-api-key";
  8 | import { listApiKeys } from "./list-api-keys";
  9 | import { deleteAllExpiredApiKeysEndpoint } from "./delete-all-expired-api-keys";
 10 | import { API_KEY_TABLE_NAME } from "..";
 11 | import type { AuthContext } from "@better-auth/core";
 12 | 
 13 | export type PredefinedApiKeyOptions = ApiKeyOptions &
 14 | 	Required<
 15 | 		Pick<
 16 | 			ApiKeyOptions,
 17 | 			| "apiKeyHeaders"
 18 | 			| "defaultKeyLength"
 19 | 			| "keyExpiration"
 20 | 			| "rateLimit"
 21 | 			| "maximumPrefixLength"
 22 | 			| "minimumPrefixLength"
 23 | 			| "maximumNameLength"
 24 | 			| "disableKeyHashing"
 25 | 			| "minimumNameLength"
 26 | 			| "requireName"
 27 | 			| "enableMetadata"
 28 | 			| "enableSessionForAPIKeys"
 29 | 			| "startingCharactersConfig"
 30 | 		>
 31 | 	> & {
 32 | 		keyExpiration: Required<ApiKeyOptions["keyExpiration"]>;
 33 | 		startingCharactersConfig: Required<
 34 | 			ApiKeyOptions["startingCharactersConfig"]
 35 | 		>;
 36 | 	};
 37 | 
 38 | let lastChecked: Date | null = null;
 39 | 
 40 | export async function deleteAllExpiredApiKeys(
 41 | 	ctx: AuthContext,
 42 | 	byPassLastCheckTime = false,
 43 | ): Promise<void> {
 44 | 	if (lastChecked && !byPassLastCheckTime) {
 45 | 		const now = new Date();
 46 | 		const diff = now.getTime() - lastChecked.getTime();
 47 | 		if (diff < 10000) {
 48 | 			return;
 49 | 		}
 50 | 	}
 51 | 	lastChecked = new Date();
 52 | 	await ctx.adapter
 53 | 		.deleteMany({
 54 | 			model: API_KEY_TABLE_NAME,
 55 | 			where: [
 56 | 				{
 57 | 					field: "expiresAt" satisfies keyof ApiKey,
 58 | 					operator: "lt",
 59 | 					value: new Date(),
 60 | 				},
 61 | 				{
 62 | 					field: "expiresAt",
 63 | 					operator: "ne",
 64 | 					value: null,
 65 | 				},
 66 | 			],
 67 | 		})
 68 | 		.catch((error) => {
 69 | 			ctx.logger.error(`Failed to delete expired API keys:`, error);
 70 | 		});
 71 | }
 72 | 
 73 | export function createApiKeyRoutes({
 74 | 	keyGenerator,
 75 | 	opts,
 76 | 	schema,
 77 | }: {
 78 | 	keyGenerator: (options: {
 79 | 		length: number;
 80 | 		prefix: string | undefined;
 81 | 	}) => Promise<string> | string;
 82 | 	opts: PredefinedApiKeyOptions;
 83 | 	schema: ReturnType<typeof apiKeySchema>;
 84 | }) {
 85 | 	return {
 86 | 		createApiKey: createApiKey({
 87 | 			keyGenerator,
 88 | 			opts,
 89 | 			schema,
 90 | 			deleteAllExpiredApiKeys,
 91 | 		}),
 92 | 		verifyApiKey: verifyApiKey({ opts, schema, deleteAllExpiredApiKeys }),
 93 | 		getApiKey: getApiKey({ opts, schema, deleteAllExpiredApiKeys }),
 94 | 		updateApiKey: updateApiKey({ opts, schema, deleteAllExpiredApiKeys }),
 95 | 		deleteApiKey: deleteApiKey({ opts, schema, deleteAllExpiredApiKeys }),
 96 | 		listApiKeys: listApiKeys({ opts, schema, deleteAllExpiredApiKeys }),
 97 | 		deleteAllExpiredApiKeys: deleteAllExpiredApiKeysEndpoint({
 98 | 			deleteAllExpiredApiKeys,
 99 | 		}),
100 | 	};
101 | }
102 | 
```

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

```typescript
  1 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  2 | import { logger } from "../env";
  3 | import {
  4 | 	createAuthorizationURL,
  5 | 	validateAuthorizationCode,
  6 | 	refreshAccessToken,
  7 | } from "../oauth2";
  8 | import { decodeJwt } from "jose";
  9 | 
 10 | /**
 11 |  * @see https://dev.twitch.tv/docs/authentication/getting-tokens-oidc/#requesting-claims
 12 |  */
 13 | export interface TwitchProfile {
 14 | 	/**
 15 | 	 * The sub of the user
 16 | 	 */
 17 | 	sub: string;
 18 | 	/**
 19 | 	 * The preferred username of the user
 20 | 	 */
 21 | 	preferred_username: string;
 22 | 	/**
 23 | 	 * The email of the user
 24 | 	 */
 25 | 	email: string;
 26 | 	/**
 27 | 	 * Indicate if this user has a verified email.
 28 | 	 */
 29 | 	email_verified: boolean;
 30 | 	/**
 31 | 	 * The picture of the user
 32 | 	 */
 33 | 	picture: string;
 34 | }
 35 | 
 36 | export interface TwitchOptions extends ProviderOptions<TwitchProfile> {
 37 | 	clientId: string;
 38 | 	claims?: string[];
 39 | }
 40 | export const twitch = (options: TwitchOptions) => {
 41 | 	return {
 42 | 		id: "twitch",
 43 | 		name: "Twitch",
 44 | 		createAuthorizationURL({ state, scopes, redirectURI }) {
 45 | 			const _scopes = options.disableDefaultScope
 46 | 				? []
 47 | 				: ["user:read:email", "openid"];
 48 | 			options.scope && _scopes.push(...options.scope);
 49 | 			scopes && _scopes.push(...scopes);
 50 | 			return createAuthorizationURL({
 51 | 				id: "twitch",
 52 | 				redirectURI,
 53 | 				options,
 54 | 				authorizationEndpoint: "https://id.twitch.tv/oauth2/authorize",
 55 | 				scopes: _scopes,
 56 | 				state,
 57 | 				claims: options.claims || [
 58 | 					"email",
 59 | 					"email_verified",
 60 | 					"preferred_username",
 61 | 					"picture",
 62 | 				],
 63 | 			});
 64 | 		},
 65 | 		validateAuthorizationCode: async ({ code, redirectURI }) => {
 66 | 			return validateAuthorizationCode({
 67 | 				code,
 68 | 				redirectURI,
 69 | 				options,
 70 | 				tokenEndpoint: "https://id.twitch.tv/oauth2/token",
 71 | 			});
 72 | 		},
 73 | 		refreshAccessToken: options.refreshAccessToken
 74 | 			? options.refreshAccessToken
 75 | 			: async (refreshToken) => {
 76 | 					return refreshAccessToken({
 77 | 						refreshToken,
 78 | 						options: {
 79 | 							clientId: options.clientId,
 80 | 							clientKey: options.clientKey,
 81 | 							clientSecret: options.clientSecret,
 82 | 						},
 83 | 						tokenEndpoint: "https://id.twitch.tv/oauth2/token",
 84 | 					});
 85 | 				},
 86 | 		async getUserInfo(token) {
 87 | 			if (options.getUserInfo) {
 88 | 				return options.getUserInfo(token);
 89 | 			}
 90 | 			const idToken = token.idToken;
 91 | 			if (!idToken) {
 92 | 				logger.error("No idToken found in token");
 93 | 				return null;
 94 | 			}
 95 | 			const profile = decodeJwt(idToken) as TwitchProfile;
 96 | 			const userMap = await options.mapProfileToUser?.(profile);
 97 | 			return {
 98 | 				user: {
 99 | 					id: profile.sub,
100 | 					name: profile.preferred_username,
101 | 					email: profile.email,
102 | 					image: profile.picture,
103 | 					emailVerified: profile.email_verified,
104 | 					...userMap,
105 | 				},
106 | 				data: profile,
107 | 			};
108 | 		},
109 | 		options,
110 | 	} satisfies OAuthProvider<TwitchProfile>;
111 | };
112 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/api-key/routes/delete-api-key.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as z from "zod";
  2 | import { APIError, sessionMiddleware } from "../../../api";
  3 | import { ERROR_CODES } from "..";
  4 | import type { apiKeySchema } from "../schema";
  5 | import type { ApiKey } from "../types";
  6 | import type { PredefinedApiKeyOptions } from ".";
  7 | import { API_KEY_TABLE_NAME } from "..";
  8 | import type { AuthContext } from "@better-auth/core";
  9 | import { createAuthEndpoint } from "@better-auth/core/api";
 10 | export function deleteApiKey({
 11 | 	opts,
 12 | 	schema,
 13 | 	deleteAllExpiredApiKeys,
 14 | }: {
 15 | 	opts: PredefinedApiKeyOptions;
 16 | 	schema: ReturnType<typeof apiKeySchema>;
 17 | 	deleteAllExpiredApiKeys(
 18 | 		ctx: AuthContext,
 19 | 		byPassLastCheckTime?: boolean,
 20 | 	): void;
 21 | }) {
 22 | 	return createAuthEndpoint(
 23 | 		"/api-key/delete",
 24 | 		{
 25 | 			method: "POST",
 26 | 			body: z.object({
 27 | 				keyId: z.string().meta({
 28 | 					description: "The id of the Api Key",
 29 | 				}),
 30 | 			}),
 31 | 			use: [sessionMiddleware],
 32 | 			metadata: {
 33 | 				openapi: {
 34 | 					description: "Delete an existing API key",
 35 | 					requestBody: {
 36 | 						content: {
 37 | 							"application/json": {
 38 | 								schema: {
 39 | 									type: "object",
 40 | 									properties: {
 41 | 										keyId: {
 42 | 											type: "string",
 43 | 											description: "The id of the API key to delete",
 44 | 										},
 45 | 									},
 46 | 									required: ["keyId"],
 47 | 								},
 48 | 							},
 49 | 						},
 50 | 					},
 51 | 					responses: {
 52 | 						"200": {
 53 | 							description: "API key deleted successfully",
 54 | 							content: {
 55 | 								"application/json": {
 56 | 									schema: {
 57 | 										type: "object",
 58 | 										properties: {
 59 | 											success: {
 60 | 												type: "boolean",
 61 | 												description:
 62 | 													"Indicates if the API key was successfully deleted",
 63 | 											},
 64 | 										},
 65 | 										required: ["success"],
 66 | 									},
 67 | 								},
 68 | 							},
 69 | 						},
 70 | 					},
 71 | 				},
 72 | 			},
 73 | 		},
 74 | 		async (ctx) => {
 75 | 			const { keyId } = ctx.body;
 76 | 			const session = ctx.context.session;
 77 | 			if (session.user.banned === true) {
 78 | 				throw new APIError("UNAUTHORIZED", {
 79 | 					message: ERROR_CODES.USER_BANNED,
 80 | 				});
 81 | 			}
 82 | 			const apiKey = await ctx.context.adapter.findOne<ApiKey>({
 83 | 				model: API_KEY_TABLE_NAME,
 84 | 				where: [
 85 | 					{
 86 | 						field: "id",
 87 | 						value: keyId,
 88 | 					},
 89 | 				],
 90 | 			});
 91 | 
 92 | 			if (!apiKey || apiKey.userId !== session.user.id) {
 93 | 				throw new APIError("NOT_FOUND", {
 94 | 					message: ERROR_CODES.KEY_NOT_FOUND,
 95 | 				});
 96 | 			}
 97 | 
 98 | 			try {
 99 | 				await ctx.context.adapter.delete<ApiKey>({
100 | 					model: API_KEY_TABLE_NAME,
101 | 					where: [
102 | 						{
103 | 							field: "id",
104 | 							value: apiKey.id,
105 | 						},
106 | 					],
107 | 				});
108 | 			} catch (error: any) {
109 | 				throw new APIError("INTERNAL_SERVER_ERROR", {
110 | 					message: error?.message,
111 | 				});
112 | 			}
113 | 			deleteAllExpiredApiKeys(ctx.context);
114 | 			return ctx.json({
115 | 				success: true,
116 | 			});
117 | 		},
118 | 	);
119 | }
120 | 
```

--------------------------------------------------------------------------------
/demo/expo-example/src/components/ui/button.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import { cva, type VariantProps } from "class-variance-authority";
 2 | import * as React from "react";
 3 | import { Pressable } from "react-native";
 4 | import { cn } from "@/lib/utils";
 5 | import { TextClassContext } from "@/components/ui/text";
 6 | 
 7 | const buttonVariants = cva(
 8 | 	"group flex items-center justify-center rounded-md web:ring-offset-background web:transition-colors web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2",
 9 | 	{
10 | 		variants: {
11 | 			variant: {
12 | 				default: "bg-primary web:hover:opacity-90 active:opacity-90",
13 | 				destructive: "bg-destructive web:hover:opacity-90 active:opacity-90",
14 | 				outline:
15 | 					"border border-input bg-background web:hover:bg-accent web:hover:text-accent-foreground active:bg-accent",
16 | 				secondary: "bg-secondary web:hover:opacity-80 active:opacity-80",
17 | 				ghost:
18 | 					"web:hover:bg-accent web:hover:text-accent-foreground active:bg-accent",
19 | 				link: "web:underline-offset-4 web:hover:underline web:focus:underline ",
20 | 			},
21 | 			size: {
22 | 				default: "h-10 px-4 py-2 native:h-12 native:px-5 native:py-3",
23 | 				sm: "h-9 rounded-md px-3",
24 | 				lg: "h-11 rounded-md px-8 native:h-14",
25 | 				icon: "h-10 w-10",
26 | 			},
27 | 		},
28 | 		defaultVariants: {
29 | 			variant: "default",
30 | 			size: "default",
31 | 		},
32 | 	},
33 | );
34 | 
35 | const buttonTextVariants = cva(
36 | 	"web:whitespace-nowrap text-sm native:text-base font-medium text-foreground web:transition-colors",
37 | 	{
38 | 		variants: {
39 | 			variant: {
40 | 				default: "text-primary-foreground",
41 | 				destructive: "text-destructive-foreground",
42 | 				outline: "group-active:text-accent-foreground",
43 | 				secondary:
44 | 					"text-secondary-foreground group-active:text-secondary-foreground",
45 | 				ghost: "group-active:text-accent-foreground",
46 | 				link: "text-primary group-active:underline",
47 | 			},
48 | 			size: {
49 | 				default: "",
50 | 				sm: "",
51 | 				lg: "native:text-lg",
52 | 				icon: "",
53 | 			},
54 | 		},
55 | 		defaultVariants: {
56 | 			variant: "default",
57 | 			size: "default",
58 | 		},
59 | 	},
60 | );
61 | 
62 | type ButtonProps = React.ComponentPropsWithoutRef<typeof Pressable> &
63 | 	VariantProps<typeof buttonVariants>;
64 | 
65 | const Button = React.forwardRef<
66 | 	React.ElementRef<typeof Pressable>,
67 | 	ButtonProps
68 | >(({ className, variant, size, ...props }, ref) => {
69 | 	return (
70 | 		<TextClassContext.Provider
71 | 			value={buttonTextVariants({
72 | 				variant,
73 | 				size,
74 | 				className: "web:pointer-events-none",
75 | 			})}
76 | 		>
77 | 			<Pressable
78 | 				className={cn(
79 | 					props.disabled && "opacity-50 web:pointer-events-none",
80 | 					buttonVariants({ variant, size, className }),
81 | 				)}
82 | 				ref={ref}
83 | 				role="button"
84 | 				{...props}
85 | 			/>
86 | 		</TextClassContext.Provider>
87 | 	);
88 | });
89 | Button.displayName = "Button";
90 | 
91 | export { Button, buttonTextVariants, buttonVariants };
92 | export type { ButtonProps };
93 | 
```
Page 8/69FirstPrevNextLast