Solution guide · Mobile App

Building a Mobile App With Offline-First Sync: Real Cost, Real Edge Cases

The short answer

Offline-first sync is buildable, and Digital Heroes has shipped it across field service, retail audit, logistics and clinical apps. Honest bands from our delivery data: adding it to an existing app runs $15,000 to $60,000 in 4 to 8 weeks. As part of a first release it runs $50,000 to $130,000 over 10 to 16 weeks. A full multi-tenant platform with offline as a core guarantee runs $150,000 to $350,000 over 6 to 12 months. The number moves on one question: how many users can edit the same record while offline. One editor per record is cheap. Many editors per record is where the budget doubles.

What offline-first actually means once real users touch it

Most vendors hear "works offline" and quote a cache. They store the last API response in local storage, show it when the network drops, and call it done. That is a read cache, not offline-first. Offline-first means the local database is the source of truth for the UI, every write lands locally first and returns instantly, and a background process reconciles those writes with the server whenever a connection appears. The user never waits on the network. That single inversion changes your data model, your API contract, your ID strategy and your support process.

The naive version fails like this. A field technician in a basement closes out a job at 10:40am, marks 3 parts consumed, uploads 4 photos, and taps Complete. No signal. At 11:15 the dispatcher, seeing the job still open in the web console, reassigns it to a second tech and edits the parts list. At 11:50 the first tech surfaces in the parking lot and the phone syncs. With naive last-write-wins on the whole record, the tech's payload has an older client timestamp than the dispatcher's edit, so the entire completion is silently discarded: photos gone, parts gone, job still open. Or the clock on that Android device is 6 minutes fast, the tech's stale write wins instead, and the dispatcher's reassignment vanishes. Both outcomes are data loss. Neither throws an error. Nobody finds out until the customer is invoiced for the wrong parts three weeks later, and by then your logs have rotated. That is the 3am page you cannot debug, because the evidence was overwritten by design.

The second classic failure is duplicates. The tech taps Complete, the request actually reaches the server, the server writes the row, and then the connection dies before the 201 comes back. The client retries. Now there are two jobs, two invoices, two parts deductions. Every offline app produces this on day one unless you designed against it.

Conflict resolution is a product decision, not a library setting

There is no framework that solves conflicts for you. You choose a strategy per entity, and someone in the business has to sign off on the choice.

Last-write-wins is the cheapest and it is defensible for genuinely single-owner data: a user's own profile, a draft note only they touch. If you use it, never trust the device clock. Android and iOS clocks drift, users change them manually, and timezone handling makes it worse. Use a Hybrid Logical Clock or a server-assigned monotonic sequence so ordering survives a wrong device clock. Field-level LWW rather than record-level LWW is a small change with a big payoff: the tech's status field and the dispatcher's assignee field both survive because they are different columns.

CRDTs are the real answer when multiple users edit the same object concurrently and you want automatic merge with no user prompt. Automerge and Yjs are the two production-grade options. Yjs is faster and has the better ecosystem for text and list editing. Automerge has cleaner JSON document semantics and built-in history. Both cost you storage: CRDT metadata grows with edit history, and on a document edited daily for a year you can see the doc outgrow the payload by several times unless you compact and snapshot. CRDTs also cannot express business rules. A counter CRDT will happily merge two concurrent decrements of your inventory to minus 3. If your data has invariants, CRDTs merge the data and break the meaning.

Operational transform is where you end up for collaborative rich text if CRDT overhead is unacceptable, and it is significantly harder to get right. Do not build one.

The strategy most business apps actually need is server-authoritative reconciliation: the client sends intents, not rows. Not "job.status = complete" but "CompleteJob(jobId, at, partsUsed)". The server replays the intent against current state, applies business rules, and can reject with a reason the app can show. This is more work up front and it is the only approach that survives an audit, because you keep both versions and the decision.

Whatever you pick, budget for the conflicts you cannot auto-resolve. You need a UI for them, a queue for the ones that need a human, and a policy for who wins. Teams that skip this ship an app that silently eats data for six months.

IDs, idempotency, and the duplicate record problem

If the server assigns IDs, offline creation is impossible without a temporary local ID, and then every foreign key pointing at that temp ID has to be rewritten when the real ID arrives. That rewrite is a recursive graph update and it is a common source of orphaned children. The fix is to generate IDs on the client: UUIDv4 works, UUIDv7 is better because it is time-ordered and does not shred your B-tree index the way v4 does on a large Postgres table. ULID is an equivalent choice. The client owns the ID from creation, nothing needs rewriting, and the record has the same identity everywhere from the moment it exists.

Client-generated IDs also give you idempotency for free if you use them properly. Every mutation carries a client-generated operation ID. The server stores processed operation IDs and returns the original result on a repeat rather than executing again. Keep that table for at least 7 days, because a phone can be in a drawer over a long weekend and still retry on Tuesday. Stripe's API is the reference implementation to copy here: idempotency key on the request, cached response, explicit conflict if you reuse a key with a different body.

Then sequence the queue. Mutations are not independent. Creating a job and then adding a note to it must replay in order, and if the create fails permanently the note must not be retried forever. You need a durable outbox with ordering, dependency awareness, retry with exponential backoff and jitter, and a poison-message path after N attempts that surfaces to the user instead of looping silently and burning battery.

The sync protocol and the parts nobody quotes for

Full-table pull on every app open is fine at 500 rows and fatal at 50,000. You need delta sync: the client sends a cursor, the server returns everything changed since. Two ways to build the cursor. A monotonic server-side sequence number or logical clock is correct. A plain updated_at timestamp is the tempting one and it is subtly broken: two rows written inside the same transaction can share a timestamp, and a row committed after your cursor but with an earlier timestamp is invisible forever. If you use timestamps, you need an overlap window and dedupe on the client, and you need to accept that clock skew across replicas will bite you.

Deletes are the thing everyone forgets. If you hard-delete on the server, the deleted row simply never appears in the delta and stays on the device forever. You need tombstones, a retention window for them, typically 30 to 90 days, and a hard rule that any client whose cursor is older than that window must do a full resync instead of a delta. Also plan for a device that has been offline for 4 months and comes back with a cursor older than every tombstone you kept.

Partial sync is what makes it viable at scale. Do not sync the whole tenant to a phone. Sync the user's territory, their next 14 days, their assigned accounts. Then handle the reassignment case: when the scope changes, the rows that left scope must be evicted locally, and the app has to tell the difference between "deleted" and "no longer yours". Getting that wrong leaks another user's data onto a device.

On storage, WatermelonDB and SQLite via op-sqlite or expo-sqlite are the React Native workhorses. PowerSync and ElectricSQL both sync Postgres to local SQLite and are the strongest current managed choices if your backend is already Postgres; Ditto is the option when devices must sync peer to peer with no server in the middle. Room with WorkManager on Android, Core Data with NSPersistentCloudKitContainer or a custom queue on iOS. One correction worth making explicitly, because half the tutorials online are stale: MongoDB retired Realm Sync and Atlas Device Sync on 30 September 2025, so it is not a candidate, whatever a 2023 blog post says.

Two platform behaviours will cost you a sprint each. iOS suspends your app aggressively and background execution is not a promise, so a sync you scheduled may run hours later or not at all until the user reopens the app; BGProcessingTask helps and does not guarantee. Android Doze and per-OEM battery killers, Xiaomi and Huawei are the worst offenders, will kill your WorkManager jobs entirely on some devices unless the user whitelists you. Your sync must be correct when it runs 9 hours late.

Testing, and why offline bugs escape QA

Offline bugs do not reproduce on a desk with fast wifi. The state space is combinatorial: two devices, three entities, network dropping at four different points in the request lifecycle. You cannot manual-test your way through it.

What a competent build does: a deterministic simulation harness that replays scripted operation logs against the merge function, so every conflict scenario is a unit test that runs in milliseconds. Property-based testing with fast-check or Hypothesis to assert convergence, that any two devices applying the same set of operations in any order end in the same state. Explicit tests for the killer case of a request that succeeds server-side and fails client-side. Chaos testing with Charles Proxy or the Network Link Conditioner profiles, not just airplane mode, because 2G with 40% packet loss breaks differently from no network. And schema migration tests against a real device database from the previous version, because a phone that skipped 6 releases will open your app with a 6-versions-old local schema and you get one shot at migrating it without wiping their unsynced work.

Budget 30 to 40 percent of the engineering effort here. On our projects that is not gold-plating, it is the difference between a launch and a rollback.

What this costs and what drives the number

These are Digital Heroes delivery bands from our own project history across 2,000+ builds, not market averages.

Adding offline-first sync to an existing, healthy app: $15,000 to $60,000, roughly 4 to 8 weeks. That is the band when the entity set is small, ownership is mostly single-user, and the backend already exposes clean write endpoints you can make idempotent.

Offline sync as part of a first release: $50,000 to $130,000 over 10 to 16 weeks. Here you are designing the schema, the sync protocol and the app at the same time, which is actually cheaper per unit of sync than retrofitting, because you are not fighting a data model that assumed the server always answers.

