how to build

Guide · 7 of 8

Keep data

Sooner or later your project needs to remember things: a high score, a list of habits, a photo, who's in the game room. This page goes through the places you can keep data, from the simplest to the hardest. Start with the simplest and only move on when you need to. And build the thing before you build the saving: a game you can play that forgets your score is worth more than a save system for a game that does not exist yet. You'll know which one to pick by the end.

You need

The six options, simplest first

  1. localStorage: in the browser, one device, no server.
  2. Workers: your own small server code. Everything below needs one.
  3. KV: simple named values, fast to read from anywhere.
  4. D1: a real database with tables.
  5. R2: files, like images and audio.
  6. Durable Objects: one tiny server per room or game, for live multiplayer.

1. localStorage

localStorage is a small notebook inside the browser. Your page can write text into it and read it back later, even after a refresh or a restart. No server, no account, free.

It is enough when the data belongs to one person on one device: a best score, settings, a to-do list just for you. The meal picker in chapter 4 used it.

const meals = JSON.parse(localStorage.getItem("meals") || "[]");
meals.push("tacos");
localStorage.setItem("meals", JSON.stringify(meals));
console.log(localStorage.getItem("meals"));

localStorage only holds text, so lists go through JSON.stringify on the way in and JSON.parse on the way out.

When you need a server

The moment you want data to follow you between devices, or be shared between people, the browser isn't enough. You need code that runs somewhere else and keeps the data for everyone. On Cloudflare, that code is a Worker.

Workers

A Worker is a small JavaScript function that runs on Cloudflare's computers, close to whoever is using it. A request comes in (someone opens a URL, or your page sends some data), your function runs, and it sends back an answer. You don't manage a machine. You write the function and deploy it.

Here is a whole Worker. It answers every request with one line of text:

export default {
  async fetch(request, env) {
    return new Response("Hello from a Worker");
  }
};

To make and deploy one, tell the agent:

Make a new Cloudflare Worker project in this folder that answers every request with "Hello from a Worker". Deploy it and give me the address. Stop and tell me when I need to log in.

You should see: it sets up the folder, pauses once so you can log in to Cloudflare in a browser, deploys, and ends with a workers.dev address. Open it and you see "Hello from a Worker".

By hand, three commands

Start a new Worker project. The setup tool asks a few questions and makes the folder for you:

npm create cloudflare@latest

Log Wrangler (Cloudflare's command-line tool) into your account. A browser opens and you click allow:

npx wrangler login

Put it online:

npx wrangler deploy

The env in that function is how your Worker reaches its storage. Each store below gets a name, called a binding, and shows up as env.THATNAME. You don't need to set bindings up by hand; ask the agent: "add a D1 database called DB to this Worker and set up the binding".

KV

KV (key/value) stores values under names, like a giant dictionary that every Worker in the world can read quickly.

Good for data that is read a lot and changed now and then: settings, a cached result, a saved quiz. In Quiz Maker, Workers AI writes a quiz once, KV keeps it, and everyone with the link reads it from KV.

await env.QUIZZES.put(id, JSON.stringify(quiz));
const saved = await env.QUIZZES.get(id);

D1

D1 is a real database (SQLite) with tables, rows and SQL (the language you use to ask a database questions).

Good for anything shaped like a spreadsheet that you want to search, sort or count: users, habits, scores, posts. In Habits, each check-in is a row, and a sync code lets you see the same habits on your phone and laptop.

const { results } = await env.DB
  .prepare("SELECT * FROM checkins WHERE code = ?")
  .bind(code)
  .all();

R2

R2 stores files: images, audio, game saves, anything too big to sit in a database row. It works like Amazon's S3, if you've heard of that.

Good for uploads and downloads. In Photo Wall, each photo goes into R2 and the gallery shows what's there.

await env.PHOTOS.put(name, request.body);
const photo = await env.PHOTOS.get(name);

Durable Objects

A Durable Object is one tiny server for one thing, like one game room, with its own storage. Everyone in that room talks to the same object, so they all see the same state at the same moment. It can hold WebSockets (a connection that stays open so the server can push updates to players instantly).

Good for live, shared things: multiplayer games, chat rooms, a shared whiteboard. In Arena, each match is one Durable Object that runs the game and sends every player's moves to the others.

Free tier numbers

The free plan covers everything in this guide. Numbers are per day unless marked. Checked 2026-11-02; they change, so see Cloudflare's pricing page.

ServiceFree tier
Static files (Pages)Free and unlimited
Workers100,000 requests/day
KV100,000 reads, 1,000 writes/day; 1 GB stored
D15 million reads, 100,000 writes/day; 5 GB stored
R210 GB stored; 1 million Class A and 10 million Class B operations per month; no fees for downloads
Durable ObjectsAvailable on the free plan (SQLite-backed)
Workers AIA free daily allowance; see the pricing page

Which one do I pick

You want to…Use
Remember things for one person on one devicelocalStorage
Run any code on a server, or hide a keyWorkers
Save something once and read it many timesKV
Keep lists of things you search, sort or countD1
Store images, audio or other filesR2
Let people play or talk together liveDurable Objects

Not sure? Ask the agent: "here's what my app needs to remember. Which of localStorage, KV, D1, R2 or Durable Objects should I use, and why?"

Secrets on a Worker

Some Workers need a key, for example to call another service. Never write it into your code. Give it to Cloudflare instead:

npx wrangler secret put NAME

Swap NAME for something like WEATHER_KEY. It asks you to paste the value, and it is stored on Cloudflare, not in your repo. Your code reads it as env.WEATHER_KEY. Type the value in yourself; don't paste it into a chat with the agent (see chapter 4).

What you just did

You met the six options: localStorage for one device, Workers for server code, then KV, D1, R2 and Durable Objects for shared data, files and live rooms. You know the free limits and the catch with each. The project briefs build each of these for real.