Introduction
Tether is an offline-first Laravel sync system for applications that need local database writes, reliable offline behaviour, and server reconciliation when connectivity returns. It lets you build apps that write locally first and synchronise with a central Laravel server without blocking the user or requiring a persistent connection.
How it works
Tether records every create, update, and delete on the client as an immutable mutation log entry. When the client syncs, it sends those entries to the server (push), then fetches state snapshots changed since its last cursor (pull). The server reconciles conflicts and maintains the authoritative state.
[Client local DB]
-> records every write
[Client mutation log]
-> push sync (client-initiated)
[Server sync endpoint]
-> applies mutations, stores authoritative state
[Server model tables]
-> state snapshot pull response
[Client reconciliation]
There is no server-to-client push, no persistent socket, and no polling. All synchronisation is strictly client-initiated.
Core concepts
Offline-first writes
All writes are persisted locally before any network activity. The client never blocks on network availability.
Mutation log
Every model change (create / update / delete) is captured as an immutable log entry with a unique ID, entity ID, operation type, payload, version number, and timestamp. These entries are the source of truth for push sync.
Client-generated identity (ULID)
Every syncable entity gets a client-generated ULID stored in a tether_id column by default. The server never generates or reassigns IDs. This means records can be created offline and will safely sync later without identity remapping.
Sync cursor
The server uses an integer microsecond cursor derived from syncable models' updated_at / deleted_at values. Clients store the last seen cursor value and send it on each pull to receive only state snapshots changed since their last sync.
Conflict resolution
When two clients modify the same record before syncing, the server detects the conflict. By default, last-write-wins compares the mutation timestamp with the server record's updated_at. Custom per-model resolvers can be registered for fine-grained control.
Packages
| Package | Install on | Purpose |
|---|---|---|
tether/core | (transitive dep) | Shared primitives: contracts, enums, mutation value objects, applicator |
tether/server | Laravel server | Sync HTTP endpoint, server-side mutation application, pull snapshot generation |
tether/client | Laravel client | Local mutation logging, push/pull sync engine, Syncable model trait |
tether/nativephp-client | NativePHP app | Lifecycle and connectivity hooks for offline sync on iOS and Android |
tether/pro-server | Laravel server | Observability dashboard, conflict management UI, replay tools |
tether/core is installed automatically as a dependency of the client and server packages. You do not install it directly.
tether/nativephp-client and tether/pro-server are commercial packages - see Add-ons for details.
Requirements
- PHP 8.3+
- Laravel 12 or 13
What Tether is not
- Not real-time. Sync happens when the client initiates it. There is no push from the server.
- Not a replication system. Tether reconciles state via mutation logs and state snapshots, not binary replication.
- Not opinionated about your UI stack. The client and server packages are pure PHP - they work with any frontend.