Skip to content

Commit 4af7948

Browse files
committed
docs: add Configuration Reference
1 parent 487fddb commit 4af7948

2 files changed

Lines changed: 326 additions & 0 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Demo implementations are available in the [`examples/`](./examples/) directory f
6969
- [Usage Examples](#usage-examples)
7070
- [Accessing Geolocation Data](#accessing-geolocation-data)
7171
- [R2 File Storage Guide](./docs/r2.md)
72+
- [Configuration Reference](./docs/configuration.md)
7273
- [License](#license)
7374
- [Contributing](#contributing)
7475

@@ -129,6 +130,8 @@ bun add better-auth-cloudflare
129130
| `cf` | object | `{}` | Cloudflare geolocation context |
130131
| `r2` | object | `undefined` | R2 bucket configuration for file storage |
131132

133+
For the full `WithCloudflareOptions` interface (including database, KV, and Drizzle adapter options), see the [Configuration Reference](./docs/configuration.md).
134+
132135
## Setup
133136

134137
Integrating `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:

docs/configuration.md

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
# Configuration Reference
2+
3+
## `withCloudflare(cloudflareOptions, authOptions)`
4+
5+
Wraps your Better Auth config with Cloudflare integrations. The result is spread into `betterAuth()`:
6+
7+
```typescript
8+
import { betterAuth } from "better-auth";
9+
import { withCloudflare } from "better-auth-cloudflare";
10+
11+
const auth = betterAuth({
12+
...withCloudflare(
13+
{
14+
/* WithCloudflareOptions */
15+
},
16+
{
17+
/* BetterAuthOptions */
18+
}
19+
),
20+
});
21+
```
22+
23+
> **Do not** add `cloudflare()` to your `plugins` array when using `withCloudflare` — it is injected automatically. Adding it manually results in a duplicate plugin.
24+
25+
### Override Behavior
26+
27+
`withCloudflare` returns a merged config object. The following keys are **always set** by the wrapper and take precedence over values in `authOptions`:
28+
29+
| Key | Behavior |
30+
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
31+
| `database` | Set from your `d1` / `d1Native` / `postgres` / `mysql` option. Omit `database` from `authOptions`. |
32+
| `secondaryStorage` | Set to `createKVStorage(kv)` when `kv` is provided, otherwise `undefined`. Omit from `authOptions`. |
33+
| `plugins` | The `cloudflare()` plugin is prepended to your `authOptions.plugins` array. |
34+
| `advanced` | Merges your `authOptions.advanced` with IP detection headers when `autoDetectIpAddress` is enabled. |
35+
| `session` | Merges your `authOptions.session`, forcing `storeSessionInDatabase: true` when `geolocationTracking` is enabled — even if you explicitly set it to `false`. |
36+
37+
If you need a custom `secondaryStorage` that is not KV, omit the `kv` option and set `secondaryStorage` outside the spread:
38+
39+
```typescript
40+
const auth = betterAuth({
41+
...withCloudflare(cloudflareOpts, authOpts),
42+
secondaryStorage: myCustomStorage,
43+
});
44+
```
45+
46+
---
47+
48+
## `WithCloudflareOptions`
49+
50+
Extends [`CloudflarePluginOptions`](#cloudflarepluginoptions) with database and KV configuration.
51+
52+
### Database Options
53+
54+
Only **one** database option may be provided — passing more than one throws at startup. All are optional; omitting them all is valid for CLI schema generation (`database` will be `undefined`).
55+
56+
| Option | Type | Description |
57+
| ---------- | --------------------------------------- | -------------------------------------------------------------------- |
58+
| `d1` | `DrizzleConfig<typeof d1Drizzle>` | D1 via Drizzle ORM |
59+
| `d1Native` | `D1Database` | Native D1 binding (no Drizzle, uses better-auth's Kysely D1 dialect) |
60+
| `postgres` | `DrizzleConfig<typeof postgresDrizzle>` | Postgres via Hyperdrive + Drizzle |
61+
| `mysql` | `DrizzleConfig<typeof mysqlDrizzle>` | MySQL via Hyperdrive + Drizzle |
62+
63+
### KV Option
64+
65+
| Option | Type | Description |
66+
| ------ | ------------- | ----------------------------------------------------------------------------------------------------------------------------- |
67+
| `kv` | `KVNamespace` | KV namespace for [secondary storage](#kv-secondary-storage). Automatically wired as `secondaryStorage` via `createKVStorage`. |
68+
69+
### `DrizzleConfig<T>`
70+
71+
```typescript
72+
type DrizzleConfig<T> = {
73+
db: ReturnType<T>;
74+
options?: Omit<DrizzleAdapterConfig, "provider">;
75+
};
76+
```
77+
78+
The `provider` is inferred from which option you use (`"sqlite"` / `"pg"` / `"mysql"`). Common adapter options: `usePlural`, `debugLogs`.
79+
80+
---
81+
82+
## `CloudflarePluginOptions`
83+
84+
Inherited by `WithCloudflareOptions`.
85+
86+
| Option | Type | Default | Description |
87+
| --------------------- | --------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------- |
88+
| `autoDetectIpAddress` | `boolean` | `true` | Adds `cf-connecting-ip` and `x-real-ip` to IP detection headers. |
89+
| `geolocationTracking` | `boolean` | `true` | Enriches sessions with geolocation fields. Overrides `session.storeSessionInDatabase` to `true`. |
90+
| `cf` | `CloudflareGeolocation \| Promise<…> \| null` | `undefined` | **Required** unless both options above are disabled. Typically `request.cf` (Hono) or `getCloudflareContext().cf` (OpenNext). |
91+
| `r2` | `R2Config` | `undefined` | R2 bucket configuration. See the [R2 File Storage Guide](./r2.md). |
92+
93+
### `CloudflareGeolocation`
94+
95+
When `geolocationTracking` is enabled, these optional `string` fields are added to the `session` table and populated on session creation from `cf`:
96+
97+
```typescript
98+
interface CloudflareGeolocation {
99+
timezone?: string | null;
100+
city?: string | null;
101+
country?: string | null;
102+
region?: string | null;
103+
regionCode?: string | null;
104+
colo?: string | null;
105+
latitude?: string | null;
106+
longitude?: string | null;
107+
}
108+
```
109+
110+
This is the subset of Cloudflare's `IncomingRequestCfProperties` that the library extracts. You can pass the full `request.cf` object — only these fields are read.
111+
112+
---
113+
114+
## KV Secondary Storage
115+
116+
Passing `kv` to `withCloudflare` enables [Better Auth Secondary Storage](https://www.better-auth.com/docs/concepts/database#secondary-storage) backed by Cloudflare KV — used for rate limiting, session caching, and verification tokens.
117+
118+
```typescript
119+
withCloudflare(
120+
{
121+
d1: { db, options: { usePlural: true } },
122+
kv: env.KV,
123+
cf: request.cf,
124+
},
125+
{
126+
rateLimit: { enabled: true, window: 60, max: 100 },
127+
}
128+
);
129+
```
130+
131+
### `createKVStorage(kv)`
132+
133+
If you need to wire secondary storage manually (without `withCloudflare`):
134+
135+
```typescript
136+
import { createKVStorage, cloudflare } from "better-auth-cloudflare";
137+
138+
const auth = betterAuth({
139+
database: myDatabase,
140+
secondaryStorage: createKVStorage(env.KV),
141+
plugins: [cloudflare({ cf: request.cf })],
142+
});
143+
```
144+
145+
> **Note:** The standalone `cloudflare()` plugin does **not** throw when `cf` is missing — the geolocation endpoint returns a 404 instead. `withCloudflare` is stricter and throws at startup if `cf` is omitted while `autoDetectIpAddress` or `geolocationTracking` is enabled.
146+
147+
### KV TTL Limitation
148+
149+
Cloudflare KV enforces a **minimum TTL of 60 seconds**. `createKVStorage` clamps lower values automatically and logs a warning. Configure rate limit `window` accordingly:
150+
151+
```typescript
152+
rateLimit: {
153+
enabled: true,
154+
window: 60, // Must be >= 60 when using KV
155+
max: 100,
156+
},
157+
```
158+
159+
Better Auth's built-in sign-in endpoints have their own default rate limit windows that may be lower than 60s, which causes KV write errors. Override them explicitly ([better-auth#5452](https://github.com/better-auth/better-auth/issues/5452)):
160+
161+
```typescript
162+
rateLimit: {
163+
enabled: true,
164+
window: 60,
165+
max: 100,
166+
customRules: {
167+
"/sign-in/email": { window: 60, max: 5 },
168+
"/sign-in/social": { window: 60, max: 5 },
169+
},
170+
},
171+
```
172+
173+
---
174+
175+
## Database Examples
176+
177+
### D1 with Drizzle
178+
179+
```typescript
180+
import { drizzle } from "drizzle-orm/d1";
181+
182+
const db = drizzle(env.DATABASE, { schema });
183+
184+
withCloudflare(
185+
{ d1: { db, options: { usePlural: true } }, cf: request.cf },
186+
{
187+
/* auth options */
188+
}
189+
);
190+
```
191+
192+
### Native D1 (No Drizzle)
193+
194+
```typescript
195+
withCloudflare(
196+
{ d1Native: env.DATABASE, cf: request.cf },
197+
{
198+
/* auth options */
199+
}
200+
);
201+
```
202+
203+
| | `d1Native` | `d1` (Drizzle) |
204+
| ----------------- | ---------------------------- | ------------------------- |
205+
| Bundle size | Smaller | Larger (includes Drizzle) |
206+
| Schema management | Manual SQL / better-auth CLI | Drizzle Kit migrations |
207+
| Type-safe queries | No | Yes |
208+
209+
### Hyperdrive (Postgres)
210+
211+
```typescript
212+
import { drizzle } from "drizzle-orm/postgres-js";
213+
import postgres from "postgres";
214+
215+
const db = drizzle(postgres(env.HYPERDRIVE.connectionString), { schema });
216+
217+
withCloudflare(
218+
{ postgres: { db }, cf: request.cf },
219+
{
220+
/* auth options */
221+
}
222+
);
223+
```
224+
225+
### Hyperdrive (MySQL)
226+
227+
```typescript
228+
import { drizzle } from "drizzle-orm/mysql2";
229+
import mysql from "mysql2/promise";
230+
231+
const db = drizzle(mysql.createPool(env.HYPERDRIVE.connectionString), { schema });
232+
233+
withCloudflare(
234+
{ mysql: { db }, cf: request.cf },
235+
{
236+
/* auth options */
237+
}
238+
);
239+
```
240+
241+
---
242+
243+
## `wrangler.toml` Reference
244+
245+
Complete example with all supported binding types. Include only what you need.
246+
247+
```toml
248+
name = "my-auth-app"
249+
main = "src/index.ts"
250+
compatibility_date = "2025-03-01"
251+
compatibility_flags = ["nodejs_compat"]
252+
253+
[observability]
254+
enabled = true
255+
256+
[placement]
257+
mode = "smart"
258+
259+
# D1 — Create with: wrangler d1 create my-auth-db
260+
[[d1_databases]]
261+
binding = "DATABASE"
262+
database_name = "my-auth-db"
263+
database_id = "<your-database-id>"
264+
migrations_dir = "drizzle"
265+
266+
# KV — Create with: wrangler kv namespace create KV
267+
[[kv_namespaces]]
268+
binding = "KV"
269+
id = "<your-kv-namespace-id>"
270+
271+
# R2 (optional) — Create with: wrangler r2 bucket create my-files
272+
[[r2_buckets]]
273+
binding = "R2_BUCKET"
274+
bucket_name = "my-files"
275+
276+
# Hyperdrive (optional) — Create with: wrangler hyperdrive create my-hd --connection-string="..."
277+
# [[hyperdrive]]
278+
# binding = "HYPERDRIVE"
279+
# id = "<your-hyperdrive-id>"
280+
281+
[vars]
282+
BETTER_AUTH_URL = "https://your-app.example.com"
283+
BETTER_AUTH_TRUSTED_ORIGINS = "https://your-app.example.com"
284+
```
285+
286+
### Binding Names and `env.d.ts`
287+
288+
The `binding` value in `wrangler.toml` determines the property name on `env`. Declare them for type safety:
289+
290+
```typescript
291+
import type { D1Database, Hyperdrive, KVNamespace, R2Bucket } from "@cloudflare/workers-types";
292+
293+
interface CloudflareBindings {
294+
DATABASE: D1Database;
295+
KV: KVNamespace;
296+
R2_BUCKET: R2Bucket;
297+
HYPERDRIVE: Hyperdrive; // Only if using Hyperdrive
298+
BETTER_AUTH_URL: string;
299+
BETTER_AUTH_TRUSTED_ORIGINS: string;
300+
}
301+
```
302+
303+
These names are configurable — if you change `binding = "KV"` to `binding = "AUTH_KV"` in `wrangler.toml`, update `env.d.ts` and your auth config to match. The [CLI](../cli/README.md) supports `--kv-binding`, `--d1-binding`, and `--r2-binding` flags for this.
304+
305+
---
306+
307+
## Commonly Used Exports
308+
309+
The main entry point (`better-auth-cloudflare`) re-exports all types and functions from the library. Commonly used:
310+
311+
| Export | Kind | Description |
312+
| --------------------------- | -------- | -------------------------------------------------------------------------------- |
313+
| `withCloudflare` | function | Wraps `BetterAuthOptions` with Cloudflare integrations (database, KV, plugin). |
314+
| `cloudflare` | function | Standalone Better Auth plugin for geolocation, IP detection, and R2. |
315+
| `createKVStorage` | function | Creates a `SecondaryStorage` backed by Cloudflare KV. |
316+
| `createR2Config` | function | Helper for creating a fully type-inferred `R2Config`. |
317+
| `CloudflareGeolocation` | type | The 8 geolocation fields extracted from `request.cf`. |
318+
| `CloudflareSession` | type | `Session` extended with geolocation fields. |
319+
| `CloudflareSessionResponse` | type | `{ session: CloudflareSession; user: User }` — shape of `/api/auth/get-session`. |
320+
| `CloudflarePluginOptions` | type | Options for the standalone `cloudflare()` plugin. |
321+
| `WithCloudflareOptions` | type | Options for the `withCloudflare` wrapper. |
322+
| `R2Config` | type | R2 bucket configuration. See the [R2 File Storage Guide](./r2.md). |
323+
| `FileMetadata` | type | Core file record shape stored in the database. |

0 commit comments

Comments
 (0)