A full platform where offline is the product guarantee, multi-tenant, multi-role, audit trail, admin console: $150,000 to $350,000 over 6 to 12 months.

What pushes this specific capability up, in order of impact:

Concurrent editors per record. One owner per record is a small multiplier. Many editors on the same record forces CRDTs or a full intent-based reconciler and roughly doubles the sync engineering.

Number of synced entities and the depth of relationships between them. 5 flat entities is a different project from 30 entities with cascading foreign keys, because every relationship is a referential-integrity question at merge time.

Attachments. Photos, signatures and video are not rows. They need a separate resumable upload queue, chunking, dedupe by content hash, local eviction policy when the device fills up, and a story for the 400MB of unsynced photos on a phone that just broke. Attachments typically add $10,000 to $25,000 on their own.

Retrofitting onto a backend with server-generated sequential IDs and no idempotency. Expect to add 3 to 5 weeks before you write a single line of mobile sync code.

Regulated data. If the offline store holds PHI or payment data you now need encryption at rest with SQLCipher or platform Keychain-backed keys, remote wipe, and an audit log that survives conflict resolution. That is a compliance workstream, not a feature.

Number of platforms. iOS plus Android plus web means three background execution models and, if you share a sync core, likely a Rust or TypeScript core with bindings, which is the right call and adds setup cost.

When you should not build this

If your app is fundamentally a reader with light writes, a form submission, a like, a comment, do not build a sync engine. Build an offline read cache plus a durable mutation outbox with idempotency keys. TanStack Query with a persister, or Apollo Client with its cache persistence, gets you there in 1 to 2 weeks instead of 8. Ninety percent of apps that ask for offline-first need exactly this and nothing more. Be honest about which you are.

If you are pre-product-market-fit and your data model will change 4 more times, take a managed sync service: PowerSync, ElectricSQL, Ditto for peer-to-peer, or Supabase with a local SQLite mirror. You are buying a solved conflict engine and a battle-tested protocol for a monthly bill instead of a quarter of engineering. The cost is real lock-in and a ceiling on custom merge logic, and you should read the pricing page carefully because these products meter on synced operations or data volume and the bill scales with usage in a way a build does not. Take the managed service anyway. You can replace it in year two when you know what your data actually looks like, and you will have shipped.

Build custom when the merge logic is your competitive advantage or a regulatory requirement, when you have hard constraints a hosted service cannot meet such as on-premise or air-gapped deployment, or when you are past 50,000 devices and the metered bill has crossed the cost of a team. Those are the honest reasons. "We want control" is not one.

How to brief and vet a developer for this

Brief them with the answers, not the request. Tell them: which entities sync, how many rows a typical device holds, who can edit each entity concurrently, the longest realistic offline window, whether attachments are in scope, and what the business wants to happen when two people edit the same field. If you cannot answer the last one, that is the first meeting, not a detail for later.

Then ask these. The answers separate people who have shipped this from people who have read about it.

"What is your conflict resolution strategy per entity, and why is it different for each one?" A single global answer means they have not thought about it. Anyone who says "we use last-write-wins" without asking what the data is has already lost your data.

"Where do IDs come from?" If the answer is the server, ask how offline creation works, then watch them build the temp-ID rewrite story live.

"A write reaches the server, the server commits, the response is lost, the client retries. What happens?" If the word idempotency does not appear in 10 seconds, stop.

"How does a device learn that a record was deleted?" Tombstones and a retention window, or they have not shipped this.

"A device has been offline for 90 days. Walk me through the first sync." Correct answer involves detecting a cursor older than the tombstone window and forcing a full resync, and preserving the queued local writes through it.

"How do you test a merge conflict?" Deterministic replay and property-based convergence tests, or the honest answer that they test manually, which tells you what you need to know.

"What happens when the local schema is 6 versions behind and there are unsynced writes in the outbox?" This is the question that finds people who have actually maintained an offline app for two years rather than launched one.

"What does the user see when we cannot merge automatically?" There must be a UI answer. If conflicts are invisible in their design, conflicts are data loss in their build.

Ask them what they would use off the shelf. A senior engineer will tell you which parts of your requirement PowerSync or ElectricSQL already solves and argue you out of half the scope. Someone who wants to hand-roll all of it on a 10-week timeline is selling you hours.

Research & sources

The evidence behind this guide

