HomeJournalThis post

Build an offline draft queue with IndexedDB

A durable browser queue needs local identity, dependency-aware replay, idempotency, conflict handling, storage policy, and truthful sync states.

JP
JP Casabianca
Designer/Engineer · Bogotá

Saving a form to local storage is not an offline workflow.

Real drafts need multiple records, ordered pending operations, schema upgrades, transactional updates, quota behavior, expiry, account boundaries, synchronization, and language that distinguishes saved on this device from accepted by the server.

The Indexed Database API 3.0 provides transactional object stores, indexes, upgrade transactions, and durability hints for substantial client-side data.

We will model drafts and sync operations separately, commit them in one transaction, and process the queue with stable operation IDs so interruption never requires guessing what reached the server.

The product promise is preserved work, not magical offline parity.

01 · DraftPreserve editable state

Store versioned field values, account and object scope, updated time, sensitivity class, and local status.

02 · OperationRecord pending intent

Append a stable operation ID, base revision, changed fields, dependency, attempt count, and next retry time transactionally.

03 · SyncReconcile with server

Claim ready operations, send idempotently, apply accepted revisions, surface conflicts, and delete only after durable confirmation.

Figure 1: A local draft queue separates content, intent, and synchronization.

Define the offline boundary

Decide which work can be created, edited, reviewed, submitted, or merely preserved without a server connection.

I would pressure-test that decision with four questions:

  • What works offline?
  • What only queues?
  • What requires fresh server truth?
  • What must never be stored locally?

The failure mode here is claiming offline support because text survives refresh. In long forms, field work, support notes, inspections, applications, and mobile workflows where navigation, tab closure, browser suspension, unstable networks, storage pressure, schema change, and authentication expiry can interrupt user work, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be an offline capability table. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is a bounded and truthful product promise. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put an offline capability table beside the question “What works offline?” before the first implementation review. The next pass would use “What only queues?” to test the boundary, then “What requires fresh server truth?” to expose the state most likely to be missed. I would keep “What must never be stored locally?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports a bounded and truthful product promise.

Design separate object stores

Draft snapshots and pending operations have different lifecycles, queries, and cleanup rules and should not be collapsed into one opaque blob.

The practical review starts here:

  • What identifies a draft?
  • What identifies an operation?
  • Which indexes are needed?
  • Which records change together?

Those questions keep placing the whole app in one localStorage value from becoming the default. I would capture the decision in a drafts/operations/meta schema, then use it while the work is still cheap to change. For offline-capable draft preservation, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like queryable and evolvable local state. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a drafts/operations/meta schema part of the working surface. I would use it to answer “What identifies a draft?” while scope is still flexible, and “What identifies an operation?” before code or content becomes expensive to unwind. During QA, “Which indexes are needed?” and “Which records change together?” become concrete checks rather than discussion prompts. That sequence turns offline-capable draft preservation into something the team can operate and gives me a specific outcome to report: queryable and evolvable local state.

  1. DeviceDraft saved locally

    The browser committed a transaction, but another device and the server do not have the change.

  2. QueuedWaiting to synchronize

    Network, auth, dependency, or backoff currently blocks the pending operation.

  3. AcceptedServer confirmed

    The operation ID and resulting server revision are recorded before local cleanup.

Figure 2: Offline status should describe where the work exists.

Wrap transaction completion

IndexedDB requests succeeding is not the same as the transaction committing, so helpers should resolve only on complete and reject on abort or error.

Before implementation, I would answer:

  • What event means committed?
  • Can one request abort all writes?
  • Are errors propagated?
  • Does the helper close over inactive transactions?

The artifact is a promise-based transaction wrapper. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is resolving after the final request callback; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.

For me, the useful receipt is accurate local durability state. That connects a transactional IndexedDB queue that stores versioned local operations, surfaces durability honestly, and synchronizes idempotently to an observable result instead of a process claim.

I would test this with one typical case and one boundary case. The typical case should make “What event means committed?” easy to answer. The boundary should force a decision about “Can one request abort all writes?” and “Are errors propagated?.” I would record both in a promise-based transaction wrapper, including the part that stayed unresolved after the first pass. The final check, “Does the helper close over inactive transactions?,” is where the artifact earns its place: it either supports accurate local durability state, or it shows exactly why another iteration is needed.

Write draft and operation atomically

A UI change that creates sync intent should update the draft and append the queued operation in one transaction.

I would use these prompts during the working review:

  • Can the draft change without a queue item?
  • Can the queue reference missing data?
  • What if serialization fails?
  • Which stores share scope?

If the team slips into writing draft and queue in separate best-effort calls, the product can still look complete while its operating rule stays ambiguous. I would make a single readwrite save transaction the shared reference and keep it small enough to update as evidence changes.

The standard is local content and pending intent that cannot drift apart. That tells me whether the decision helped the product, not merely whether the document was completed.

The working sequence is small: draft a single readwrite save transaction, review it against “Can the draft change without a queue item?,” implement the narrowest useful path, and then return with evidence for “Can the queue reference missing data?.” I would use “What if serialization fails?” to inspect product consequence and “Which stores share scope?” to decide whether the result is stable enough to ship. This keeps writing draft and queue in separate best-effort calls visible as a known risk and makes local content and pending intent that cannot drift apart the release receipt rather than a hopeful conclusion.

SignalDecisionWorking note
EphemeralRelaxed durabilitySearch history or reconstructable cache may favor speed and battery over crash-level persistence.
User workDefault or strict reviewLong-form drafts deserve stronger persistence, explicit transaction completion, and recovery tests.
SensitiveMinimize or avoidSecrets, regulated content, and shared-device risk may require encryption, short expiry, or no local storage.
Figure 3: Storage choices follow consequence.

Give operations stable identities

A UUID or equivalent operation key lets the server recognize a retry after the client loses the response.

I would pressure-test that decision with four questions:

  • When is the ID created?
  • How long is deduplication retained?
  • What response is replayed?
  • Can payload change under one ID?

The failure mode here is creating a new request ID on every retry. In long forms, field work, support notes, inspections, applications, and mobile workflows where navigation, tab closure, browser suspension, unstable networks, storage pressure, schema change, and authentication expiry can interrupt user work, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be an idempotent sync operation contract. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is safe recovery from unknown submission outcomes. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put an idempotent sync operation contract beside the question “When is the ID created?” before the first implementation review. The next pass would use “How long is deduplication retained?” to test the boundary, then “What response is replayed?” to expose the state most likely to be missed. I would keep “Can payload change under one ID?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports safe recovery from unknown submission outcomes.

Build a claim-and-retry loop

The sync worker should claim ready operations, respect dependencies, cap concurrency, classify failures, and use backoff without blocking editing.

The practical review starts here:

  • Which operations are ready?
  • Can two tabs sync?
  • Which failures retry?
  • When does the user intervene?

Those questions keep sending every pending operation on every online event from becoming the default. I would capture the decision in a lease-based queue processor, then use it while the work is still cheap to change. For offline-capable draft preservation, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like controlled synchronization under repeated connectivity. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a lease-based queue processor part of the working surface. I would use it to answer “Which operations are ready?” while scope is still flexible, and “Can two tabs sync?” before code or content becomes expensive to unwind. During QA, “Which failures retry?” and “When does the user intervene?” become concrete checks rather than discussion prompts. That sequence turns offline-capable draft preservation into something the team can operate and gives me a specific outcome to report: controlled synchronization under repeated connectivity.

Handle conflicts without deleting drafts

A server revision mismatch should preserve local work, fetch current truth, and move the draft into a resolvable conflict state.

Before implementation, I would answer:

  • What base revision was used?
  • Can changes merge?
  • Which fields conflict?
  • What survives discard?

The artifact is a local/base/server conflict record. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is dropping the queue item on a 409 or 412 response; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.

For me, the useful receipt is recoverable concurrency instead of data loss. That connects a transactional IndexedDB queue that stores versioned local operations, surfaces durability honestly, and synchronizes idempotently to an observable result instead of a process claim.

I would test this with one typical case and one boundary case. The typical case should make “What base revision was used?” easy to answer. The boundary should force a decision about “Can changes merge?” and “Which fields conflict?.” I would record both in a local/base/server conflict record, including the part that stayed unresolved after the first pass. The final check, “What survives discard?,” is where the artifact earns its place: it either supports recoverable concurrency instead of data loss, or it shows exactly why another iteration is needed.

Migrate schemas deliberately

Database version upgrades need small, synchronous structural changes and follow-up data migration that can resume if the browser closes.

