Chapter 08 · Offline Persistence, Local Cache, Synchronization, Conflict Behavior, and Offline Indexes

Test Airplane-Mode, Restart, Conflicting Writes, Reconnect, and Security-Rule Changes End to End

Test Firestore offline behavior end to end across restart, conflicts, reconnect and Security Rules changes, then define authoritative workflow states.

Intermediate135–160 minutesEnd-to-end offline failure matrixFirebase JS 12.19.0 · CLI 15.30.0Last reviewed: September 2026

Learning outcomes

01

Execute an end-to-end offline test matrix covering airplane-mode simulation, restart, two-client conflict, reconnect and Rules regression.

02

Observe metadata, pending-write Promise outcomes and final server state rather than treating UI appearance as proof of success.

03

Keep emulator tests isolated by resetting persistent cache and distinguish emulator evidence from production browser/mobile behavior.

04

Produce a release checklist defining which states are authoritative, which workflows may be offline, and which require online transactional authority.

Execution and safety note

Use the Emulator Suite, a Firebase demo project, or an isolated test project for destructive, security-sensitive, billing-sensitive, migration, backup/restore, or write-heavy exercises unless the lesson explicitly marks managed verification as required. Treat shown output as expected evidence unless it is explicitly identified as captured output, and re-check current Firebase/Google Cloud edition, mode, quota, pricing, and security documentation before production execution.

Chapter 08 reproducibility baseline · reviewed 16 September 2026

AtlasMart continues the same environment used in Chapters 01–07: project ID demo-atlasmart-firestore, Standard edition / Native mode / (default) database for the mandatory lab, Firestore emulator 127.0.0.1:8080, Authentication emulator 127.0.0.1:9099, Emulator UI 127.0.0.1:4000, Firebase CLI 15.30.0, Firebase JavaScript SDK 12.19.0, Firebase Admin Node SDK 14.4.0 (bundling @google-cloud/firestore 9.1.0), and Node.js 22+. The emulator has no production location or billing contract; any production region, pricing, SLA, IAM and managed-service latency remain explicitly unproven by this lab.

Evidence boundary

The mandatory exercises run locally and deliberately manipulate client network state. They prove client-cache, queued-write, listener, Rules and synchronization behavior for the pinned SDK/emulator combination. They do not prove production WAN p95/p99 latency, device-OS background behavior, billing, data-recovery guarantees, real multi-region propagation, every browser storage policy, or Enterprise Pipeline behavior. Pipeline operations do not support offline persistence; Enterprise offline exercises must use Core operations.

1. AtlasMart problem: offline success becomes a permission failure after reconnect

The most dangerous offline bug is not “the cache is empty.” It is a screen that says “saved” while the mutation is only pending, then silently loses the change after reconnect because Rules changed or authentication no longer authorizes it. Chapter 08 ends by making that failure visible and testable.

Scenario Cache mode Action Required evidence
T1 memory offline read memory warm document then disable network cached read/listener works in-process; restart loses it
T2 persistent restart persistent single-tab warm document, restart page/browser context cached document remains available
T3 multi-tab queued write persistent multi-tab disable network in one tab, observe second tab shared local persistence exposes coordinated pending mutation
T4 controlled conflict two isolated clients write different values offline; reconnect A then B final server state follows controlled commit order
T5 Rules regression persistent client queue write, tighten Rules, reconnect server rejects pending mutation; client must surface failure/rollback
T6 transaction offline any client cache disable network then run read/write transaction transaction rejects; no false success
T7 clean reset persistent terminate and clear test persistence next test starts without stale cached docs/pending writes

2. Test harness: keep every step observable

firebase.json
{  "firestore": { "rules": "firestore.rules", "indexes": "firestore.indexes.json", "edition": "standard" },  "emulators": {    "firestore": { "port": 8080, "edition": "standard" },    "auth": { "port": 9099 },    "ui": { "enabled": true, "port": 4000 }  }}
firestore.rules
rules_version = '2';service cloud.firestore {  match /databases/{database}/documents {    match /profiles/{uid} {      allow read, create, update: if request.auth != null && request.auth.uid == uid;      allow delete: if false;    }    match /carts/{uid} {      allow read, create, update: if request.auth != null && request.auth.uid == uid;      allow delete: if false;      match /items/{productId} {        allow read, create, update, delete: if request.auth != null && request.auth.uid == uid;      }    }    match /catalogItems/{productId} {      allow read: if true;      allow write: if false;    }    match /{document=**} { allow read, write: if false; }  }}
local setup
mkdir atlasmart-offline && cd atlasmart-offlinenpm init -ynpm install firebase-admin@14.4.0# Save firebase.json and firestore.rules from this lesson.printf '{"indexes":[],"fieldOverrides":[]}' > firestore.indexes.json# terminal Anpx firebase-tools@15.30.0 emulators:start --project demo-atlasmart-firestore --only firestore,auth# terminal B: serve browser filespython -m http.server 4173# then open http://127.0.0.1:4173/ in a supported browser
e2e scenario skeleton
import {  initializeFirestore, persistentLocalCache, persistentMultipleTabManager,  connectFirestoreEmulator, doc, setDoc, onSnapshot,  disableNetwork, enableNetwork, waitForPendingWrites, terminate,  clearIndexedDbPersistence} from "https://www.gstatic.com/firebasejs/12.19.0/firebase-firestore.js";// 1) start persistent + multi-tab; attach metadata listener// 2) go offline; set profile.offlineScenario="queued-before-rule-change"// 3) verify hasPendingWrites=true and fromCache=true/appropriate cache state// 4) while still offline, replace firestore.rules with the deny-write version// 5) let Emulator Suite reload the rules// 6) reconnect; await the write Promise / waitForPendingWrites and record rejection// 7) verify listener rolls back/re-synchronizes to accepted server state// 8) stop listeners; terminate(db); clearIndexedDbPersistence(db) before next test

