---
title: "Cloudflare KV"
description: "Use Cloudflare KV for the MainSDK cache and runtime settings - namespaces, main.yaml, credentials, eventual consistency, testing and swapping."
section: "integrations"
canonical: "https://mainsdk.dev/docs/integrations/cloudflare-kv"
updated: "2026-10-28"
---

# Cloudflare KV

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

Canonical: https://mainsdk.dev/docs/integrations/cloudflare-kv · Updated: 2026-10-28

Cloudflare KV is a key-value store replicated across Cloudflare's network, and a driver behind two interfaces: [`kv`](https://mainsdk.dev/docs/interfaces/kv), for small values and cached results, and [`config`](https://mainsdk.dev/docs/interfaces/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.

> **Note:** `kv` has no spec yet. Its calls and options on this page are provisional, taken from the catalog and the product requirements, and may change when the spec lands. `config` comes with the config-secrets spec.

## Interfaces it serves

### Key-value store and cache (`kv`)

- Status: Building for 0.3 · ships as a beta API
- Driver: `cloudflare-kv` (Cloudflare KV). planned
- Docs: https://mainsdk.dev/docs/interfaces/kv.md

```yaml
kv:
  driver: cloudflare-kv
```

Swap to another provider: `local`, `redis`, `upstash`, `vercel-runtime-cache`.

### Runtime settings (`config`)

- Status: Building for 0.2 · ships as a stable API
- Driver: `cloudflare-kv` (Cloudflare KV). planned
- Docs: https://mainsdk.dev/docs/interfaces/config.md

```yaml
config:
  driver: cloudflare-kv
```

Swap to another provider: `files`, `local`, `redis`, `postgres`, `vercel-global-config`, `aws-appconfig`.

## 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:

```yaml title="main.yaml"
config:
  driver: cloudflare-kv
  schema: ./config.schema.json
  # token: secret:CLOUDFLARE_API_TOKEN   # suggested name; connection keys (account, namespace) are [TBD]
```

For the cache and key-value store:

```yaml title="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](https://mainsdk.dev/docs/interfaces/secrets).

## Use it

Read a typed setting and react when it changes:

```ts title="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:

```ts title="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 [TBD].
- **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](https://mainsdk.dev/docs/interfaces/config) and [Key-value store and cache](https://mainsdk.dev/docs/interfaces/kv).

## 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`](https://mainsdk.dev/docs/interfaces/limits) and [`db`](https://mainsdk.dev/docs/interfaces/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](https://mainsdk.dev/docs/concepts/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 [TBD].
- `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

```yaml title="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

- [Runtime settings](https://mainsdk.dev/docs/interfaces/config) and [Key-value store and cache](https://mainsdk.dev/docs/interfaces/kv)
- [Vercel](https://mainsdk.dev/docs/integrations/vercel), [AWS AppConfig](https://mainsdk.dev/docs/integrations/aws-appconfig) and [Upstash](https://mainsdk.dev/docs/integrations/upstash)
- [Swap a driver](https://mainsdk.dev/docs/guides/swap-a-driver)