I would use these prompts during the working review:

  • Which store changes?
  • Can upgrade block other tabs?
  • How is backfill resumed?
  • What happens to old clients?

If the team slips into performing expensive asynchronous work inside upgradeneeded, the product can still look complete while its operating rule stays ambiguous. I would make a versioned upgrade and resumable backfill plan the shared reference and keep it small enough to update as evidence changes.

The standard is local data that survives application evolution. That tells me whether the decision helped the product, not merely whether the document was completed.

The working sequence is small: draft a versioned upgrade and resumable backfill plan, review it against “Which store changes?,” implement the narrowest useful path, and then return with evidence for “Can upgrade block other tabs?.” I would use “How is backfill resumed?” to inspect product consequence and “What happens to old clients?” to decide whether the result is stable enough to ship. This keeps performing expensive asynchronous work inside upgradeneeded visible as a known risk and makes local data that survives application evolution the release receipt rather than a hopeful conclusion.

Design quota, expiry, and privacy

Local persistence is device data with storage limits, shared-device exposure, deletion requests, logout behavior, and sensitivity consequences.

I would pressure-test that decision with four questions:

  • How much can be stored?
  • Which drafts expire?
  • What clears on logout?
  • Is encryption useful or misleading?

The failure mode here is keeping every draft forever because IndexedDB feels private. In long forms, field work, support notes, inspections, applications, and mobile workflows where navigation, tab closure, browser suspension, unstable networks, storage pressure, schema change, and authentication expiry can interrupt user work, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a client-storage data policy. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is bounded storage with explicit user protection. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put a client-storage data policy beside the question “How much can be stored?” before the first implementation review. The next pass would use “Which drafts expire?” to test the boundary, then “What clears on logout?” to expose the state most likely to be missed. I would keep “Is encryption useful or misleading?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports bounded storage with explicit user protection.

Test browser interruption

Verification should close tabs mid-transaction, kill network after send, open competing tabs, exhaust quota, upgrade schema, expire auth, and restore drafts.

The practical review starts here:

  • Does commit status remain honest?
  • Can a retry duplicate?
  • Can migrations resume?
  • Does logout remove the right data?

Those questions keep testing only DevTools offline after a successful load from becoming the default. I would capture the decision in an offline interruption harness, then use it while the work is still cheap to change. For offline-capable draft preservation, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like evidence that user work survives the failures that matter. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make an offline interruption harness part of the working surface. I would use it to answer “Does commit status remain honest?” while scope is still flexible, and “Can a retry duplicate?” before code or content becomes expensive to unwind. During QA, “Can migrations resume?” and “Does logout remove the right data?” become concrete checks rather than discussion prompts. That sequence turns offline-capable draft preservation into something the team can operate and gives me a specific outcome to report: evidence that user work survives the failures that matter.

What I would show in the work

The public version needs evidence from the work itself. For this topic, the first five artifacts I would reach for are:

  • an offline capability table
  • a drafts/operations/meta schema
  • a promise-based transaction wrapper
  • a single readwrite save transaction
  • an idempotent sync operation contract

I would not publish all five at equal weight. One should orient the reader, one should reveal the hardest tradeoff, and one should prove the result. The others can live in a downloadable note or appear as supporting frames. That edit matters because a transactional IndexedDB queue that stores versioned local operations, surfaces durability honestly, and synchronizes idempotently becomes harder to understand when every process detail is treated as equally important.

I would also show one rejected direction. The useful version is specific: which option looked attractive, which constraint made it wrong, and what evidence supported the narrower choice. That gives an engineering manager something real to question and keeps the case study from reading like the final answer was obvious from the beginning.

indexeddb-draft-queue.ts
# drafts
key: account/object
schemaVersion, fields, baseRevision, localRevision, updatedAt, status, sensitivity, expiresAt.

# operations key: operationId draftKey, patch, dependencyIds, attempt, nextAttemptAt, createdAt, authScope.

# sync claim → send → commit One readwrite transaction records accepted revision and removes operation; conflict preserves both local and server state.

Figure 4: The schema keeps retry and cleanup deterministic.

Resource path

The practical follow-up I would build is an IndexedDB draft queue starter with schema versions, object stores, transaction helpers, operation IDs, local status, quota handling, migrations, sync worker, conflict policy, encryption decision, cleanup, and tests. I am treating that as a resource backlog item, not pretending the adjacent downloads below are the same artifact. The related cards cover useful pieces of the workflow today; this specific file should only be published when its examples, fields, and instructions are complete.