Independent findings on why this investment pays off. Every link goes to the primary source.

  1. Standish's 2015 CHAOS research found roughly a third of software projects (about 36% by the Modern definition) fully succeed on time, on budget, and on scope, with top success drivers including executive support, user involvement, and clear requirements/business objectives. Source: Standish Group (CHAOS Report) (2015) →
  2. US mcommerce reached $280.4 billion in Jan - July 2024 (up 10.2% YoY), accounting for 49.3% of all online sales, with full-year 2024 mobile spending forecast at $534.88 billion. Source: EMARKETER (Insider Intelligence) (2024) →
  3. Only about 30% of digital transformations succeed at meeting their objectives, but getting six critical success factors in place (leadership commitment, talent, agile culture, progress monitoring, clear strategy, and a modernized platform) raises the odds of success from 30% to 80%. Source: Boston Consulting Group (BCG) (2020) →
  4. 48% of private companies cite integration with legacy systems or technical debt as a top obstacle to realizing the full value of their digital and AI investments (behind data quality/availability at 72% and gaps in AI fluency or technology talent/leadership at 53%). Source: Deloitte (2026) →
Rohan Malhotra · Enterprise Software Consultant

Rohan advises mid-market and enterprise teams on ERP, CRM and custom software, and has led delivery on dozens of business-software builds.

Writes for Digital Heroes, shipping business software for 2,000+ brands across 55+ countries since 2017.

FAQ

Frequently asked questions

