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:
| Capability | SDK / API |
|---|---|
| Offline sync | client.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 job | client.mobile.startWorkOrder(params). Transition to in_progress and create a check-in; optional GPS. Returns check-in id. |
| Stop job | client.mobile.stopWorkOrder(params). Optionally log time, add note, complete work order with cause/resolution, and set completed-at GPS. |
| Add note | client.mobile.addNote(params). Add a note to a work order; optional GPS. Returns note id. |
| Register attachment | client.mobile.registerWorkOrderAttachment(params). Register an existing file (e.g. after resumable upload) as a work order attachment. Returns attachment id. |
| Mobile views | client.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.
| Entity | Storage path convention (storage.foldername = 3 segments) | View |
|---|---|---|
| Work order | tenant_id/work_order/work_order_id/filename | v_work_order_attachments |
| Asset | tenant_id/asset/asset_id/filename | v_asset_attachments |
| Location | tenant_id/location/location_id/filename | v_location_attachments |
| Other | tenant_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_metreswhen the technician checked in. - Time entries (
app.work_order_time_entries):latitude,longitude,accuracy_metreswhen the entry was logged. Useclient.workOrders.logTime({ ...params, latitude, longitude, accuracyMetres }). - Work orders:
completed_at_latitude,completed_at_longitudewhen the work order was completed (set byclient.mobile.stopWorkOrderwhen completing with GPS).
All GPS columns are nullable. Use numeric for lat/long in the database; the SDK accepts number for params.