Mobile field

Offline sync, start/stop job, add note, register attachment, and lightweight mobile views with optional GPS. Set tenant context before use.

Overview

The database and SDK support mobile-first field workflows for technicians:

CapabilitySDK / API
Offline syncclient.mobile.sync(params). Single JSON payload of work orders, assets, locations, time entries, attachments, check-ins, notes. Use updatedAfter for incremental sync; optional technicianId to filter work orders.
Start jobclient.mobile.startWorkOrder(params). Transition to in_progress and create a check-in; optional GPS. Returns check-in id.
Stop jobclient.mobile.stopWorkOrder(params). Optionally log time, add note, complete work order with cause/resolution, and set completed-at GPS.
Add noteclient.mobile.addNote(params). Add a note to a work order; optional GPS. Returns note id.
Register attachmentclient.mobile.registerWorkOrderAttachment(params). Register an existing file (e.g. after resumable upload) as a work order attachment. Returns attachment id.
Mobile viewsclient.mobile.listMobileWorkOrders(), listMobileAssets(), listMobileLocations(), etc. Minimal columns for offline use.

Attachments (photos, documents, signatures, scans) are supported for work orders, assets, and locations. Work order attachments can be created by uploading to Storage (trigger creates the link) or by registering an existing file with registerWorkOrderAttachment. Asset and location attachments use Storage paths tenant_id/asset/asset_id/filename and tenant_id/location/location_id/filename.

Offline sync

Fetch a single JSON payload for mobile offline sync. Returns work_orders, assets, locations, time_entries, attachments, check_ins, and notes. Use updatedAfter (ISO timestamp) for incremental sync; pass technicianId to restrict work orders to those assigned to that technician.

client.mobile.sync(params)

await client.setTenant(tenantId)

const payload = await client.mobile.sync({
  tenantId,
  updatedAfter: lastSyncAt ?? null,  // e.g. '2025-03-17T12:00:00Z'
  limit: 500,
  technicianId: currentTechnicianId ?? null,  // optional: only "my" work orders
})
// payload.work_orders, payload.assets, payload.locations, payload.time_entries,
// payload.attachments, payload.check_ins, payload.notes
  • Name
    tenantId
    Description
    Required. Tenant UUID.
  • Name
    updatedAfter
    Description
    Optional. ISO timestamp; only rows updated/created after this are returned. Omit for full sync.
  • Name
    limit
    Description
    Optional. Max rows per entity type (default 500, max 2000).
  • Name
    technicianId
    Description
    Optional. When set, work_orders are filtered to those assigned to this technician (assigned_to or work_order_assignments).

Start work order

Transition the work order to in_progress and create a check-in record. Optional GPS (latitude, longitude, accuracy_metres). Returns the check-in id. Requires the current user to be assigned to the work order or have workorder.edit.

client.mobile.startWorkOrder(params)

const checkInId = await client.mobile.startWorkOrder({
  tenantId,
  workOrderId,
  latitude: 52.52,
  longitude: 13.405,
  accuracyMetres: 10,
})
// Returns string (check-in UUID)

Stop work order

Optionally log time (with optional GPS), add a note, and/or complete the work order with cause and resolution. When completing, optional completed_at_latitude and completed_at_longitude are set on the work order. Requires assigned or workorder.edit.

client.mobile.stopWorkOrder(params)

await client.mobile.stopWorkOrder({
  tenantId,
  workOrderId,
  complete: true,
  minutes: 45,
  note: 'Replaced filter and verified airflow.',
  latitude: 52.52,
  longitude: 13.405,
  cause: 'Clogged filter',
  resolution: 'Replaced filter; cleaned duct.',
})
  • Name
    complete
    Description
    Optional. Default true. If true, transitions work order to completed.
  • Name
    minutes
    Description
    Optional. If set, logs a time entry with optional GPS.
  • Name
    note
    Description
    Optional. Adds a work order note (with optional GPS).
  • Name
    latitude, longitude, accuracyMetres
    Description
    Optional. GPS for time entry and/or note and completed-at.
  • Name
    cause, resolution
    Description
    Optional. Set when completing the work order.

