adapter-cloudflare
Cloudflare Workers adapter for Catmint. Transforms Catmint build output into a Cloudflare Workers-compatible bundle deployable with wrangler.
Import
import cloudflare from "@catmint/adapter-cloudflare";
Signature
function cloudflareAdapter(
options?: CloudflareAdapterOptions,
): CloudflareAdapter;
The default export is the cloudflareAdapter factory function.
CloudflareAdapter
The return type extends CatmintAdapter with an additional vitePlugin property:
interface CloudflareAdapter extends CatmintAdapter {
vitePlugin: Plugin[];
}
| Property | Type | Description |
|---|---|---|
vitePlugin | Plugin[] | Vite plugins that enable workerd dev mode. Add this to vite.plugins in your config to run your app in workerd. |
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
options | CloudflareAdapterOptions | No | Configuration for the Cloudflare Workers deployment. |
CloudflareAdapterOptions
| Property | Type | Default | Description |
|---|---|---|---|
name | string | "catmint-app" | Worker name used in the generated wrangler.jsonc. |
routes | string[] | ['/*'] | Worker route patterns. |
compatibilityDate | string | Current date | Cloudflare Workers compatibility date. Defaults to today's date in YYYY-MM-DD format. |
maxBodySize | number | 1048576 | Maximum request body size in bytes (default 1 MB). Requests exceeding this limit receive a 413 Payload Too Large response. |
kvNamespaces | KVNamespaceBinding[] | -- | KV Namespace bindings. See Bindings. |
r2Buckets | R2BucketBinding[] | -- | R2 bucket bindings. See Bindings. |
d1Databases | D1DatabaseBinding[] | -- | D1 database bindings. See Bindings. |
durableObjects | DurableObjectBinding[] | -- | Durable Object bindings. See Bindings. |
wrangler | Record<string, unknown> | -- | Raw wrangler config merged into the generated wrangler.jsonc. Overrides any generated values. |
Return Value
Returns a CloudflareAdapter object with name: '@catmint/adapter-cloudflare' and a vitePlugin array. The adapter operates in one of two modes depending on whether vitePlugin is added to your Vite config:
Plugin Mode
When cf.vitePlugin is included in vite.plugins, the adapter uses @cloudflare/vite-plugin to run your RSC and SSR environments inside the actual workerd runtime during development. At build time, a pre-compiled TypeScript worker entry is used. This mode gives you the highest fidelity Cloudflare development experience.
Legacy Mode
When cf.vitePlugin is not included, the dev server runs in Node.js with getPlatformProxy() providing emulated bindings. At build time, the worker entry is generated from a string template. This mode requires only wrangler (not @cloudflare/vite-plugin).
In both modes, when the adapt() method is called during build, it:
- Generates
worker.js-- the Workers entry point - Generates
wrangler.jsonc-- Wrangler configuration with the specified name, compatibility date, routes, and bindings - Copies client assets and pre-rendered pages to
public/ - Copies the server bundle into the output directory
- If the build manifest has
hasMiddleware: true, middleware execution is included in the Worker handler. Aprerendered.jsmodule is generated that inlines middleware-protected pre-rendered HTML as a pathname-to-string map, since Workers cannot access the filesystem.
Output is written to dist/cloudflare/.
Frontend Mode
When the application is built with mode: 'frontend', the Cloudflare adapter skips worker generation entirely and only copies the static client assets. No worker.js is produced -- deploy the output as a static site to Cloudflare Pages or any static hosting provider.
Edge Compatibility Validation
During the build, the adapter scans server function source paths for Node.js-specific module imports (fs, child_process, node:, net, tls, etc.) and logs warnings for potential runtime incompatibilities. The Cloudflare Workers runtime does not support most Node.js built-in modules.
Security Headers
The adapter automatically applies baseline security headers (X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, X-Frame-Options: SAMEORIGIN) to all responses, including static assets served via env.ASSETS. These are applied before any middleware-set headers.
Bindings
Cloudflare bindings connect your Worker to platform services like KV, R2, D1, and Durable Objects. Configure bindings in the adapter options and they are emitted into wrangler.jsonc.
KV Namespaces
cloudflare({
kvNamespaces: [{ binding: "MY_KV", id: "abc123def456" }],
});
| Field | Type | Required | Description |
|---|---|---|---|
binding | string | Yes | The binding name in code |
id | string | Yes | KV namespace ID |
R2 Buckets
cloudflare({
r2Buckets: [{ binding: "MY_BUCKET", bucketName: "my-bucket" }],
});
| Field | Type | Required | Description |
|---|---|---|---|
binding | string | Yes | The binding name in code |
bucketName | string | Yes | R2 bucket name |
D1 Databases
cloudflare({
d1Databases: [
{ binding: "DB", databaseName: "mydb", databaseId: "def456ghi789" },
],
});
| Field | Type | Required | Description |
|---|---|---|---|
binding | string | Yes | The binding name in code |
databaseName | string | Yes | D1 database name |
databaseId | string | Yes | D1 database ID |
Durable Objects
cloudflare({
durableObjects: [
{ name: "COUNTER", className: "Counter" },
{ name: "CHAT_ROOM", className: "ChatRoom", scriptName: "chat-worker" },
],
});
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The binding name in code |
className | string | Yes | Durable Object class name |
scriptName | string | No | Worker script name (for external Durable Objects) |
Wrangler Passthrough
For bindings not covered by the typed fields (Queues, Vectorize, AI, Hyperdrive, etc.), use the wrangler passthrough option. This object is merged directly into the generated wrangler.jsonc:
cloudflare({
wrangler: {
ai: { binding: "AI" },
queues: {
producers: [{ binding: "MY_QUEUE", queue: "my-queue" }],
},
hyperdrive: [{ binding: "HYPERDRIVE", id: "abc123" }],
},
});
The
wranglerpassthrough is merged last and overrides any generated values with the same key. Use it carefully to avoid overwriting bindings set via the typed options.
Workerd Dev Mode (Vite Plugin)
The Cloudflare adapter exposes a vitePlugin property that integrates @cloudflare/vite-plugin into your Vite config. When enabled, the RSC and SSR Vite environments run inside the real workerd runtime during development, giving you full Cloudflare Workers API compatibility without emulation.
Setup
Install @cloudflare/vite-plugin as a dev dependency:
pnpm add -D @cloudflare/vite-plugin
Then instantiate the adapter and pass both the adapter and its vitePlugin into your config:
// catmint.config.ts
import { defineConfig } from "catmint/config";
import cloudflare from "@catmint/adapter-cloudflare";
const cf = cloudflare({ compatibilityDate: "2026-01-01" });
export default defineConfig({
mode: "fullstack",
adapter: cf,
vite: {
plugins: [cf.vitePlugin],
},
});
@cloudflare/vite-pluginis an optional peer dependency. If it is not installed, thevitePluginarray is inert -- it will not error, but workerd dev mode will not activate. The adapter falls back to legacy dev mode usinggetPlatformProxy().
How It Works
- The bridge plugin sets an internal signal during Vite's
confighook, telling the adapter that plugin mode is active - The loader plugin dynamically imports
@cloudflare/vite-pluginand configures it to take over thessrenvironment withrscas a child environment - During
catmint dev, both RSC and SSR code run inside a localworkerdprocess instead of Node.js - All bindings configured in the adapter options are passed to the workerd config automatically
When to Use It
Use plugin mode when you need:
- Accurate Cloudflare Workers API behavior during development (e.g.,
caches,crypto.subtle,HTMLRewriter) - To test Workers-specific runtime constraints (no Node.js built-in modules)
- High fidelity between your dev and production environments
If you only need basic bindings (KV, R2, D1) during development, legacy dev mode with getPlatformProxy() is simpler and does not require @cloudflare/vite-plugin.
Local Development
When cf.vitePlugin is not in your Vite plugins (or @cloudflare/vite-plugin is not installed), the adapter falls back to legacy dev mode. When you run catmint dev, the adapter's dev() hook starts a local emulation of Cloudflare bindings using Wrangler's getPlatformProxy(). This means getPlatform() returns real, working KV, R2, D1, and Durable Object bindings backed by a local workerd process -- no mocks or stubs needed.
Requirements
wrangler must be installed as a dev dependency:
pnpm add -D wrangler
If wrangler is not installed, the dev server throws an error with install instructions.
What You Get in Dev
getPlatform() returns the same shape as production:
const platform = getPlatform<CloudflarePlatform>();
// platform.env — locally emulated bindings (KV, R2, D1, etc.)
// platform.ctx — execution context
// platform.cf — cf properties
// platform.caches — cache API
Bindings configured in the adapter options (kvNamespaces, d1Databases, r2Buckets, durableObjects, wrangler passthrough) are automatically available in both catmint dev and production builds -- catmint.config.ts is the single source of truth. No separate wrangler.toml is needed.
If no bindings are configured in the adapter options, getPlatformProxy() falls back to reading from a wrangler.toml / wrangler.jsonc in the project root.
The local proxy is automatically cleaned up when the dev server shuts down.
How It Works
- At startup, the adapter dynamically imports
wranglerand callsgetPlatformProxy() - If bindings are configured in the adapter options, a temporary
wrangler.jsoncis generated and passed togetPlatformProxy()via itsconfigPathoption - This starts a local
workerdprocess with your configured bindings - On each request, the dev server calls the adapter's
getPlatform()with the incoming request - The adapter returns
{ env, ctx, cf, caches }from the proxy - The dev server stores this in the request context, making it available via
getPlatform()
Accessing Bindings at Runtime
Use getPlatform() from catmint/server to access Cloudflare bindings in server functions, middleware, endpoints, and RSC rendering:
import { createServerFn } from "catmint/server";
import { getPlatform } from "catmint/server";
interface CloudflarePlatform {
env: {
MY_KV: KVNamespace;
MY_BUCKET: R2Bucket;
DB: D1Database;
};
ctx: ExecutionContext;
}
export const getData = createServerFn(async () => {
const { env } = getPlatform<CloudflarePlatform>();
const value = await env.MY_KV.get("key");
return { value };
});
See getPlatform for full documentation and platform-specific types.
Examples
// catmint.config.ts -- with workerd dev mode (recommended)
import { defineConfig } from "catmint/config";
import cloudflare from "@catmint/adapter-cloudflare";
const cf = cloudflare({ compatibilityDate: "2026-01-01" });
export default defineConfig({
mode: "fullstack",
adapter: cf,
vite: {
plugins: [cf.vitePlugin],
},
});
// catmint.config.ts -- defaults (legacy mode)
import { defineConfig } from "catmint/config";
import cloudflare from "@catmint/adapter-cloudflare";
export default defineConfig({
adapter: cloudflare(),
});
// Custom name, bindings, and compatibility date
import { defineConfig } from "catmint/config";
import cloudflare from "@catmint/adapter-cloudflare";
export default defineConfig({
adapter: cloudflare({
name: "my-app",
compatibilityDate: "2026-01-01",
kvNamespaces: [{ binding: "CACHE", id: "abc123" }],
d1Databases: [
{ binding: "DB", databaseName: "mydb", databaseId: "def456" },
],
}),
});
After building, deploy with:
npx wrangler deploy --config dist/cloudflare/wrangler.jsonc
