Cloud saves
One garden, every device
Remy plays Garden Defense on a phone at breakfast. After school, Remy picks up a tablet. If the save lives in local storage (inside one browser on one device), the tablet starts at wave 1.
With a cloud save, the save lives on a server. The tablet logs in with the same account, which has the same UID (the player's unique user ID), and opens at wave 12, exactly where the phone left off.
Firestore: folders and cards
- CollectionLike a folder. It holds lots of documents of the same kind, such as "players".
- DocumentLike a card inside the folder. One card holds one thing, such as one player's save. Max size: 1 MiB.
- FieldOne line on the card: a label and a value, like wave: 12. Values can be numbers, text, true or false, lists and more.
A database is an organised store of data that a server can search very fast. Firebase's database is called Firestore.
One card per player
Our plan: a collection called players holds one document for each player, named with that player's UID. Its full address, or path, is players/k3P9xQ2m.
Inside, Remy's card might say:wave: 12seeds: 340sprigHat: "acorn"
To find a save, the server jumps straight to that path. No searching through millions of cards.
Document shape
Kai sketched three ways to store Garden Defense saves. Which one fits our plan of one card per player?
- One document called allSaves, with every player's progress in one long list
- A players collection with one document per UID, holding wave, seeds and sprigHat
- A separate collection for each field: one for waves, one for seeds, one for hats
Show the answer
A players collection with one document per UID, holding wave, seeds and sprigHat
One card per UID lets the server find a save in one step, and lets a security rule check the UID. One giant document would hit Firestore's limit of 1 MiB per document, and splitting the fields means three reads for every load.
Writing the card: setDoc
Here is the real save code:
const ref = doc(db, "players", user.uid);await setDoc(ref, { wave: 12, seeds: 340, sprigHat: "acorn"});
doc() builds the card's address. setDoc() writes the card. Watch out: setDoc replaces the whole card. To change only some fields, add { merge: true } as a third argument, or use updateDoc().
Bug hunt
1// Remy buys the straw hat in the shop2const ref = doc(db, "players", user.uid);3await setDoc(ref, { sprigHat: "straw" });
After buying the hat, Remy's garden is back at wave 1 with 0 seeds. Why?
- setDoc replaced the whole card, so wave and seeds were wiped
- Buying a hat gives the player a new UID
- Firestore cannot store text like "straw"
Show the answer
setDoc replaced the whole card, so wave and seeds were wiped
setDoc writes exactly what you give it, so the new card holds only sprigHat. Adding { merge: true } or using updateDoc changes the hat and keeps every other field.
Reading the card: getDoc
When the game starts, it loads the card:
const snap = await getDoc(ref);if (snap.exists()) { startGame(snap.data());} else { startGame({ wave: 1, seeds: 100 });}
A snapshot is a copy of the card at that moment. A brand-new player has no card yet, so we always check exists() first. Forgetting this is one of the most common cloud save bugs.
Save at good moments
Every read and write counts against our Firebase allowance. At the time of writing, the free Spark plan includes about 20,000 writes a day for the whole project.
A game at 60 FPS that saved every frame would make 3,600 writes a minute for one player. Firestore's own guides also warn that one document can't be updated at an unlimited rate, and suggest about one write a second as a safe target. So we save at good moments: when a wave ends, when you buy something, when you quit.
Bug hunt
1function update() {2 movePests();3 growFlowers();4 saveToCloud(player);5 draw();6}
update() runs 60 times a second. Which line would use up our Firebase allowance in minutes?
- Line 2
- Line 3
- Line 4
- Line 5
Show the answer
Line 4
Line 4 writes to the cloud on every frame, 3,600 times a minute. It belongs in the code that runs when a wave ends, not inside update().
No signal? No problem
Phones lose signal in tunnels, cars and lifts. When we switch on Firestore's offline cache, it keeps a copy of your card on the device. If you beat wave 15 on a bus with no signal, it remembers the change. When the signal comes back, it sends the change to the server. This catching up is called syncing.
One save per account now lives in the cloud. Next: what happens when that account plays on the web, a phone and Steam, and two devices disagree.
Bolt says: Collections are folders, documents are cards, fields are lines on the card. Tell a friend that and they'll understand Firestore.
Fact-checked 9 October 2026
- Cloud Firestore usage and limits firebase.google.com
- Cloud Firestore: add data (setDoc, merge, updateDoc) firebase.google.com
- Cloud Firestore data model firebase.google.com
- Cloud Firestore best practices firebase.google.com
- Cloud Firestore distributed counters firebase.google.com
- Cloud Firestore: access data offline firebase.google.com