MainSDK
Search⌘K
Integrations

Cloudflare KV

Use Cloudflare KV for the MainSDK cache and runtime settings - namespaces, main.yaml, credentials, eventual consistency, testing and swapping.

2 interfaces2 driversWritten guide
Updated 2026-10-28

Cloudflare KV is a key-value store replicated across Cloudflare's network, and a driver behind two interfaces: kv, for small values and cached results, and config, for typed runtime settings that change without a deploy. Reads are fast everywhere because values are cached close to the reader. Pick it when your app already runs on Cloudflare, or when you want settings and cached values read quickly in many places and can accept that a write takes a short while to show up everywhere.

Interfaces it serves

Pick Cloudflare KV in main.yaml for any of these. Your calls stay the same, and you can swap later.

Before you start

You need a Cloudflare account.

  1. Create a KV namespace for each use. Keep cached values and runtime settings in separate namespaces, and use separate namespaces for development and production.
  2. Create an API token scoped to the account's Workers KV storage with the least rights the driver needs: read for config, read and write for kv. Copy the token and your account ID.
  3. Note each namespace ID. The driver needs it to find your values.

Store the token in your secrets manager, never in code or a committed env file.

Configure

For runtime settings:

main.yaml
config:
  driver: cloudflare-kv
  schema: ./config.schema.json
  # token: secret:CLOUDFLARE_API_TOKEN   # suggested name; connection keys (account, namespace) are [to be decided]

For the cache and key-value store:

main.yaml
kv:
  driver: cloudflare-kv

The config spec leaves the connection keys for each hosted store (store id, region, namespace) undecided, and kv connection settings arrive with its spec. Settings are not secrets: keep credentials in Secrets.

Use it

Read a typed setting and react when it changes:

app/lib/cart.ts
import { config, log } from "mainsdk";

export function canAdd(count: number) {
  return count < config.get("cart.maxItems");   // typed from config.schema.json
}

const stop = config.watch("cart.maxItems", (v) => log.info("changed", { v }));

Store a value with an expiry:

app/onboarding/actions.ts
"use server";
import { auth, kv } from "mainsdk";

export async function saveDraftStep(req: Request, step: number) {
  const session = await auth.require(req);
  await kv.set(`onboarding:${session.user.id}`, { step }, { ttl: "1d" });
}

Local development and testing

  • No account: use config: { driver: files } in main.env.dev.yaml, and the in-memory fakes in tests. The local driver for kv is not named yet.
  • A development namespace: point your development overlay at its own namespace to try the real store.
  • Tests: createTestApp() from @mainsdk/testing runs config and kv on in-memory fakes. chaos.on("config", { outage: true }) proves config.get() keeps serving the last good value when Cloudflare is unreachable; chaos.on("kv", { outage: true }) does the same drill for the cache.

What MainSDK adds on top

  • Settings typed against your schema; a bad value is rejected with MAIN-CONFIG-0020 and the last good one is kept.
  • Every settings change the app sees is audited, with the key, hashes of old and new values and the source.
  • kv keys scoped per tenant, a TTL on every entry, and no personal data in shared caches without a label.
  • The same calls on every driver, so moving settings or cache off Cloudflare is one line.

See Runtime settings and Key-value store and cache.

Limits and notes

  • Cloudflare KV is eventually consistent: a write can take a while to reach every location. Don't use it for counters, locks or anything that needs read-after-write; use limits and db instead.
  • cloudflare-kv comes with the config-secrets spec for config (phase 1). For kv it is a planned phase 1 driver, and kv has no spec yet.
  • Cloudflare replicates KV values across its network, so keep personal data out of it unless your residency rules allow that copy. MainSDK regions are US, EU and India; see Data and regions.
  • Running your app itself on Cloudflare Workers comes with the portable TypeScript core (spec portable-core, 0.4). How the driver reaches KV from Node is not decided yet.
  • config.raw() returns the provider's client for features config does not wrap; every use is logged and listed by mainsdk check.

Swap to or from it

main.yaml
config:
  driver: cloudflare-kv   # was vercel-global-config

Your schema, config.get() and config.watch() calls stay the same; copy the values into the new namespace first. For kv, cached entries refill on the new driver as they expire.

FAQ

Why didn't my change show up straight away? KV is eventually consistent. Expect a short delay before every reader sees a new value; config.watch() fires when your app sees it.

Should settings and cache share a namespace? No. Keep them apart so a cache write can never overwrite a setting, and so the config token can be read-only.

Next

Open questions

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet