Cloudflare KV: Distributed Config Storage for Edge Apps
Get new tutorials every Tuesday
Join developers reading CoreCodery Weekly — Flutter, Cloudflare & Supabase.
No spam. Unsubscribe anytime.
Cloudflare KV: Distributed Config Storage for Edge Apps
*Most developers reach for a database when they need to store configuration. For edge apps on Cloudflare Workers, that is usually the wrong tool. Cloudflare KV exists for exactly this use case — and once you understand when to use it (and when not to), it changes how you build edge-first apps.*
What Is Cloudflare KV?
Cloudflare KV is a globally distributed key-value store built into the Cloudflare Workers platform. Think of it as a giant dictionary — you store a value under a key, and read it back from any Worker anywhere in the world with a single line of code.
Unlike a database, KV has no rows, no joins, no transactions. It stores arbitrary strings (or binary data) under arbitrary keys. That simplicity is what makes it fast: reads are served from local in-memory caches at Cloudflare's 300+ edge locations, typically in under 1ms.
What KV Is Good For
KV shines for read-heavy, write-infrequent data:
- Site-wide config: maintenance_mode, announcement_banner, tagline
- Feature flags: feature_new_checkout: "true"
- Caching computed results or rendered HTML
- Session tokens or short-lived auth state
- Rate limiting counters with TTL
When NOT to Use KV
KV is the wrong tool when you need:
- Structured queries — KV has no SQL, no indexes, no filtering
- Transactional writes — no atomicity across multiple keys
- Strong consistency — KV is eventually consistent (writes take up to 60 seconds to propagate globally)
- Relational data — use D1 for that
If you need to query `SELECT * FROM items WHERE price > 10`, that is D1. If you need to read `site_config` on every request, that is KV.
KV vs D1 vs R2 — Decision Matrix
Here is a quick reference for choosing the right Cloudflare storage primitive:
- KV — key-value config, feature flags, caching responses, session tokens with TTL
- D1 — relational data, SQL queries, structured content with filtering, user-generated data with joins
- R2 — file storage, images, binaries, large blobs
The short version: KV for config and cache, D1 for data, R2 for files.
Setup — wrangler.jsonc Bindings
Add a KV namespace binding to `wrangler.jsonc`:
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2024-12-01",
"kv_namespaces": [
{
"binding": "SITE_CONFIG",
"id": "REPLACE_WITH_KV_NAMESPACE_ID"
}
]
}Create the namespace with Wrangler:
wrangler kv:namespace create SITE_CONFIGCopy the `id` from the output and paste it into `wrangler.jsonc`. The `binding` name (`SITE_CONFIG`) is how your Worker code references the namespace — it becomes a typed property on the `Env` object.
For local development, Wrangler emulates KV automatically with `wrangler dev --local`. You do not need a separate local namespace.
Reading Config in a Worker
Once the binding is in place, `env.SITE_CONFIG` is a fully typed `KVNamespace` object available inside your `fetch` handler.
export interface Env {
SITE_CONFIG: KVNamespace;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const configJson = await env.SITE_CONFIG.get("site_config");
const config = configJson ? JSON.parse(configJson) : null;
if (config?.maintenance_mode) {
return new Response("Site under maintenance. Back soon.", { status: 503 });
}
return new Response("OK");
},
};KV reads return `string | null`. If the key does not exist, you get `null` — not an error. Always handle the null case.
Typed Config
For structured config, store JSON and parse it on read. Define a TypeScript interface to stay type-safe:
interface SiteConfig {
site_name: string;
tagline: string;
maintenance_mode: boolean;
announcement: string | null;
}
async function getSiteConfig(env: Env): Promise<SiteConfig | null> {
const raw = await env.SITE_CONFIG.get("site_config");
if (!raw) return null;
return JSON.parse(raw) as SiteConfig;
}Writing and Invalidating Values
KV writes use `.put()`:
await env.SITE_CONFIG.put("site_config", JSON.stringify({
site_name: "CoreCodery",
tagline: "Build fast. Ship smarter.",
maintenance_mode: false,
announcement: null,
}));TTL — Automatic Expiration
Set a time-to-live so keys expire automatically:
// Expires in 5 minutes
await env.SITE_CONFIG.put("cached_homepage", html, {
expirationTtl: 300,
});
// Expires at a specific Unix timestamp
await env.SITE_CONFIG.put("promo_banner", bannerJson, {
expiration: Math.floor(Date.now() / 1000) + 86400, // 24 hours
});TTL is useful for cached data. For config values you manage manually, omit TTL and write a new value whenever you want to update.
Deleting Keys
await env.SITE_CONFIG.delete("site_config");You can also delete from the CLI:
wrangler kv:key delete --namespace-id YOUR_KV_ID "site_config"Real Example: CoreCodery.com Site Config
CoreCodery.com uses KV for site-wide configuration that needs to change without a redeploy. Here is the binding from our production `wrangler.jsonc`:
"kv_namespaces": [
{
// Config for site-wide settings: feature flags, announcements
"binding": "SITE_CONFIG",
"id": "a543a46e5ccd4ab5bc4b25dc479a4474"
}
]The value stored at key `site_config` is a JSON object:
{
"site_name": "CoreCodery",
"tagline": "Build fast. Ship smarter.",
"maintenance_mode": false,
"announcement": null
}In our Next.js + Cloudflare Workers setup (via opennextjs-cloudflare), we read this config in middleware before any page renders:
// middleware.ts
import { NextRequest, NextResponse } from "next/server";
export async function middleware(request: NextRequest) {
const ctx = (request as unknown as { cloudflare?: { env?: Env } }).cloudflare;
const env = ctx?.env;
if (env?.SITE_CONFIG) {
const raw = await env.SITE_CONFIG.get("site_config");
const config = raw ? (JSON.parse(raw) as { maintenance_mode?: boolean }) : null;
if (config?.maintenance_mode && !request.nextUrl.pathname.startsWith("/admin")) {
return NextResponse.rewrite(new URL("/maintenance", request.url));
}
}
return NextResponse.next();
}To enable maintenance mode across the entire site, we run one CLI command — no redeploy required:
wrangler kv:key put --namespace-id a543a46e5ccd4ab5bc4b25dc479a4474 "site_config" '{"site_name":"CoreCodery","tagline":"Build fast. Ship smarter.","maintenance_mode":true,"announcement":"Scheduled maintenance — back in 30 minutes."}'The change propagates to all 300+ Cloudflare edge locations within 60 seconds. No redeploy, no migration, no config server to maintain.
Gotchas to Know
1. Eventual Consistency
KV writes propagate globally within about 60 seconds. If you write a value and immediately read it from a different geographic region, you may get the old value.
Practical impact: For config that changes rarely (maintenance mode, feature flags), 60 seconds of lag is fine. For data that changes frequently or needs immediate consistency everywhere — use D1 or Supabase.
2. No Transactions
KV has no atomic multi-key operations. You cannot update two keys as a single atomic unit. If two config values must always be consistent with each other, store them in the same key as a single JSON object — that is what we do with `site_config`.
3. Value Size Limit
KV values are limited to 25 MB. Keys are limited to 512 bytes. For most config use cases this is not a constraint. For large binary assets, use R2.
4. Write Limits on Free Tier
The free tier allows 1,000 KV writes per day and 100,000 reads per day. Config values are written rarely, so you will almost never hit this. If you use KV as a cache with a high write rate, plan accordingly.
Wrapping Up
Cloudflare KV is the right tool for a specific job: globally distributed, read-heavy, infrequently-written config and cache. In that lane, it is fast, simple, and requires zero infrastructure management.
The pattern we use at CoreCodery — a single `site_config` JSON key that controls maintenance mode, announcements, and site metadata — has worked reliably in production with sub-1ms reads and zero operational overhead.
When you need relational data or transactional writes, use D1 (see Build a Serverless REST API with Cloudflare Workers + D1). When you need file storage, use R2. When you need global config that reads fast from everywhere and rarely changes — KV is exactly what you want.
*Next in this series: Cloudflare R2 — object storage for Edge Apps.*
*Tags: Cloudflare, KV, Workers, Edge Computing, TypeScript, Configuration*
Enjoyed this? Get notified of new posts.
Weekly tutorials on Flutter, Cloudflare & Supabase — free.
No spam. Unsubscribe anytime.