{"id":28127885,"url":"https://github.com/zpg6/better-auth-cloudflare","last_synced_at":"2026-01-21T13:25:27.829Z","repository":{"id":292435547,"uuid":"979632115","full_name":"zpg6/better-auth-cloudflare","owner":"zpg6","description":"Seamlessly integrate better-auth with Cloudflare Workers, D1, Hyperdrive, KV, R2, and geolocation services. CLI for project generation, automated resource provisioning on Cloudflare, and database migrations. Supports Next.js, Hono, and more!","archived":false,"fork":false,"pushed_at":"2025-12-06T13:00:40.000Z","size":497,"stargazers_count":426,"open_issues_count":12,"forks_count":28,"subscribers_count":8,"default_branch":"main","last_synced_at":"2026-01-19T22:56:11.668Z","etag":null,"topics":["better-auth","cf","cloudflare","d1","drizzle","drizzle-orm","hyperdrive","nextjs","opennextjs","r2","workers","workers-d1","workers-kv"],"latest_commit_sha":null,"homepage":"https://better-auth-cloudflare.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zpg6.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-05-07T20:25:25.000Z","updated_at":"2026-01-19T07:49:35.000Z","dependencies_parsed_at":null,"dependency_job_id":"320c026e-aec8-433e-9491-8fffd9230d0c","html_url":"https://github.com/zpg6/better-auth-cloudflare","commit_stats":null,"previous_names":["zpg6/better-auth-cloudflare"],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/zpg6/better-auth-cloudflare","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zpg6%2Fbetter-auth-cloudflare","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zpg6%2Fbetter-auth-cloudflare/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zpg6%2Fbetter-auth-cloudflare/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zpg6%2Fbetter-auth-cloudflare/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zpg6","download_url":"https://codeload.github.com/zpg6/better-auth-cloudflare/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zpg6%2Fbetter-auth-cloudflare/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28633757,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-21T04:47:28.174Z","status":"ssl_error","status_checked_at":"2026-01-21T04:47:22.943Z","response_time":86,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["better-auth","cf","cloudflare","d1","drizzle","drizzle-orm","hyperdrive","nextjs","opennextjs","r2","workers","workers-d1","workers-kv"],"created_at":"2025-05-14T11:24:02.669Z","updated_at":"2026-01-21T13:25:27.817Z","avatar_url":"https://github.com/zpg6.png","language":"TypeScript","funding_links":[],"categories":["TypeScript","Plugins"],"sub_categories":[],"readme":"# better-auth-cloudflare\n\nSeamlessly integrate [Better Auth](https://github.com/better-auth/better-auth) with Cloudflare Workers, D1, Hyperdrive, KV, R2, and geolocation services.\n\n[![NPM Version](https://img.shields.io/npm/v/better-auth-cloudflare)](https://www.npmjs.com/package/better-auth-cloudflare)\n[![NPM Downloads](https://img.shields.io/npm/dt/better-auth-cloudflare)](https://www.npmjs.com/package/better-auth-cloudflare)\n[![License: MIT](https://img.shields.io/npm/l/better-auth-cloudflare)](https://opensource.org/licenses/MIT)\n\n**LIVE DEMOS**:\n\n- **OpenNextJS**: [https://better-auth-cloudflare.zpg6.workers.dev](https://better-auth-cloudflare.zpg6.workers.dev/)\n- **Hono**: [https://better-auth-cloudflare-hono.zpg6.workers.dev](https://better-auth-cloudflare-hono.zpg6.workers.dev/)\n\nDemo implementations are available in the [`examples/`](./examples/) directory for **OpenNextJS ◆** and **Hono 🔥**, along with recommended scripts for generating database schema, migrating, and more. The library is compatible with any framework that runs on Cloudflare Workers.\n\n## Features\n\n- 🗄️ **Database Integration**: Support for D1 (SQLite), Postgres, and MySQL databases via Drizzle ORM.\n- 🚀 **Hyperdrive Support**: Connect to Postgres and MySQL databases through Cloudflare Hyperdrive.\n- 🔌 **KV Storage Integration**: Optionally use Cloudflare KV for secondary storage (e.g., session caching).\n- 📁 **R2 File Storage**: Upload, download, and manage user files with Cloudflare R2 object storage and database tracking.\n- 📍 **Automatic Geolocation Tracking**: Enrich user sessions with location data derived from Cloudflare.\n- 🌐 **Cloudflare IP Detection**: Utilize Cloudflare's IP detection headers out-of-the-box.\n- 🔍 **Rich Client-Side Context**: Access timezone, city, country, region, and more via the client plugin.\n- 📦 **CLI**: Tools for getting started quickly with Hono or Next.js, managing database schema, and more.\n\n## Roadmap\n\n- [x] IP Detection\n- [x] Geolocation\n- [x] D1\n- [x] Hyperdrive (Postgres/MySQL)\n- [x] KV\n- [x] R2\n- [ ] Cloudflare Email\n- [ ] Cloudflare Images\n- [ ] Durable Objects\n- [ ] D1 Multi-Tenancy\n\n**CLI:**\n\n- [x] `generate` - Create new projects from Hono/Next.js templates with automatic Cloudflare resource setup\n- [ ] `integrate` - Add `better-auth-cloudflare` to existing projects, creating/updating auth and schema files\n- [x] `migrate` - Update auth schema and run database migrations when configuration changes\n- [ ] `plugin` - Generate empty Better Auth plugin for quickly adding typesafe endpoints and schema fields\n- [x] `version` - Check the version of the CLI\n- [x] `help` - Show all commands and their usage\n\n**Examples:**\n\n- [x] Hono\n- [x] OpenNextJS\n- [ ] SvelteKit (+ Hyperdrive)\n- [ ] TanStack Start (+ Durable Objects)\n\n## Table of Contents\n\n- [Quick Start with CLI](#quick-start-with-cli)\n- [Configuration Options](#configuration-options)\n- [Manual Installation](#manual-installation)\n- [Manual Setup](#manual-setup)\n    - [1. Define Your Database Schema (`src/db/schema.ts`)](#1-define-your-database-schema-srcdbschemats)\n    - [2. Initialize Drizzle ORM (`src/db/index.ts`)](#2-initialize-drizzle-orm-srcdbindexts)\n    - [3. Configure Better Auth (`src/auth/index.ts`)](#3-configure-better-auth-srcauthindexts)\n    - [4. Generate and Manage Auth Schema](#4-generate-and-manage-auth-schema)\n    - [5. Configure KV as Secondary Storage (Optional)](#5-configure-kv-as-secondary-storage-optional)\n    - [6. Set Up API Routes](#6-set-up-api-routes)\n    - [7. Initialize the Client](#7-initialize-the-client)\n- [Usage Examples](#usage-examples)\n    - [Accessing Geolocation Data](#accessing-geolocation-data)\n- [R2 File Storage Guide](./docs/r2.md)\n- [License](#license)\n- [Contributing](#contributing)\n\n## Quick Start with CLI\n\n⚡️ For the fastest setup, use the CLI to generate a complete project (including the resources on Cloudflare):\n\n**Interactive mode** (asks questions and provides helpful defaults):\n\n```bash\nnpx @better-auth-cloudflare/cli@latest generate\n```\n\n**Non-interactive mode** (use arguments):\n\n```bash\n# Simple D1 app with KV (fully deployed to Cloudflare)\nnpx @better-auth-cloudflare/cli@latest generate \\\n  --app-name=my-auth-app \\\n  --template=hono \\\n  --database=d1 \\\n  --kv=true \\\n  --r2=false \\\n  --apply-migrations=prod\n```\n\n**Migration workflow**:\n\n```bash\nnpx @better-auth-cloudflare/cli@latest migrate                         # Interactive\nnpx @better-auth-cloudflare/cli@latest migrate --migrate-target=prod   # Non-interactive\n```\n\nThe CLI creates projects from Hono or Next.js templates and can automatically set up D1, KV, R2, and Hyperdrive resources. See [CLI Documentation](./cli/README.md) for full documentation and all available arguments.\n\n**Troubleshooting**:\n\nIf you encounter this error when using the CLI: `...Error [ERR_REQUIRE_ESM]: require() of ES Module...`, make sure your node version is at least `v23.0.0`, `v22.12.0`, or `v20.19.0`, depending on the major version you use. Read more [here](https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require)\n\n## Manual Installation\n\n```bash\nnpm install better-auth-cloudflare\n# or\nyarn add better-auth-cloudflare\n# or\npnpm add better-auth-cloudflare\n# or\nbun add better-auth-cloudflare\n```\n\n## Configuration Options\n\n| Option                | Type    | Default     | Description                                    |\n| --------------------- | ------- | ----------- | ---------------------------------------------- |\n| `autoDetectIpAddress` | boolean | `true`      | Auto-detect IP address from Cloudflare headers |\n| `geolocationTracking` | boolean | `true`      | Track geolocation data in the session table    |\n| `cf`                  | object  | `{}`        | Cloudflare geolocation context                 |\n| `r2`                  | object  | `undefined` | R2 bucket configuration for file storage       |\n\n## Setup\n\nIntegrating `better-auth-cloudflare` into your project involves a few key steps to configure your database, authentication logic, and API routes. Follow these instructions to get started:\n\n\u003cbr\u003e\n\n### 1. Define Your Database Schema (`src/db/schema.ts`)\n\nYou'll need to merge the Better Auth schema with any other Drizzle schemas your application uses. This ensures that Drizzle can manage your entire database structure, including the tables required by Better Auth.\n\n```typescript\nimport * as authSchema from \"./auth.schema\"; // This will be generated in a later step\n\n// Combine all schemas here for migrations\nexport const schema = {\n    ...authSchema,\n    // ... your other application schemas\n} as const;\n```\n\n_Note: The `auth.schema.ts` file will be generated by the Better Auth CLI in a subsequent step._\n\n\u003cbr\u003e\n\n### 2. Initialize Drizzle ORM (`src/db/index.ts`)\n\nProperly initialize Drizzle with your database. This function will provide a database client instance to your application. For D1, you'll use Cloudflare D1 bindings, while Postgres/MySQL will use Hyperdrive connection strings.\n\n```typescript\nimport { getCloudflareContext } from \"@opennextjs/cloudflare\";\nimport { drizzle } from \"drizzle-orm/d1\";\nimport { schema } from \"./schema\";\n\nexport async function getDb() {\n    // Retrieves Cloudflare-specific context, including environment variables and bindings\n    const { env } = await getCloudflareContext({ async: true });\n\n    // Initialize Drizzle with your D1 binding (e.g., \"DB\" or \"DATABASE\" from wrangler.toml)\n    return drizzle(env.DATABASE, {\n        // Ensure \"DATABASE\" matches your D1 binding name in wrangler.toml\n        schema,\n        logger: true, // Optional\n    });\n}\n```\n\n\u003cbr\u003e\n\n### 3. Configure Better Auth (`src/auth/index.ts`)\n\nSet up your Better Auth configuration, wrapping it with `withCloudflare` to enable Cloudflare-specific features. The exact configuration depends on your framework:\n\n**For most frameworks (Hono, etc.):**\n\n```typescript\nimport type { D1Database, IncomingRequestCfProperties } from \"@cloudflare/workers-types\";\nimport { betterAuth } from \"better-auth\";\nimport { withCloudflare } from \"better-auth-cloudflare\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\nimport { drizzle } from \"drizzle-orm/d1\";\nimport { schema } from \"../db\";\n\n// Single auth configuration that handles both CLI and runtime scenarios\nfunction createAuth(env?: CloudflareBindings, cf?: IncomingRequestCfProperties) {\n    // Use actual DB for runtime, empty object for CLI\n    const db = env ? drizzle(env.DATABASE, { schema, logger: true }) : ({} as any);\n\n    return betterAuth({\n        ...withCloudflare(\n            {\n                autoDetectIpAddress: true,\n                geolocationTracking: true,\n                cf: cf || {},\n                d1: env\n                    ? {\n                          db,\n                          options: {\n                              usePlural: true,\n                              debugLogs: true,\n                          },\n                      }\n                    : undefined,\n                kv: env?.KV,\n                // Optional: Enable R2 file storage\n                r2: {\n                    bucket: env.R2_BUCKET,\n                    maxFileSize: 10 * 1024 * 1024, // 10MB\n                    allowedTypes: [\".jpg\", \".jpeg\", \".png\", \".gif\", \".pdf\", \".doc\", \".docx\"],\n                    additionalFields: {\n                        category: { type: \"string\", required: false },\n                        isPublic: { type: \"boolean\", required: false },\n                        description: { type: \"string\", required: false },\n                    },\n                },\n            },\n            {\n                emailAndPassword: {\n                    enabled: true,\n                },\n                rateLimit: {\n                    enabled: true,\n                    window: 60, // Minimum KV TTL is 60s\n                    max: 100, // reqs/window\n                    customRules: {\n                        // https://github.com/better-auth/better-auth/issues/5452\n                        \"/sign-in/email\": {\n                            window: 60,\n                            max: 100,\n                        },\n                        \"/sign-in/social\": {\n                            window: 60,\n                            max: 100,\n                        },\n                    },\n                },\n            }\n        ),\n        // Only add database adapter for CLI schema generation\n        ...(env\n            ? {}\n            : {\n                  database: drizzleAdapter({} as D1Database, {\n                      provider: \"sqlite\",\n                      usePlural: true,\n                      debugLogs: true,\n                  }),\n              }),\n    });\n}\n\n// Export for CLI schema generation\nexport const auth = createAuth();\n\n// Export for runtime usage\nexport { createAuth };\n```\n\n**For OpenNext.js with complex async requirements:**\nSee the [OpenNext.js example](./examples/opennextjs/README.md) for a more complex configuration that handles async database initialization and singleton patterns.\n\n**Using Hyperdrive (MySQL):**\n\n```typescript\nimport { drizzle } from \"drizzle-orm/mysql2\";\nimport mysql from \"mysql2/promise\";\n\nasync function getDb() {\n    const { env } = await getCloudflareContext({ async: true });\n    const connection = mysql.createPool(env.HYPERDRIVE_URL);\n    return drizzle(connection, { schema });\n}\n\nconst auth = betterAuth({\n    ...withCloudflare(\n        {\n            mysql: {\n                db: await getDb(),\n            },\n            // other cloudflare options...\n        },\n        {\n            // your auth options...\n        }\n    ),\n});\n```\n\n**Using Hyperdrive (Postgres):**\n\n```typescript\nimport { drizzle } from \"drizzle-orm/postgres-js\";\nimport postgres from \"postgres\";\n\nasync function getDb() {\n    const { env } = await getCloudflareContext({ async: true });\n    const sql = postgres(env.HYPERDRIVE_URL);\n    return drizzle(sql, { schema });\n}\n\nconst auth = betterAuth({\n    ...withCloudflare(\n        {\n            postgres: {\n                db: await getDb(),\n            },\n            // other cloudflare options...\n        },\n        {\n            // your auth options...\n        }\n    ),\n});\n```\n\n### 4. Generate and Manage Auth Schema\n\nBetter Auth uses Drizzle ORM for database interactions, allowing for automatic schema management for your database (D1/SQLite, Postgres, or MySQL).\n\nTo generate or update your authentication-related database schema, run the Better Auth CLI:\n\n```bash\nnpx @better-auth/cli@latest generate\n```\n\nThis command inspects your `src/auth/index.ts` (specifically the `auth` export) and creates/updates `src/db/auth.schema.ts` with the necessary Drizzle schema definitions for tables like users, sessions, accounts, etc.\n\n**Recommended Usage:**\n\nSpecify your configuration file and output path for more precise control:\n\n```bash\nnpx @better-auth/cli@latest generate --config src/auth/index.ts --output src/db/auth.schema.ts -y\n```\n\nThis command will:\n\n- Read the `export const auth` configuration from `src/auth/index.ts`.\n- Output the generated Drizzle schema to `src/db/auth.schema.ts`.\n- Automatically confirm prompts (`-y`).\n\nAfter generation, you can use Drizzle Kit to create and apply migrations to your database. Refer to the [Drizzle ORM documentation](https://orm.drizzle.team/kit/overview) for managing migrations.\n\nFor integrating the generated `auth.schema.ts` with your existing Drizzle schema, see [managing schema across multiple files](https://orm.drizzle.team/docs/sql-schema-declaration#schema-in-multiple-files). More details on schema generation are available in the [Better Auth docs](https://www.better-auth.com/docs/adapters/drizzle#schema-generation--migration).\n\n### 5. Configure KV as Secondary Storage (Optional)\n\nIf you provide a KV namespace in the `withCloudflare` configuration (as shown in `src/auth/index.ts`), it will be used as [Secondary Storage](https://www.better-auth.com/docs/concepts/database#secondary-storage) by Better Auth. This is typically used for caching or storing session data that doesn't need to reside in your primary database.\n\nEnsure your KV namespace (e.g., `USER_SESSIONS`) is correctly bound in your `wrangler.toml` file.\n\n#### Important: KV TTL Limitation\n\nCloudflare KV has a minimum TTL (Time To Live) requirement of **60 seconds**. If you're using KV for secondary storage with rate limiting enabled, you **must** configure your rate limit windows to be at least 60 seconds to prevent crashes:\n\n```typescript\nrateLimit: {\n    enabled: true,\n    window: 60, // Minimum KV TTL is 60s\n    max: 100, // reqs/window\n    customRules: {\n        // https://github.com/better-auth/better-auth/issues/5452\n        \"/sign-in/email\": {\n            window: 60,\n            max: 100,\n        },\n        \"/sign-in/social\": {\n            window: 60,\n            max: 100,\n        },\n    },\n},\n```\n\nThe library automatically enforces this minimum and will log a warning if a TTL less than 60 seconds is attempted, but it's better to configure your rate limits correctly from the start.\n\n### 6. Set Up API Routes\n\nCreate API routes to handle authentication requests. Better Auth provides a handler that can be used for various HTTP methods.\n\n```typescript\n// Example: src/app/api/auth/[...all]/route.ts\n// Adjust the path based on your project structure (e.g., Next.js App Router)\n\nimport { initAuth } from \"@/auth\"; // Adjust path to your auth/index.ts\n\nexport async function POST(req: Request) {\n    const auth = await initAuth();\n    return auth.handler(req);\n}\n\nexport async function GET(req: Request) {\n    const auth = await initAuth();\n    return auth.handler(req);\n}\n\n// You can also add handlers for PUT, DELETE, PATCH if needed by your auth flows\n```\n\n### 7. Initialize the Client\n\nSet up the Better Auth client, including the Cloudflare plugin, to interact with authentication features on the front-end.\n\n```typescript\n// Example: src/lib/authClient.ts or similar client-side setup file\n\nimport { createAuthClient } from \"better-auth/client\";\nimport { cloudflareClient } from \"better-auth-cloudflare/client\";\n\nconst authClient = createAuthClient({\n    plugins: [cloudflareClient()], // includes geolocation and R2 file features (if configured)\n});\n\nexport default authClient;\n```\n\n## Usage Examples\n\n### Accessing Geolocation Data\n\nThis library enables access to Cloudflare's geolocation data both on the client and server-side.\n\n**Client-side API:**\nUse the `authClient` to fetch geolocation information.\n\n```typescript\nimport authClient from \"@/lib/authClient\"; // Adjust path to your client setup\n\nconst displayLocationInfo = async () =\u003e {\n    try {\n        const result = await authClient.cloudflare.geolocation();\n        if (result.error) {\n            console.error(\"Error fetching geolocation:\", result.error);\n        } else if (result.data \u0026\u0026 !(\"error\" in result.data)) {\n            console.log(\"📍 Geolocation data:\", {\n                timezone: result.data.timezone,\n                city: result.data.city,\n                country: result.data.country,\n                region: result.data.region,\n                regionCode: result.data.regionCode,\n                colo: result.data.colo,\n                latitude: result.data.latitude,\n                longitude: result.data.longitude,\n            });\n        }\n    } catch (err) {\n        console.error(\"Failed to get geolocation data:\", err);\n    }\n};\n\ndisplayLocationInfo();\n```\n\n### R2 File Storage\n\nIf you've configured R2 in your server setup, you can upload and manage files:\n\n```typescript\nimport authClient from \"@/lib/authClient\";\n\n// Upload a file with metadata\nconst uploadFile = async (file: File) =\u003e {\n    const result = await authClient.uploadFile(file, {\n        category: \"documents\",\n        isPublic: false,\n        description: \"Important document\",\n    });\n\n    if (result.error) {\n        console.error(\"Upload failed:\", result.error.message || \"Failed to upload file. Please try again.\");\n    } else {\n        console.log(\"File uploaded:\", result.data);\n    }\n};\n\n// List user's files\nconst listFiles = async () =\u003e {\n    const result = await authClient.files.list();\n    if (result.data) {\n        console.log(\"User files:\", result.data);\n    }\n};\n\n// Download a file\nconst downloadFile = async (fileId: string, filename: string) =\u003e {\n    const result = await authClient.files.download({ fileId });\n    if (result.error) {\n        console.error(\"Download failed:\", result.error);\n        return;\n    }\n\n    // Extract blob and create download\n    const response = result.data;\n    const blob = response instanceof Response ? await response.blob() : response;\n    const url = window.URL.createObjectURL(blob);\n    const a = document.createElement(\"a\");\n    a.href = url;\n    a.download = filename;\n    a.click();\n    window.URL.revokeObjectURL(url);\n};\n```\n\nFor complete R2 file storage documentation, see the [R2 File Storage Guide](./docs/r2.md).\n\n## License\n\n[MIT](./LICENSE)\n\n## Contributing\n\nContributions are welcome! Whether it's bug fixes, feature additions, or documentation improvements, we appreciate your help in making this project better. For major changes or new features, please open an issue first to discuss what you would like to change.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzpg6%2Fbetter-auth-cloudflare","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzpg6%2Fbetter-auth-cloudflare","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzpg6%2Fbetter-auth-cloudflare/lists"}