The first version should stay concise: context, constraint, decision, evidence, owner, and follow-up. Its value would come from helping someone repeat this exact review, not from adding another generic PDF to the site.

Review checklist

The article-specific review questions are:

  • What works offline?
  • What identifies a draft?
  • What event means committed?
  • Can the draft change without a queue item?
  • When is the ID created?
  • Which operations are ready?
  • What base revision was used?
  • Which store changes?
  • How much can be stored?
  • Does commit status remain honest?

I would add two editorial checks before publishing: can a recruiter find the point in the first minute, and can an engineer trace at least one claim to an implementation or production receipt? If either answer is no, the article needs another edit.

Implementation notes

For offline-capable draft preservation, I would write the implementation note before polish. It would name the changed surface, source of truth, owner, failure boundary, and verification path. Those details prevent the principle from floating above the actual code or operational workflow.

The proof signals I care about are specific to this article:

  • controlled synchronization under repeated connectivity
  • recoverable concurrency instead of data loss
  • local data that survives application evolution
  • bounded storage with explicit user protection
  • evidence that user work survives the failures that matter

I would choose two or three of those signals for the first release rather than instrumenting everything. The strongest pair usually combines one direct behavior check with one operating check: a route and a data query, a keyboard path and a support state, a handler replay and a reconciliation result, or a migration count and a rendered screen.

The follow-up belongs in the note before shipping. It should say what remains temporary, what evidence would trigger another pass, and who owns that decision. That is how the first version stays intentionally narrow without making the boundary invisible.

Case-study packaging

I would structure the case-study version around the four visual lessons already established:

  • A local draft queue separates content, intent, and synchronization.
  • Offline status should describe where the work exists.
  • Storage choices follow consequence.
  • The schema keeps retry and cleanup deterministic.

The opening frame explains the product pressure. The middle two show the decision moving through the system. The last frame is the receipt: what was checked, what held, and what remained unresolved. That order lets the reader move from product judgment into implementation detail without reconstructing the whole project first.

I would include one caveat tied to long forms, field work, support notes, inspections, applications, and mobile workflows where navigation, tab closure, browser suspension, unstable networks, storage pressure, schema change, and authentication expiry can interrupt user work: a data limit, rollout boundary, unsupported state, external dependency, or result that is still directional. A precise caveat makes the evidence easier to trust because it shows where the claim stops.

The final test is whether the page creates a better conversation. If the artifact helps someone ask a sharper question about product judgment, implementation detail, or release proof in a live interview, it belongs in the story.

Interview angle

In an interview, I would explain this through a transactional IndexedDB queue that stores versioned local operations, surfaces durability honestly, and synchronizes idempotently. The story should start with the product pressure, then move into the system constraint, the artifact, and the proof. That order keeps the answer grounded. It also gives the interviewer several places to go deeper: data, frontend architecture, design systems, support, migration, accessibility, or release process.

The strongest version of the answer includes a tradeoff. I want to be able to say what I chose, what I left alone, and how I knew the work helped. That is more credible than presenting every project as a clean win.

The hiring signal

An IndexedDB draft queue is a hiring signal because it shows I can design local persistence as a data system with transactions, migrations, privacy, synchronization, and product states.

That is the level I want this site to communicate. The work should show taste, but it should also show operating judgment. It should make me look like someone who can enter a real product system, understand the messy middle, ship the useful version, and leave enough proof for the next person to trust it.

Companion artifacts

Use this after reading.

Practical downloads and templates that turn the article into something you can bring into a product review, implementation pass, or agent workflow.

DownloadJun 2026

Front-End State Recipes

Reusable recipes for optimistic actions, loading, empty, error, data-transition, and disabled-control states.

FrontendStatesUX
View details
TemplateJul 2026

Dependency Adoption Receipt

A reviewable receipt for package need, identity, provenance, permissions, supply-chain risk, verification, ownership, and removal.

Supply chainSecurityAI-assisted
View details
DownloadJun 2026

UI PR Risk Review Checklist

A merge-readiness checklist for product intent, states, accessibility, visual durability, and UI implementation risk.

UI reviewQAFrontend
View details