In the previous article, I explored how Serverpod 4 brings Flutter, backend code, a database, and coding agents into one development loop.
That improves how quickly a team can build software.
It does not answer a harder mobile question:
What happens when the network disappears and two devices change the same data?
Offline-first is often reduced to “store everything in SQLite.” That is local caching. It is not synchronization.
Serverpod’s new serverpod_offline_sync package is an ambitious step in the right direction. It adds a delta-CRDT synchronization layer that runs equivalent logic against client SQLite and server PostgreSQL databases. It is also explicitly still under development and not ready for production use, so it should be evaluated as a technical preview rather than assumed infrastructure. The package documentation is unusually candid about both the model and its constraints.
This is where the real engineering work begins.
Offline-first is a synchronization problem
Many applications can read data offline. The difficulty starts when multiple replicas modify it.
Phone A
Customer status = "Paid"
Phone B
Customer status = "Cancelled"
Server
Customer status = ?
Both devices may have been offline. Both users may reconnect later. There is no SQLite API that can decide which value is correct.
That is not a storage problem. It is a distributed-systems problem involving ordering, ownership, conflict resolution, deletes, relationships, and authorization.
Three generations of offline applications
Most mobile products sit somewhere along this spectrum.
1. Cache-first
Server
↓
HTTP API
↓
SQLite cache
The local database is an optimization. A user cannot make meaningful changes without a network. News readers often work this way: simple and reliable, but not offline-first.
2. Queue-based offline
SQLite
↓
Upload queue
↓
Server
The app records local actions and replays INSERT, UPDATE, and DELETE requests after reconnecting. This is a valuable improvement, but a queue only retries operations. It does not decide whether the resulting data is correct.
3. A synchronization engine
SQLite
↕
Synchronization engine
↕
PostgreSQL
The application writes local data. The synchronization layer understands replication, conflict handling, row visibility, deletes, restores, and constraints. That is the direction Serverpod is pursuing.
What Serverpod’s CRDT layer changes
The package is designed to preserve Serverpod’s ordinary development model. A team works with generated models and normal CRUD calls; the synchronization layer tracks operations atomically and replicates them.
According to its documentation, the design includes:
- delta-CRDT replication with identical merge logic on client and server;
- one-shot and continuous synchronization;
- field-level ordering through hybrid logical clocks;
- row lifecycle controlled by a monotone tombstone model;
- foreign-key actions and unique constraints preserved after merges;
- conflicts that require a person surfaced as visible state instead of silently discarding data;
- scoped, read-only or read-write sharing.
That last point is important. CRDTs offer deterministic convergence. They do not know what the product should do.
If two salespeople rename the same customer to two different values, a deterministic merge can select a value. The correct business outcome may still be “ask a person.” Technology can settle a conflict without making it a good product decision.
Offline-first is really about ownership
Synchronization is dangerous when it is described as “replicate everything.” Serious products need to answer a different question:
Which data is this user allowed to receive and change?
Serverpod models this through scopes. A row belongs to one scope; a user has a personal scope and may have membership in shared scopes with read-only or read-write access. Scope management remains a domain responsibility on the server, where an application can enforce its own invitation, organization, and authorization rules.
This matters for teams, workspaces, private records, and multi-tenant applications. Synchronization without ownership is data leakage with better network behavior.
Constraints are where replication becomes difficult
The expensive synchronization bugs are usually not visible at the moment a user performs an action.
Device A: delete customer
Device B: edit customer
Device C: create invoice for customer
Every device may succeed locally. The hard problem is whether all replicas can later converge without violating referential integrity or silently losing meaningful work.
This is why Serverpod’s documented requirements matter as much as its happy path. Synced tables must exist on both ends, use a UUID primary key, and include a scope relationship. Unique indexes must include the scope; several relation shapes are intentionally limited; and foreign-key relations need to be deferrable. These are not incidental setup details. They are the cost of maintaining a deterministic model across disconnected replicas.
Serverpod and PowerSync make different trade-offs
PowerSync approaches the same product space with a backend-agnostic service that keeps a backend database synchronized with in-app SQLite. It leaves application-specific write handling in the existing backend API.
That is attractive for a heterogeneous organization that already has a server, authorization model, and integrations it wants to retain.
Serverpod’s tighter approach uses its own models, ORM, generated client, migrations, sessions, and scoped data model. For a Flutter team already committed to Serverpod, that can mean less integration work and a more coherent programming model.
Neither approach is universally better. They optimize different constraints:
| Need | Likely direction |
|---|---|
| Existing, mixed-language backend | Backend-agnostic synchronization layer |
| New, Dart-first Serverpod product | Integrated Serverpod synchronization |
| Business-specific merge decisions | Explicit domain workflow, regardless of engine |
| High-risk or regulated data | A pilot with conservative scope and rigorous review |
Agentic development makes sync mistakes more consequential
Serverpod 4 lets an agent update models, create migrations, modify Flutter code, reload the server, and inspect results. Add synchronization to that workflow and an agent may also alter synchronized tables, scopes, or replicated fields.
The dangerous failures are no longer only syntax errors and visible exceptions. They can be distributed data errors that appear after a delayed reconnection—or only when two people edit the same record in different places.
The safest model remains supervised autonomy:
Agent proposes model and migration changes
↓
Tests run against disposable replicas
↓
Human reviews ownership, conflicts, and data impact
↓
Controlled CI promotes approved changes
Offline-first needs a different test discipline
Unit, widget, and integration tests remain necessary. They are not sufficient for synchronization.
A useful scenario test looks more like this:
Phone A goes offline
↓
It modifies an order
↓
Phone B goes offline
↓
It deletes the same order
↓
Both reconnect
↓
Verify the final state, permissions, and user-visible conflict
Then vary the conditions: duplicate operations, delayed packets, partial sync, clock drift, an interrupted migration, relationship changes, and an authorization change during the session.
The goal is not merely to prove that data transfers. It is to prove that the system converges while preserving the product’s invariants.
The real work starts after adding SQLite
Offline viewing, offline editing, offline collaboration, offline payments, offline inventory, and offline authorization are different problems. Each needs a deliberate answer for source of truth, conflicts, deletes, replay, duplication, and ownership.
Serverpod’s synchronization effort is promising because it tries to move replication mechanics out of everyday application code while retaining relational guarantees. If it matures, Flutter teams could spend much less time rebuilding infrastructure that few products should implement from scratch.
But “the engine handles sync” should never become “the product no longer needs distributed-systems thinking.”
SQLite solves local storage. The difficult question is whether independent copies of reality can converge into a trusted system without violating what the product promises.
