Cloudflare KV, R2, and D1 Storage — Birdor Cloudflare Tutorial Series (Part 6)
Leeting Yan
Cloudflare offers a suite of distributed storage products that extend its serverless platform far beyond simple compute. KV, R2, and D1 each serve a different purpose — from configuration and caching to object storage and relational data.
This guide provides a calm, structured look at Cloudflare’s storage ecosystem and explains how to use each service with Workers and Pages Functions. The goal is clarity, not complexity.
1. The Philosophy of Cloudflare Edge Storage
Cloudflare’s storage model follows a simple idea:
Bring data closer to users, just like compute.
Rather than storing everything in a single region, Cloudflare distributes storage across its global network — reducing latency and making applications more resilient.
Each product balances:
- performance
- consistency
- durability
- cost
- operational complexity
Understanding the differences helps you select the right storage for your project.
2. Cloudflare KV (Key-Value Storage)
2.1 What Cloudflare KV Is
KV is a globally distributed key-value store — ideal for low-latency reads from anywhere in the world.
Strengths:
- extremely fast global reads
- simple API
- excellent for caching or configuration
- nearly instant availability worldwide
- large-scale, eventually consistent storage
Limitations:
- writes propagate slowly (eventual consistency)
- not ideal for transactional or relational data
- limited query patterns (key lookup only)
2.2 Typical Use Cases
KV fits well for:
- feature flags
- configuration values
- cached API responses
- user-facing JSON blobs
- session-less state
- storing Hugo-generated search indexes
- storing pre-rendered HTML snippets
- durable app settings
KV shines when reads are far more common than writes.
2.3 Adding KV to a Pages or Worker Project
Add KV binding in your Cloudflare dashboard:
Pages → Settings → Functions → KV Namespace Bindings
Example binding:
KV = my_kv_namespace
2.4 Using KV in a Function
export async function onRequest(context) {
await context.env.KV.put("greeting", "Hello Birdor");
const value = await context.env.KV.get("greeting");
return new Response(value);
}
KV stores only strings, but you can encode structured data using JSON.stringify.
3. Cloudflare R2 (Object Storage)
3.1 What R2 Is
R2 is S3-compatible object storage with no egress fees.
It is ideal for storing large files and binary data.
Strengths:
- S3 API compatible
- zero egress fees
- stores large objects (images, videos, backups)
- integrates with Workers, Pages, and Durable Objects
Limitations:
- higher latency than KV
- not designed for frequently changing small values
- not globally replicated by default (but can be accessed globally)
3.2 Typical Use Cases
R2 works best for:
- static assets (large binary files)
- media uploads
- backups and archives
- user-generated content
- large JSON files
- file hosting behind a Worker
- object-based CDN flows
R2 is great for workloads that would normally require S3.
3.3 Adding R2 to a Worker or Pages Project
Dashboard → R2 → Create Bucket
Then bind it:
R2 = my_r2_bucket
3.4 Using R2 in Workers or Pages Functions
Upload a file:
export async function onRequest(context) {
await context.env.R2.put("hello.txt", "Hello from Birdor");
return new Response("Uploaded");
}
Download a file:
export async function onRequest(context) {
const object = await context.env.R2.get("hello.txt");
return new Response(object.body);
}
Delete:
await context.env.R2.delete("hello.txt");
R2 is straightforward and ideal for large asset workflows.
4. Cloudflare D1 (SQL Database)
4.1 What D1 Is
D1 is Cloudflare’s serverless database built on SQLite — but replicated and synchronized globally.
Strengths:
- relational data
- SQL queries
- small to medium transactional workloads
- tight integration with Workers
- durable, persistent storage
Limitations:
- still evolving
- limited concurrency for heavy write workloads
- not yet a full replacement for high-throughput regional SQL clusters
However, for many apps, D1 is more than sufficient.
4.2 Typical Use Cases
D1 fits:
- user profiles
- small dashboards
- blog comments
- key metrics
- small-scale admin tools
- low-complexity relational data
- prototype APIs
- moderate-scale SaaS projects
D1 offers the relational model missing from KV and R2.
4.3 Adding D1 to a Worker or Pages Project
In Cloudflare:
Workers → D1 → Create Database
Then bind it:
DB = my_d1_database
4.4 Querying D1
D1 exposes two methods:
.prepare(sql).all(),.run(),.first()
Example: create a table
await context.env.DB.prepare(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT
)
`).run();
Insert:
await context.env.DB.prepare(
"INSERT INTO users (name) VALUES (?)"
).bind("Birdor User").run();
Select:
const { results } = await context.env.DB.prepare(
"SELECT * FROM users"
).all();
return new Response(JSON.stringify(results));
D1 brings durable relational data to edge applications.
5. Choosing Between KV, R2, and D1 (A Calm Decision Guide)
| Storage | Best For | Avoid When |
|---|---|---|
| KV | config, flags, cached JSON, search indexes | transactional updates |
| R2 | images, files, backups, big blobs | small frequent updates |
| D1 | relational data, user data, structured records | heavy write throughput |
You can combine them:
- KV for config
- D1 for relational data
- R2 for file uploads
Most Cloudflare-native apps use all three.
6. Using Cloudflare Storage with Hugo
A Hugo site is static, but Cloudflare storage enables:
Example use cases:
- contact forms → store in D1
- API caching → store in KV
- image uploads → store in R2
- comment system → store in D1 or KV
- real-time data → sync with Worker + KV
- page metadata → store in D1
These additions turn Hugo into a fully featured dynamic application without a traditional backend.
7. Best Practices for Working with Edge Storage
7.1 Keep KV writes minimal
KV is optimized for reads, not rapid write frequency.
7.2 Use JSON for structured data
KV + JSON is a strong pattern.
7.3 Store large assets in R2
Do not store binary data in KV or D1.
7.4 Use prepared statements with D1
Protects from SQL injection and improves performance.
7.5 Add caching layers in Workers
Using the Cache API:
const cache = caches.default;
7.6 Use versioning for R2 files
Good for file updates.
7.7 Leverage Durable Objects if you need state
Example:
- chat room live presence
- counters
- real-time coordination
8. Troubleshooting
KV Issues
- check bindings and environment names
- KV propagation may take seconds
- ensure keys are strings
R2 Issues
- bucket name must match binding
- binary uploads require correct encoding
D1 Issues
- schema changes require migration
- SQLite concurrency rules apply
- use
.prepare()consistently
9. Conclusion and What’s Next
In this chapter, we explored:
- the purpose of Cloudflare’s three storage systems
- what each is best for
- how to use KV, R2, and D1 in Pages Functions and Workers
- decision-making strategies for choosing storage
- best practices for edge-native data workflows
Cloudflare’s storage offerings unlock powerful capabilities for Hugo and JAMstack developers — from simple APIs to full edge applications.
Next chapter:
Cloudflare Tutorial Series — Part 7: Zero Trust and Security Essentials