Instrument every snapshot with ISO timestamp, document path, fromCache, hasPendingWrites, visible value and active user UID. Instrument every write Promise with a correlation ID and final resolve/reject outcome. A UI-only screenshot is insufficient evidence because it cannot distinguish local pending state from committed state.

3. Failure injection: change Rules while the write is pending

Start with the normal Chapter 08 Rules, warm the profile document, disable the SDK network and queue an update. While the client remains offline, replace the rule file with a version that denies profile writes. The Emulator Suite reloads the local rules; reconnect the client and observe that the backend rejects the queued mutation.

firestore.rules · normal
rules_version = '2';service cloud.firestore {  match /databases/{database}/documents {    match /profiles/{uid} {      allow read, create, update: if request.auth != null && request.auth.uid == uid;      allow delete: if false;    }    match /carts/{uid} {      allow read, create, update: if request.auth != null && request.auth.uid == uid;      allow delete: if false;      match /items/{productId} {        allow read, create, update, delete: if request.auth != null && request.auth.uid == uid;      }    }    match /catalogItems/{productId} {      allow read: if true;      allow write: if false;    }    match /{document=**} { allow read, write: if false; }  }}
firestore.rules · temporary deny-write regression
rules_version = '2';service cloud.firestore {  match /databases/{database}/documents {    match /profiles/{uid} {      allow read: if request.auth != null && request.auth.uid == uid;      allow create, update: if false;      allow delete: if false;    }    match /carts/{uid} {      allow read: if request.auth != null && request.auth.uid == uid;      allow create, update: if false;      allow delete: if false;      match /items/{productId} {        allow read, create, update, delete: if request.auth != null && request.auth.uid == uid;      }    }    match /catalogItems/{productId} {      allow read: if true;      allow write: if false;    }    match /{document=**} { allow read, write: if false; }  }}
Expected mechanism

The local view may show the queued value while offline. After reconnect, the write Promise should reject when the backend applies the new Rules, and synchronized state should return to an accepted server value. Record the actual error code/message from your run; do not hard-code a fictional transcript.

4. Restart matrix: memory versus persistent cache

Run the same warmed-document test under memory and persistent cache. A fresh memory-cache instance should not inherit prior process state. A persistent-cache instance should be able to recover cached documents across restart, subject to browser/platform storage availability. This proves persistence behavior for your test browser; it does not prove every mobile OS eviction/background policy.

5. Security Rules, Auth, App Check and IAM boundaries

The mandatory lab uses Auth emulator + Firestore Rules. App Check is intentionally not treated as offline authorization: it is an app-attestation/abuse-control signal for supported production requests, not a replacement for user authorization. Admin/server SDKs use Google credentials/IAM and bypass Firestore Security Rules, so a server-side repair script must enforce application authorization separately. The emulator exercise does not prove production App Check enforcement or IAM.

6. Release checklist: define the authoritative state

Workflow Offline allowed? Authority Reconnect action
profile preference yes, if privacy policy permits server-accepted profile doc show pending; surface reject and restore server state
cart editing yes as intent server cart after sync revalidate price/availability
inventory reservation no final success offline transaction/trusted service attempt reservation after reconnect
payment no payment/backend state machine fetch authoritative status
admin moderation usually no client offline path trusted backend + IAM require online audited action

For every offline-capable write, document: retry/idempotency semantics, how the user sees pending/rejected state, what happens on sign-out, which cached fields are sensitive, and how support staff can diagnose an offline conflict without collecting private document contents in logs.

7. Cleanup and repeatability

  1. Restore the normal firestore.rules.
  2. Wait for pending writes to resolve/reject; capture only synthetic test values.
  3. Detach all listeners.
  4. Terminate the Firestore instance and clear IndexedDB persistence for this test origin.
  5. Stop/restart Emulator Suite if you want an empty backend; remember that emulator shutdown alone does not clear client cache.
  6. Rerun T1–T7. The outcome class should match even if callback timing differs.

Production judgment

Offline readiness is a cross-layer property: cache policy, local UX, Rules/Auth state, conflict model, transaction boundaries, device privacy, telemetry, and incident recovery all matter. Test p95/p99 only on defined device/network/cache scenarios; never extrapolate from localhost. The next chapter moves to transactions, batched writes, atomic transforms, retry semantics and contention—the online mechanisms needed when last-write-wins is not enough.

Knowledge check

  1. What proves an offline edit was committed?
  2. Why change Rules while a write is pending?
  3. What must be cleared between persistent-cache emulator tests?
  4. Can App Check replace Security Rules authorization?
  5. Which next mechanism handles read-dependent invariants?
Review the answers

1. A local snapshot does not. You need successful backend acknowledgement/synchronization and no pending writes, plus application-specific validation where required.

2. It proves the app handles a realistic reconnect-time authorization failure instead of assuming queued writes always succeed.

3. Client persistent cache/pending state as well as emulator backend state, depending on the scenario.

4. No. App Check is not user authorization; Rules/IAM and application authorization remain necessary.

5. Transactions/trusted transactional workflows, covered in Chapter 09.

Summary and next step

You now have a deterministic offline test matrix and an explicit authority model. Chapter 09 builds the transaction/batch/atomic-transform tools used when offline last-write-wins cannot satisfy correctness.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.