How much does offline-first sync cost to add to an app?
Adding offline-first sync to an existing, healthy app typically runs $15,000 to $60,000 over 4 to 8 weeks in Digital Heroes delivery experience. Built as part of a first release it runs $50,000 to $130,000 over 10 to 16 weeks, and a full platform where offline is the core guarantee runs $150,000 to $350,000 over 6 to 12 months. The single biggest driver is whether multiple users can edit the same record while offline, which roughly doubles the sync engineering.
What does a $20,000 offline sync budget actually buy?
At the $20,000 level you get a real offline-first build for a narrow scope: roughly 5 to 8 entities with single-owner editing, client-generated UUIDv7 IDs, an idempotent mutation outbox with retry and backoff, delta sync with tombstones, and field-level last-write-wins. It does not buy CRDTs, attachment sync, or a conflict resolution UI. If your data has concurrent editors or photos, $20,000 buys a prototype that will lose data in production, so scope down the entities instead of the engineering.
Why does an offline sync project quoted at $40,000 come back at $90,000?
Almost always one of four things: attachments were assumed out of scope and then added, which is $10,000 to $25,000 on its own for a resumable chunked upload queue and eviction policy. Or the existing backend uses server-generated sequential IDs with no idempotency, which adds 3 to 5 weeks before any mobile work starts. Or the business discovered mid-build that two roles edit the same record, forcing CRDTs. Or the data is regulated and now needs SQLCipher encryption at rest plus an audit trail.
How long does it take to build offline-first sync?
Four to 8 weeks to add it to an existing app with a clean data model, 10 to 16 weeks when it is part of a first release. Roughly 30 to 40 percent of that time is testing, because offline bugs do not reproduce on a desk with fast wifi and need deterministic replay plus property-based convergence tests. Anyone quoting 2 weeks is building a read cache, not offline-first.
Can you add offline-first sync to our existing app?
Yes, and the cost depends almost entirely on your backend. If your API already accepts client-generated IDs and supports idempotency keys, it is a 4 to 8 week retrofit. If your server assigns sequential IDs and has no idempotency layer, add 3 to 5 weeks to make the write path safe before any mobile sync code is written. A read-only cache retrofit is much cheaper at 1 to 2 weeks, and it is what most apps actually need.
What breaks at scale with offline sync?
Full-table pull on app open, which is fine at 500 rows and fatal at 50,000, so you need delta sync with a monotonic cursor rather than a plain updated_at timestamp. Tombstone retention breaks next: a device offline longer than your 30 to 90 day tombstone window must be forced into a full resync or it keeps deleted rows forever. Attachment storage fills devices, and territory reassignment leaks other users' data if you do not evict rows that left the sync scope.
Should we use PowerSync or ElectricSQL instead of building it?
Yes, if you are pre-product-market-fit or your data model is still moving. PowerSync and ElectricSQL sync Postgres to local SQLite and give you a solved conflict engine for a monthly bill instead of a quarter of engineering; Ditto is the pick when devices sync peer to peer. Note that MongoDB retired Atlas Device Sync on 30 September 2025, so ignore any tutorial that recommends Realm sync. Build custom only when merge logic is a regulatory requirement, when you need on-premise or air-gapped deployment, or when you are past roughly 50,000 devices and the metered bill exceeds the cost of a team.
What is the difference between an offline cache and offline-first?
An offline cache stores the last API response and shows it when the network drops, so reads work but writes fail. Offline-first makes the local database the source of truth: every write lands locally and returns instantly, and a background process reconciles with the server later. That inversion changes your ID strategy, your API contract and your conflict policy, which is why one takes 2 weeks and the other takes 10.
How do you stop offline sync from creating duplicate records?
Client-generated IDs plus idempotency keys. The client mints a UUIDv7 or ULID at creation time so the record has one identity everywhere, and every mutation carries a client-generated operation ID. The server stores processed operation IDs for at least 7 days and returns the original result on a repeat instead of executing again, which covers the classic case where the server commits but the response is lost and the client retries.
How long does it take to go from idea to a live app in the App Store?
Plan on 10 to 16 weeks for a focused first version on Digital Heroes timelines: about two weeks of design, eight to ten weeks of development and testing, then store submission. Apple usually reviews within 24 to 48 hours, and Google Play can take up to a week for a new developer account. The schedule slips when the feature list grows mid-build far more often than it slips because of the stores.
Should I sign a fixed-price contract or pay time and materials for my app?
Fixed price fits a tightly scoped version one with a frozen feature list; time and materials fits ongoing product work where priorities shift monthly. The catch with fixed price is that every change becomes a negotiation, and the quote carries a built-in risk premium. A common middle path is fixed-price discovery and design, then time and materials with a monthly cap for the build.
Who owns the source code when an agency builds my app?
You should own the source code outright, and the contract must say it plainly with an intellectual property assignment that transfers ownership on final payment. Watch for agreements that only license the code to you, keep it in the agency's repository, or register the Apple and Google developer accounts under the agency's name. Insist on code delivered into a repository you control from week one, not at final handover.
How many people should be working on my software project?
Three to five for a typical focused build: a project lead, one or two engineers, a designer, and part-time QA, which is the standard shape across 2,000+ Digital Heroes projects. Larger platforms justify 6 to 10, but a ten-person team on a small first version usually signals bill padding rather than horsepower. What predicts success is whether a senior engineer is writing your code daily, not the headcount on the proposal.
Will Apple reject my app if I build it with a no-code tool?
Apple can reject it, depending on the tool and how generic the result is. Review guidelines 4.2 and 4.3 reject apps with minimal functionality or apps generated from commercial templates that duplicate thousands of others, which catches thin website wrappers and unmodified template apps. Tools that compile to real native code, FlutterFlow being the main example, pass review routinely as long as the app itself does something substantive.
How long until a business app pays for itself?
Internal and operations apps pay back fastest, typically inside 12 to 24 months across Digital Heroes projects, because the savings are countable: hours of manual entry removed, errors avoided, jobs scheduled tighter. Consumer apps are slower and riskier because payback depends on acquisition costs you only partly control. Before building, write down the one number the app must move, bookings per week or support calls per day, and have the agency design around it.
Can I move my users and data off a no-code platform into a custom app?
Your data can move, but your users' passwords cannot. Platforms like Bubble let you export records through CSV files or their API, but password hashes never leave the platform, so a migration needs a password reset or email login flow for every existing user. Plan the export before you hit the platform's pricing or capacity ceilings, because migrating under pressure is how data gets lost.
Can custom software connect to the tools we already use, like QuickBooks, Stripe, and Google Workspace?
Yes, and connecting your existing tools is one of the main reasons to build custom: mainstream platforms like QuickBooks, Stripe, Shopify, and Google Workspace all publish documented APIs. Budget 1 to 3 weeks of work per integration depending on API quality and how much data flows in both directions. Ask any vendor whether they have integrated with your specific tools before, because quirks like QuickBooks' OAuth token handling and API rate limits get learned on someone's project, and it should not be yours.
What does it cost to run a mobile app every month after launch?
Budget three buckets: store fees (Apple charges $99 a year, Google Play a one-time $25), hosting and infrastructure, and per-use services like maps, SMS, or payment processing. Across Digital Heroes client projects, a small production app runs $150 to $500 a month all-in before any new feature work. The number scales with usage, so ask your agency for a cost projection at 1,000 users and at 50,000, not just at launch.
Keep reading
let's build

Build something worth launching.

A plan, a team, a timeline, within 24 hours. No decks, no discovery calls. Tell us what you're building and we'll come back with a real scope and a real number.

message us directly · we reply within one business day

mission briefing

Monthly dispatch

Playbooks, real build costs, and what we're shipping. One email a month. No fluff.

visit us

New York HQ

1140 Broadway, Suite 704 · New York, NY 10001

Get directions
Online now

Hey there 👋 How can we help you today?