Add note

Add a note to a work order. Optional GPS when the note was taken. Returns note id. Permission: assigned or workorder.edit.

client.mobile.addNote(params)

const noteId = await client.mobile.addNote({
  tenantId,
  workOrderId,
  body: 'Customer reported noise from unit; will inspect.',
  latitude: 52.52,
  longitude: 13.405,
})
// Returns string (note UUID)

Register attachment

When the client uploads a file to Storage first (e.g. resumable upload) and then needs to link it to a work order, use registerWorkOrderAttachment. The file must already exist in app.files (e.g. created by a custom flow or by uploading to a path that does not use the work-order trigger). Validates that the file and work order belong to the tenant. Returns attachment id.

client.mobile.registerWorkOrderAttachment(params)

const attachmentId = await client.mobile.registerWorkOrderAttachment({
  tenantId,
  workOrderId,
  fileId: 'uuid-from-app-files',
  label: 'Before photo',
  kind: 'photo',
})
// Returns string (attachment UUID)

For the standard flow (upload to Storage path tenant_id/work_order/{work_order_id}/filename), the trigger creates the file and attachment automatically; use client.workOrders.listAttachments(workOrderId) to get the new row. Use registerWorkOrderAttachment only when you have an existing file id (e.g. after a resumable upload that wrote to Storage and created app.files separately).

Mobile views

Lightweight views with minimal columns for mobile clients. Set tenant context, then query via the SDK list methods or directly from Supabase.

List mobile work orders

await client.setTenant(tenantId)

const workOrders = await client.mobile.listMobileWorkOrders()
// MobileWorkOrderRow[]: id, tenant_id, title, status, priority, assigned_to, location_id, asset_id, due_date, completed_at, updated_at

Other list methods: listMobileAssets(), listMobileLocations(), listMobileTimeEntries(), listMobileAttachments(), listMobileCheckIns(), listMobileNotes(). Use updated_at (or created_at for check_ins and notes) for incremental sync when not using client.mobile.sync().

Attachments for assets and locations

Attachments (photos, documents, signatures, scans) are supported for assets and locations as well as work orders.

EntityStorage path convention (storage.foldername = 3 segments)View
Work ordertenant_id/work_order/work_order_id/filenamev_work_order_attachments
Assettenant_id/asset/asset_id/filenamev_asset_attachments
Locationtenant_id/location/location_id/filenamev_location_attachments
Othertenant_id/<entity_type>/<entity_id>/filename (e.g. part, purchase_order, incident)v_entity_attachments

Upload to the attachments bucket with the appropriate path; a trigger creates app.files and app.entity_attachments. Use client.supabase.storage.from('attachments').createSignedUrl(...) with bucket_id and storage_path from the view rows. Kind values (e.g. photo, document, signature, scan) are free-form; document in your UI for consistency.

Raw queries:

// Asset attachments (after setTenant)
const { data: assetAttachments } = await client.supabase
  .from('v_asset_attachments')
  .select('*')
  .eq('asset_id', assetId)

// Location attachments
const { data: locationAttachments } = await client.supabase
  .from('v_location_attachments')
  .select('*')
  .eq('location_id', locationId)

GPS and check-ins

Optional GPS is stored on:

  • Check-ins (app.work_order_check_ins): latitude, longitude, accuracy_metres when the technician checked in.
  • Time entries (app.work_order_time_entries): latitude, longitude, accuracy_metres when the entry was logged. Use client.workOrders.logTime({ ...params, latitude, longitude, accuracyMetres }).
  • Work orders: completed_at_latitude, completed_at_longitude when the work order was completed (set by client.mobile.stopWorkOrder when completing with GPS).

All GPS columns are nullable. Use numeric for lat/long in the database; the SDK accepts number for params.

Was this page helpful?