Client Storage
Access Cloudflare D1 databases, R2 object storage, and KV namespaces directly from the client.
Configuration
Add bindings to your package.json:
{
"name": "my-app",
"exports": "./src",
"cloudflare": {
"d1": ["MY_DB", "ANALYTICS_DB"],
"r2": ["UPLOADS"],
"kv": ["SESSIONS"]
}
}Binding names must be UPPER_SNAKE_CASE. When you run npm run dev or npm run export, the appropriate wrangler.toml sections are generated automatically.
Importing the Client
The client object is available as the default export:
import client from "https://my-worker.workers.dev/";
// Or destructure alongside named exports
import client, { greet, Counter } from "https://my-worker.workers.dev/";D1 Database
Query D1 databases using tagged template literals for automatic parameter binding:
const { d1 } = client;
// Simple query (returns all rows)
const users = await d1.MY_DB`SELECT * FROM users`;
// Parameterized queries (safe from SQL injection)
const userId = 123;
const user = await d1.MY_DB`SELECT * FROM users WHERE id = ${userId}`.first();
// Insert with parameters
const name = "Alice";
const email = "alice@example.com";
await d1.MY_DB`INSERT INTO users (name, email) VALUES (${name}, ${email})`.run();
// Complex queries
const active = true;
const limit = 10;
const results = await d1.MY_DB`
SELECT * FROM users
WHERE active = ${active}
ORDER BY created_at DESC
LIMIT ${limit}
`.all();Query Methods
| Method | Returns |
|---|---|
.all() | { results: T[], success: boolean, meta: object } |
.first() | First row or null |
.first(column) | Value of specific column from first row |
.run() | { success: boolean, meta: object } for INSERT/UPDATE/DELETE |
.raw() | Array of arrays (raw rows without column names) |
The default behavior (calling the query directly) is equivalent to .all().
R2 Object Storage
Store and retrieve files from R2 buckets:
const { r2 } = client;
// Get an object
const file = await r2.UPLOADS.get("images/photo.jpg");
if (file) {
const data = file.body; // Uint8Array
const contentType = file.httpMetadata?.contentType;
}
// Put an object
await r2.UPLOADS.put("documents/report.pdf", pdfData, {
httpMetadata: { contentType: "application/pdf" }
});
// Delete an object
await r2.UPLOADS.delete("temp/old-file.txt");
// List objects
const listing = await r2.UPLOADS.list({ prefix: "images/" });
for (const obj of listing.objects) {
console.log(obj.key, obj.size);
}
// Check if object exists (without downloading)
const head = await r2.UPLOADS.head("images/photo.jpg");
if (head) {
console.log("Size:", head.size);
}KV Key-Value Store
Fast, globally distributed key-value storage:
const { kv } = client;
// Get a value
const session = await kv.SESSIONS.get("user:123");
// Get with type hint
const data = await kv.SESSIONS.get("config", { type: "json" });
// Put a value
await kv.SESSIONS.put("user:123", JSON.stringify({ loggedIn: true }));
// Put with expiration (TTL in seconds)
await kv.SESSIONS.put("temp:token", "abc123", { expirationTtl: 3600 });
// Delete a key
await kv.SESSIONS.delete("user:123");
// List keys
const keys = await kv.SESSIONS.list({ prefix: "user:" });
for (const key of keys.keys) {
console.log(key.name);
}
// Get with metadata
const result = await kv.SESSIONS.getWithMetadata("user:123");
console.log(result.value, result.metadata);Creating Bindings
After configuring package.json, you need to create the actual resources in Cloudflare:
# Create D1 database
wrangler d1 create my-app-my-db
# Create R2 bucket
wrangler r2 bucket create my-app-uploads
# Create KV namespace
wrangler kv namespace create SESSIONSUpdate the generated wrangler.toml with the returned IDs before deploying.
Authentication
Enable authentication powered by better-auth:
{
"cloudflare": {
"auth": true
}
}This automatically adds an AUTH_DB D1 binding for storing users and sessions.
Usage
import client from "https://my-worker.workers.dev/";
const { auth } = client;
// Sign up with email
const { user, error } = await auth.signUp.email(
"user@example.com",
"password123",
"John Doe"
);
// Sign in with email
await auth.signIn.email("user@example.com", "password123");
// Sign in with OAuth (redirects to provider)
await auth.signIn.social("google");
await auth.signIn.social("github");
// Get current session
const session = await auth.getSession();
// Get current user
const user = await auth.getUser();
// Sign out
await auth.signOut();
// Check if authenticated
if (auth.isAuthenticated) {
console.log("User is logged in");
}OAuth Setup
Use the built-in CLI to configure OAuth providers:
# Add Google OAuth
npm run auth:add -- google YOUR_CLIENT_ID:YOUR_CLIENT_SECRET
# Add GitHub OAuth
npm run auth:add -- github YOUR_CLIENT_ID:YOUR_CLIENT_SECRET
# List configured providers
npm run auth:list
# Remove a provider
npm run auth:remove -- googleThis automatically:
- Enables auth in
package.json - Saves credentials to
.dev.vars(local development) - Adds
.dev.varsto.gitignore - Generates
BETTER_AUTH_SECRET
For production, set these secrets in your Cloudflare dashboard:
BETTER_AUTH_SECRETGOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRETGITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET
OAuth callback URLs:
https://your-worker.workers.dev/_auth/callback/googlehttps://your-worker.workers.dev/_auth/callback/github
Database Setup
Run better-auth migrations on your AUTH_DB:
# Create the database
wrangler d1 create my-app-auth-db
# Run migrations (after deploying once)
npx better-auth migrateServer-Side Access
On the server side, bindings are available via the standard Cloudflare env object:
// src/index.ts
export async function getUser(id: number, env: Env) {
const user = await env.MY_DB.prepare("SELECT * FROM users WHERE id = ?")
.bind(id)
.first();
return user;
}The client storage feature gives browser clients the same access pattern, with all operations proxied through WebSocket.