Advanced

Payload Mapping

Transform Tether mutation and snapshot payloads between client and server using client mutation mappers, push mappers, and pull snapshot mappers.

Payload mapping lets you transform Laravel offline sync data at the boundary between client and server. Use it when client-local fields, server database fields, or public sync payloads need to differ without changing the core sync protocol. There are three distinct mappers:

MapperRuns onDirectionPurpose
mutationMapperClientOutbound pushTransform a mutation's payload before it is sent to the server
pushMutationMapperServerInbound pushTransform a mutation's payload before it is applied to the database
pullSnapshotMapperServerOutbound pullTransform model attributes before they are sent to the client

Client-side mutation mapper

Registered via ClientSyncRegistry. Transforms the payload of an outbound mutation before it leaves the client.

Use when:

  • Adding client-side metadata (e.g. app version, device fingerprint)
  • Converting client-local field names to server-expected names
  • Filtering fields that should never be sent to the server
use Tether\Client\ClientSyncRegistry;
use Tether\Core\Mutation\Mutation;

public function boot(): void
{
    app(ClientSyncRegistry::class)->register(
        modelClass: Task::class,
        mutationMapper: function (Mutation $mutation): Mutation {
            return $mutation->withPayload(array_merge($mutation->getPayload(), [
                'client_app_version' => config('app.version'),
            ]));
        },
    );
}

Callback signature:

function (Mutation $mutation): Mutation

Server-side push mutation mapper

Registered via SyncRegistry or as tetherPushMutationMapper on the model. Transforms a mutation's payload after it arrives on the server but before it is applied to the database or checked for conflicts.

Use when:

  • Stamping the authenticated user ID onto every mutation (prevents clients writing records for other users)
  • Renaming or converting fields between client and server schema
  • Adding server-computed values the client doesn't know

Via SyncRegistry

use Tether\Server\SyncRegistry;
use Tether\Core\Mutation\Mutation;

public function boot(): void
{
    app(SyncRegistry::class)->register(
        modelClass: Task::class,
        pushMutationMapper: fn (Mutation $mutation, Request $request) => $mutation->withPayload(array_merge(
            $mutation->getPayload(),
            ['user_id' => $request->user()->id],
        )),
    );
}

Via model method (Syncable)

use Tether\Server\Traits\Syncable;
use Illuminate\Http\Request;

class Task extends Model
{
    use Syncable;

    public static function tetherPushMutationMapper(
        Mutation $mutation,
        Request $request,
    ): Mutation {
        return $mutation->withPayload(array_merge($mutation->getPayload(), ['user_id' => $request->user()->id]));
    }
}

Callback signature:

function (Mutation $mutation, Request $request): Mutation

Server-side pull snapshot mapper

Registered via SyncRegistry or as tetherPullSnapshotMapper on the model. Transforms a model's attributes before they are serialised into a pull snapshot and sent to the client.

Use when:

  • Stripping sensitive server-only fields before clients receive them
  • Adding computed or derived values the client needs
  • Renaming server field names to client-expected names

Via SyncRegistry

use Tether\Server\SyncRegistry;
use Tether\Core\Sync\Snapshot;
use Illuminate\Support\Arr;

public function boot(): void
{
    app(SyncRegistry::class)->register(
        modelClass: Task::class,
        pullSnapshotMapper: fn (Snapshot $snapshot, $row) => $snapshot->withPayload(Arr::except($snapshot->payload, ['internal_notes', 'admin_flag'])),
    );
}

Via model method (Syncable)

use Tether\Server\Traits\Syncable;
use Illuminate\Support\Arr;

class Task extends Model
{
    use Syncable;

    public static function tetherPullSnapshotMapper(Snapshot $snapshot, self $row): Snapshot
    {
        return $snapshot->withPayload(Arr::except($snapshot->payload, ['internal_notes', 'admin_flag']));
    }
}

Callback signature:

function (Snapshot $snapshot, Model $row): Snapshot