First Class Modules
Work Orders
Tracks work requests through a secured lifecycle with number, customer, location, summary, priority, status, assignments, related records, and service evidence.
module.work-ordersUse Work Orders when an app needs accountable operational work that can be created, prioritized, assigned, moved through approved states, and connected to customers or other records.
The bundled renderer key is module.work-orders. First-class means BuildWithHQ supplies a native, typed, secured runtime experience inside normal app provisioning and the signed-in user's existing permissions.
What it does
Tracks work requests through a secured lifecycle with number, customer, location, summary, priority, status, assignments, related records, and service evidence.
Key capabilities
- Search and filter work orders by text and status.
- Create work with summary, description, priority, and optional customer context.
- Move work through the supported status lifecycle with optimistic concurrency.
- Assign team members, designate primary responsibility, and expose related-record counts.
- Let the tenant choose whether Work Order payment collection is disabled, optional, required, manual, at creation, or required before completion.
- Show secured payment history and queue idempotent charges against protected, consented payment methods without exposing card or provider secrets.
Common uses
- Field service and maintenance dispatch.
- Facilities, asset, repair, installation, or inspection follow-up.
- Internal service requests and operational task queues that need a stronger lifecycle than a simple task.
How it connects
Work Orders compose naturally with Contacts, Calendar, Files, Conversations, Notifications, Record Relations, Record Activity, and the shared tenant payment ledger. Charges remain linked payment records rather than columns or raw processor data copied into Work Orders.
Where applicable, its records use the universal RecordId conventions so they can participate in secured relationships, activity history, favorites, dynamic fields, notifications, Inbox attention, and global search without copying the source record.
Security and data boundary
The service limits rows by SaaS app, tenant, DataRole, and location and separately checks create, transition, assignment, and payment authority. The account owner manages payment policy and external connector references. Collection requires the existing CanBillRecords capability plus edit and record scope; customer and assignee identifiers are query/action targets, not proof of access.
- The authenticated service derives the SaaS app, tenant account, user, DataRole, and location scope; browser identifiers are never authorization proof.
- The page editor composes React components with validated data bindings. Those bindings call typed runtime APIs, whose application services execute reviewed stored procedures.
- List, search, detail, relation, activity, favorite, and write operations reapply their required server-side permissions.
Add it to an app
- Open the SaaS app in the Developer Console and identify the user journey and page where this module belongs.
- Add the validated
module.work-ordersmodule block through the supported page/template authoring flow. - Configure the module with the page editor's React components and validated data bindings; the bindings call authenticated platform APIs backed by reviewed stored procedures.
- Place the page in the correct user-type menus and assign existing DataRole, record, field, and location permissions.
- Test list, detail, search, empty, denied, stale-update, and cross-location behavior before publishing an exact version.
Copy/paste the Work Orders page
The native block is the complete starting point: secured search and status filters, bounded server pagination, create/detail views, status transitions, assignments, related-record counts, and the permission-aware payment panel. Paste this page document into Raw JSON mode:
{
"blocks": [
{
"_id": "operations-work-orders",
"_type": "module.work-orders",
"props": {},
"children": []
}
]
}
The native list asks SQL for only 100 authorized rows at a time. Its next-page control follows hasMore; it labels totalRecords as “at least” while totalIsExact is false rather than running a tenant-wide count for every page.
Custom unstyled React work queue
For custom presentation inside apps/tenant-runtime/src, retain the authenticated API helper and SDK parser and replace only the markup. Search, status, location, ordering, and pagination stay in the stored procedure; React receives the page it should display:
import { FormEvent, useEffect, useState } from "react";
import {
workOrderStatuses,
type WorkOrderDetail,
type WorkOrderList,
type WorkOrderStatus,
} from "@buildwithhq/module-sdk";
import { getWorkOrder, listWorkOrders } from "./api";
const emptyWorkOrders: WorkOrderList = {
contractVersion: 1,
pageNumber: 1,
pageSize: 50,
totalRecords: 0,
totalPages: 0,
totalIsExact: true,
hasMore: false,
items: [],
};
type UnstyledWorkOrderQueueProps = {
locationId?: string;
};
export function UnstyledWorkOrderQueue({ locationId = "" }: UnstyledWorkOrderQueueProps) {
const [draftSearch, setDraftSearch] = useState("");
const [search, setSearch] = useState("");
const [status, setStatus] = useState<WorkOrderStatus | "">("");
const [pageNumber, setPageNumber] = useState(1);
const [result, setResult] = useState<WorkOrderList>(emptyWorkOrders);
const [selected, setSelected] = useState<WorkOrderDetail | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
setError(null);
listWorkOrders(search, status, controller.signal, pageNumber, 50, locationId)
.then(setResult)
.catch((caught: unknown) => {
if (!controller.signal.aborted) {
setError(caught instanceof Error ? caught.message : "Work orders could not be loaded.");
}
})
.finally(() => {
if (!controller.signal.aborted) setLoading(false);
});
return () => controller.abort();
}, [locationId, pageNumber, search, status]);
function submitSearch(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
setPageNumber(1);
setSearch(draftSearch.trim());
}
async function openWorkOrder(recordId: string) {
setError(null);
try {
setSelected(await getWorkOrder(recordId));
} catch (caught) {
setError(caught instanceof Error ? caught.message : "The work order could not be loaded.");
}
}
const totalLabel = result.totalIsExact
? `${result.totalRecords} matching work orders`
: `At least ${result.totalRecords} matching work orders`;
return (
<section className="custom-work-orders" aria-labelledby="work-orders-title" aria-busy={loading}>
<header>
<h1 id="work-orders-title">Work orders</h1>
<p>{totalLabel}</p>
</header>
<form role="search" onSubmit={submitSearch}>
<label>
Search number or summary
<input value={draftSearch} onChange={(event) => setDraftSearch(event.target.value)} />
</label>
<label>
Status
<select value={status} onChange={(event) => { setPageNumber(1); setStatus(event.target.value as WorkOrderStatus | ""); }}>
<option value="">All statuses</option>
{workOrderStatuses.map((value) => <option key={value}>{value}</option>)}
</select>
</label>
<button type="submit" disabled={loading}>Search</button>
</form>
{error && <p role="alert">{error}</p>}
{!loading && result.items.length === 0 && <p>No visible work orders match these filters.</p>}
<ul aria-label="Work order results">
{result.items.map((workOrder) => (
<li key={workOrder.recordId}>
<button type="button" onClick={() => void openWorkOrder(workOrder.recordId)}>
<strong>{workOrder.workOrderNumber} · {workOrder.summary}</strong>
<span>{workOrder.status} · {workOrder.priority}</span>
<span>{workOrder.customerName || "No customer"} · {workOrder.locationName || "All locations"}</span>
</button>
</li>
))}
</ul>
<nav aria-label="Work order pages">
<button type="button" disabled={loading || result.pageNumber <= 1} onClick={() => setPageNumber((page) => page - 1)}>Previous</button>
<span>Page {result.pageNumber}</span>
<button type="button" disabled={loading || !result.hasMore} onClick={() => setPageNumber((page) => page + 1)}>Next</button>
</nav>
{selected && (
<article aria-label="Selected work order">
<h2>{selected.workOrderNumber} · {selected.summary}</h2>
<p>{selected.description || "No description"}</p>
<dl>
<div><dt>Status</dt><dd>{selected.status}</dd></div>
<div><dt>Priority</dt><dd>{selected.priority}</dd></div>
<div><dt>Assignments</dt><dd>{selected.assignmentCount}</dd></div>
<div><dt>Related records</dt><dd>{selected.relationCount}</dd></div>
</dl>
<ul aria-label="Assigned team">
{selected.assignments.map((assignment) => (
<li key={assignment.workOrderAssignmentId}>
{assignment.assignedUserName || assignment.assignedUserId} · {assignment.assignmentRole}
{assignment.isPrimary ? " · Primary" : ""}
</li>
))}
</ul>
</article>
)}
</section>
);
}
Replacement-safe work and lifecycle helpers
Create can use a server-generated number. Update replaces the editable work-order fields, so rebuild it from the latest secured detail and include its updatedUtc. Status and assignment are separate commands, and each command advances that timestamp. Re-read the detail after every mutation before issuing another command.
import type {
WorkOrderAssignmentRole,
WorkOrderDetail,
WorkOrderStatus,
WorkOrderWrite,
} from "@buildwithhq/module-sdk";
import {
assignWorkOrder,
createWorkOrder,
setWorkOrderStatus,
updateWorkOrder,
} from "./api";
export const workOrderReplacement = (current: WorkOrderDetail): WorkOrderWrite => ({
expectedUpdatedUtc: current.updatedUtc,
locationId: current.locationId ?? null,
customerRecordId: current.customerRecordId ?? null,
summary: current.summary,
description: current.description ?? "",
priority: current.priority,
scheduledStartUtc: current.scheduledStartUtc ?? null,
scheduledEndUtc: current.scheduledEndUtc ?? null,
});
export async function createRepairWork(locationId?: string, customerRecordId?: string) {
return createWorkOrder({
locationId: locationId ?? null,
customerRecordId: customerRecordId ?? null,
summary: "Inspect and repair rooftop unit",
description: "Confirm fault, record readings, repair, and attach closeout evidence.",
priority: "High",
});
}
export async function saveWorkOrder(
current: WorkOrderDetail,
changes: Partial<Omit<WorkOrderWrite, "expectedUpdatedUtc" | "workOrderNumber">>,
) {
const request: WorkOrderWrite = {
...workOrderReplacement(current),
...changes,
expectedUpdatedUtc: current.updatedUtc,
};
return updateWorkOrder(current.recordId, request);
}
export async function moveWorkOrder(current: WorkOrderDetail, status: WorkOrderStatus) {
return setWorkOrderStatus(current.recordId, {
expectedUpdatedUtc: current.updatedUtc,
status,
});
}
export async function setWorkOrderAssignee(
current: WorkOrderDetail,
assignedUserId: string,
assignmentRole: WorkOrderAssignmentRole,
isPrimary = false,
isActive = true,
) {
return assignWorkOrder(current.recordId, {
expectedUpdatedUtc: current.updatedUtc,
assignedUserId,
assignmentRole,
isPrimary,
isActive,
});
}
Choose customerRecordId from secured Contacts results and assignedUserId from a server-scoped active-user picker. They are action targets, not proof of access: SQL independently checks customer readability, assignee activity, and work-order location compatibility. The server also enforces the allowed transition graph; do not duplicate it as authorization logic in React.
Reuse the native payment panel
When a custom Work Order detail needs collection, compose the existing panel rather than handling cards or processor secrets. It shows only controls allowed by the current payment policy and user capability:
Amounts in this payment API are currently named and represented in cents. Configure only currencies whose charge amounts use two decimal places; the server rejects JPY, KRW, CLP and other zero-decimal currencies with tenant_payments_unsupported_currency. A queued legacy amount in one of those currencies is failed before the worker contacts Stripe.
import type { WorkOrderDetail } from "@buildwithhq/module-sdk";
import { TenantPaymentsPanel } from "./TenantPaymentsPanel";
type WorkOrderPaymentsProps = {
workOrder: WorkOrderDetail;
reload: () => void;
};
export function WorkOrderPayments({ workOrder, reload }: WorkOrderPaymentsProps) {
return (
<TenantPaymentsPanel
moduleKey="WorkOrders"
recordId={workOrder.recordId}
customerRecordId={workOrder.customerRecordId}
onChanged={reload}
/>
);
}
The panel submits only a protected payment-method identity, integer amount/currency, and a fresh idempotency key. Card numbers, security codes, raw provider tokens, API secrets, and webhook secrets do not enter the browser request. If the tenant policy requires successful payment before completion, an unpaid or still-open charge makes the Completed transition return 409.
Optional styling
.custom-work-orders { width: 100%; max-width: none; }
.custom-work-orders form { display: flex; gap: .75rem; align-items: end; flex-wrap: wrap; }
.custom-work-orders form label { min-width: 12rem; flex: 1; }
.custom-work-orders input,
.custom-work-orders select { box-sizing: border-box; width: 100%; }
.custom-work-orders ul { list-style: none; margin: 1rem 0; padding: 0; }
.custom-work-orders li + li { border-top: 1px solid var(--line, #d8dee8); }
.custom-work-orders li > button {
background: transparent;
border: 0;
display: grid;
gap: .25rem;
padding: .85rem 0;
text-align: left;
width: 100%;
}
.custom-work-orders nav { align-items: center; display: flex; gap: 1rem; }
.custom-work-orders article {
background: var(--surface, #fff);
border: 1px solid var(--line, #d8dee8);
border-radius: .75rem;
margin-top: 1rem;
padding: 1rem;
}
Exact lifecycle and data path
| Purpose | Method and route | Contract behavior |
|---|---|---|
| List/search/filter | GET /api/modules/work-orders?search=&status=&locationId=&pageNumber=1&pageSize=100 | Stable updated-time ordering; 1–200 rows; hasMore plus an exact/lower-bound total |
| Detail | GET /api/modules/work-orders/{recordId} | Work fields, customer/location labels, active assignments, and relation count |
| Create | POST /api/modules/work-orders | Starts at Requested; server can generate the work-order number |
| Replace/update | PUT /api/modules/work-orders/{recordId} | Full editable shape plus exact expectedUpdatedUtc |
| Transition | PUT /api/modules/work-orders/{recordId}/status | Server-enforced transition graph, edit permission, concurrency, and payment gate |
| Assign/deactivate | PUT /api/modules/work-orders/{recordId}/assignment | Technician, Lead, Contractor, or Dispatcher; active/location-compatible tenant user |
| Payment settings | GET /api/modules/payments/settings | Returns only allowed management/collection capabilities and safe connector references |
| Payment policy | PUT /api/modules/payments/settings/WorkOrders | Account-owner policy for disabled/optional/required collection and timing |
| Save connector references | PUT /api/modules/payments/connectors | Protected secret-store references only; no processor secret value |
| Deactivate connector | DELETE /api/modules/payments/connectors/{connectorId} | Account-owner operation; existing payment evidence remains |
| Protected methods | GET /api/modules/payments/methods?customerRecordId={customerRecordId} | Active, consented, display-safe methods only |
| Payment history | GET /api/modules/payments/records/{recordId} | Secured totals, payments, and charge-intent states |
| Queue charge | POST /api/modules/payments/records/{recordId}/charges | 202 Accepted; protected method reference and idempotency key |
The Work Orders application service executes sp_WorkOrders_ListSecured, sp_WorkOrders_GetSecured, sp_WorkOrders_CreateSecured, sp_WorkOrders_UpdateSecured, sp_WorkOrders_SetStatusSecured, and sp_WorkOrders_AssignSecured. Identity and routing stay server-owned. SQL selects only the authorized page before the React component renders it.
Validation failures return 400, permission failures return 403, absent or inaccessible detail targets share the same 404 work_order_not_found response, and stale versions, invalid transitions, or a required-payment completion fence return 409.
See the complete React component catalog for all generated page blocks and operations, or the headless application guide when the React application lives outside the BuildWithHQ tenant shell.
Build a professional Work Orders dashboard
These six registry-backed presentation blocks let a designer turn the secured Work Orders API into a complete admin page without writing a chart, grid, status badge, empty state, or timeline from scratch. The normal operational API begins at /api/modules/work-orders; a dashboard-wide count or trend should come from a separate purpose-built presentation binding so the browser never downloads a broad record population to calculate one number.
The ZIP contains this feature's fictional design composition at pages/first-class/work-orders-sample.json, its executable authenticated page at pages/first-class/work-orders-live.json, and the catalog-driven FirstClassPresentationGallery.tsx. Use the sample only for visual design. The live page calls the real native API and reviewed stored procedures under the signed-in user's scope.
| Payload kind | Runtime block | Useful Work Orders projection |
|---|---|---|
metric-set | core.metric-strip | Open, due, overdue, completed, and priority counts |
entity-list | core.entity-list | Recent authorized work orders and their next action |
progress-list | core.progress-list | Distribution by status, priority, owner, or location |
series-chart | core.series-chart | Created, started, completed, and closed work over time |
data-grid | core.presentation-grid | One bounded page with ownership, state, and due dates |
timeline | core.timeline | Create, assign, dispatch, update, evidence, signoff, and completion events |
Copy/paste design preview: all six blocks
This complete static page document renders immediately in Puck/Monaco and is useful while styling a template. Its names, counts, dates, and IDs are fictional design fixtures; static preview values are not live tenant facts.
Copy the complete six-block page JSON
{
"blocks": [
{
"_id": "work-orders-metrics",
"_type": "core.metric-strip",
"props": {
"title": "Work order command center",
"asOfUtc": "2026-09-04T18:00:00Z",
"items": [
{
"key": "open",
"label": "Open work orders",
"value": 126,
"format": "number",
"tone": "primary"
},
{
"key": "due",
"label": "Due today",
"value": 18,
"format": "number",
"tone": "warning"
},
{
"key": "overdue",
"label": "Overdue",
"value": 7,
"format": "number",
"tone": "danger"
},
{
"key": "completed",
"label": "Completed this week",
"value": 84,
"format": "number",
"tone": "success"
}
]
},
"children": []
},
{
"_id": "work-orders-recent",
"_type": "core.entity-list",
"props": {
"title": "Recent work orders",
"hasMore": true,
"items": [
{
"id": "work-orders-sample-1",
"recordId": "work-orders-record-1",
"primary": "WO-1048 - North Wing closeout",
"secondary": "Acme Field Services - Reno",
"status": {
"key": "ready-for-signoff",
"label": "Ready for signoff",
"tone": "warning"
},
"trailing": "Due today"
},
{
"id": "work-orders-sample-2",
"recordId": "work-orders-record-2",
"primary": "WO-1048 - North Wing closeout - Follow-up",
"secondary": "Acme Field Services - Reno - Updated two hours ago by the assigned owner",
"status": {
"key": "in-review",
"label": "In review",
"tone": "primary"
},
"trailing": "Today"
},
{
"id": "work-orders-sample-3",
"recordId": "work-orders-record-3",
"primary": "WO-1048 - North Wing closeout - West region",
"secondary": "Acme Field Services - Reno - Related to three visible records at the Reno location",
"status": {
"key": "on-track",
"label": "On track",
"tone": "success"
},
"trailing": "3 related"
},
{
"id": "work-orders-sample-4",
"recordId": "work-orders-record-4",
"primary": "WO-1048 - North Wing closeout - Customer response",
"secondary": "Acme Field Services - Reno - Waiting for an external response before work can continue",
"status": {
"key": "scheduled",
"label": "Scheduled",
"tone": "warning"
},
"trailing": "Tomorrow"
},
{
"id": "work-orders-sample-5",
"recordId": "work-orders-record-5",
"primary": "WO-1048 - North Wing closeout - Regional operations review with a deliberately long title",
"secondary": "Acme Field Services - Reno - This deliberately longer supporting line verifies wrapping, truncation, responsive spacing, and dense dashboard behavior.",
"status": {
"key": "needs-attention",
"label": "Needs attention",
"tone": "danger"
},
"trailing": "Review",
"tertiary": "Long-content fixture: verify keyboard focus, wrapping, narrow columns, and mobile overflow before publishing."
},
{
"id": "work-orders-sample-6",
"recordId": "work-orders-record-6",
"primary": "WO-1048 - North Wing closeout - Completed preview",
"secondary": "Acme Field Services - Reno - Closed after review with its related evidence retained",
"status": {
"key": "complete",
"label": "Complete",
"tone": "success"
},
"trailing": "Closed"
}
]
},
"children": []
},
{
"_id": "work-orders-bystatus",
"_type": "core.progress-list",
"props": {
"title": "Work orders by status",
"items": [
{
"key": "new",
"label": "New",
"value": 21,
"maximum": 126,
"displayValue": "21",
"tone": "neutral",
"status": {
"key": "new",
"label": "New",
"tone": "neutral"
}
},
{
"key": "active",
"label": "In progress",
"value": 68,
"maximum": 126,
"displayValue": "68",
"tone": "primary",
"status": {
"key": "active",
"label": "In progress",
"tone": "primary"
}
},
{
"key": "ready",
"label": "Ready for signoff",
"value": 37,
"maximum": 126,
"displayValue": "37",
"tone": "success",
"status": {
"key": "ready",
"label": "Ready for signoff",
"tone": "success"
}
}
]
},
"children": []
},
{
"_id": "work-orders-trend",
"_type": "core.series-chart",
"props": {
"title": "Work order throughput",
"variant": "bar",
"defaultPeriodKey": "d7",
"periods": [
{
"key": "d7",
"label": "7 days",
"labels": [
"Fri",
"Sat",
"Sun",
"Mon",
"Tue",
"Wed",
"Thu"
],
"series": [
{
"key": "primary",
"label": "Created",
"tone": "primary",
"values": [
8,
5,
4,
12,
15,
11,
17
]
},
{
"key": "secondary",
"label": "Completed",
"tone": "success",
"values": [
6,
4,
3,
9,
12,
10,
14
]
}
]
}
]
},
"children": []
},
{
"_id": "work-orders-table",
"_type": "core.presentation-grid",
"props": {
"title": "Visible work orders",
"columns": [
{
"key": "number",
"label": "Work order",
"type": "text",
"align": "left"
},
{
"key": "summary",
"label": "Summary",
"type": "text",
"align": "left"
},
{
"key": "assignee",
"label": "Assignee",
"type": "text",
"align": "left"
},
{
"key": "status",
"label": "Status",
"type": "status",
"align": "left"
},
{
"key": "due",
"label": "Due",
"type": "date",
"align": "left"
}
],
"rows": [
{
"id": "work-orders-row-1",
"recordId": "work-orders-record-1",
"cells": {
"number": "WO-1048",
"summary": "North Wing closeout",
"assignee": "Jordan Lee",
"status": {
"key": "ready",
"label": "Ready for signoff",
"tone": "warning"
},
"due": "2026-09-04T23:00:00Z"
}
},
{
"id": "work-orders-row-2",
"recordId": "work-orders-record-2",
"cells": {
"number": "WO-1048 - Follow-up",
"summary": "North Wing closeout",
"assignee": "Jordan Lee",
"status": {
"key": "in-review",
"label": "In review",
"tone": "primary"
},
"due": "2026-09-04T15:42:00Z"
}
},
{
"id": "work-orders-row-3",
"recordId": "work-orders-record-3",
"cells": {
"number": "WO-1048 - West region",
"summary": "North Wing closeout",
"assignee": "Jordan Lee",
"status": {
"key": "on-track",
"label": "On track",
"tone": "success"
},
"due": "2026-09-04T12:18:00Z"
}
},
{
"id": "work-orders-row-4",
"recordId": "work-orders-record-4",
"cells": {
"number": "WO-1048 - Customer response",
"summary": "North Wing closeout",
"assignee": "Jordan Lee",
"status": {
"key": "scheduled",
"label": "Scheduled",
"tone": "warning"
},
"due": "2026-09-03T21:07:00Z"
}
},
{
"id": "work-orders-row-5",
"recordId": "work-orders-record-5",
"cells": {
"number": "WO-1048 - Regional operations review with a deliberately long title",
"summary": "North Wing closeout - This deliberately longer supporting line verifies wrapping, truncation, responsive spacing, and dense dashboard behavior.",
"assignee": "Jordan Lee",
"status": {
"key": "needs-attention",
"label": "Needs attention",
"tone": "danger"
},
"due": "2026-09-03T16:31:00Z"
}
},
{
"id": "work-orders-row-6",
"recordId": "work-orders-record-6",
"cells": {
"number": "WO-1048 - Completed preview",
"summary": "North Wing closeout",
"assignee": "Jordan Lee",
"status": {
"key": "complete",
"label": "Complete",
"tone": "success"
},
"due": "2026-09-02T19:14:00Z"
}
}
],
"page": {
"pageNumber": 1,
"pageSize": 6,
"totalRecords": 126,
"totalIsExact": true,
"hasMore": true
}
},
"children": []
},
{
"_id": "work-orders-timeline",
"_type": "core.timeline",
"props": {
"title": "Work order activity",
"hasMore": true,
"items": [
{
"id": "work-orders-event-1",
"recordId": "work-orders-record-1",
"occurredUtc": "2026-09-04T17:58:00Z",
"title": "Work order moved to signoff",
"description": "WO-1048 completed its field checklist and is ready for review.",
"actor": "Jordan Lee",
"tone": "success"
},
{
"id": "work-orders-event-2",
"recordId": "work-orders-record-2",
"occurredUtc": "2026-09-04T15:42:00Z",
"title": "Work order moved to signoff - Follow-up",
"description": "WO-1048 completed its field checklist and is ready for review. Updated two hours ago by the assigned owner.",
"actor": "Avery Patel",
"tone": "primary"
},
{
"id": "work-orders-event-3",
"recordId": "work-orders-record-3",
"occurredUtc": "2026-09-04T12:18:00Z",
"title": "Work order moved to signoff - West region",
"description": "WO-1048 completed its field checklist and is ready for review. Related to three visible records at the Reno location.",
"actor": "Sam Rivera",
"tone": "success"
},
{
"id": "work-orders-event-4",
"recordId": "work-orders-record-4",
"occurredUtc": "2026-09-03T21:07:00Z",
"title": "Work order moved to signoff - Customer response",
"description": "WO-1048 completed its field checklist and is ready for review. Waiting for an external response before work can continue.",
"actor": "Maya Chen",
"tone": "warning"
},
{
"id": "work-orders-event-5",
"recordId": "work-orders-record-5",
"occurredUtc": "2026-09-03T16:31:00Z",
"title": "Work order moved to signoff - Regional operations review with a deliberately long title",
"description": "WO-1048 completed its field checklist and is ready for review. This deliberately longer supporting line verifies wrapping, truncation, responsive spacing, and dense dashboard behavior.",
"actor": "Automation",
"tone": "danger"
},
{
"id": "work-orders-event-6",
"recordId": "work-orders-record-6",
"occurredUtc": "2026-09-02T19:14:00Z",
"title": "Work order moved to signoff - Completed preview",
"description": "WO-1048 completed its field checklist and is ready for review. Closed after review with its related evidence retained.",
"actor": "Jordan Lee",
"tone": "success"
}
]
},
"children": []
}
]
}Copy/paste React composition
Copy InteractivePresentationComponents.tsx and its optional CSS from the Professional Foundation Developer Kit, then add this module-specific composition. It is semantic and unstyled by default; pass styled after importing interactive-presentation-components.css for the supplied polished foundation. Either version accepts only a bounded already-authorized document and fails closed through the shared strict parsers.
import {
EntityList,
MetricStrip,
PresentationGrid,
ProgressList,
SeriesChart,
Timeline,
} from "./InteractivePresentationComponents";
export interface WorkOrdersPresentationData {
readonly metrics: unknown;
readonly recent: unknown;
readonly byStatus: unknown;
readonly trend: unknown;
readonly table: unknown;
readonly timeline: unknown;
}
export interface WorkOrdersPresentationProps {
/** Pass only the already-authorized presentation document returned by the API. */
readonly data?: WorkOrdersPresentationData | null;
readonly loading?: boolean;
readonly error?: boolean;
readonly styled?: boolean;
/** Record identity is navigation context; the detail API must authorize it again. */
readonly onOpenRecord?: (recordId: string) => void;
}
export function WorkOrdersPresentation({
data,
loading = false,
error = false,
styled = false,
onOpenRecord,
}: WorkOrdersPresentationProps) {
if (error) return <p role="alert">The Work Orders presentation could not be loaded.</p>;
if (loading || !data) return <p role="status">Loading Work Orders presentation...</p>;
return (
<main className={styled ? "bwhq-api-example" : undefined}>
<header>
<p>Work Orders</p>
<h1>Work order command center</h1>
<p>Current workload, priority, ownership, visible records, throughput, and recent field activity.</p>
</header>
<MetricStrip data={data.metrics} styled={styled} />
<div className={styled ? "bwhq-api-example__split" : undefined}>
<ProgressList data={data.byStatus} styled={styled} />
<EntityList data={data.recent} styled={styled} onOpenRecord={onOpenRecord} />
</div>
<SeriesChart data={data.trend} styled={styled} />
<PresentationGrid data={data.table} styled={styled} onOpenRecord={onOpenRecord} />
<Timeline data={data.timeline} styled={styled} onOpenRecord={onOpenRecord} />
</main>
);
}
Copy the live, authenticated module page
This document has no placeholder key and needs no invented endpoint. Save it to a page and add that page to a User Type menu. The registered native component calls /api/modules/work-orders, uses the current tenant session, and preserves the module's real list, detail, create/update, pagination, empty, loading, and error behavior. Dynamic Records discovers the organization's real tenant-owned modules when no module key is configured.
Copy the executable live page
{
"blocks": [
{
"_id": "work-orders-live",
"_type": "module.work-orders",
"props": {},
"children": []
}
]
}The server derives SaaS app, organization, user, DataRoles, locations, module-specific membership/privacy, and any AI-read gate from verified identity. A returned identifier can select a detail target, but the detail or write endpoint authorizes it again. Page layout, status, tone, totals, action names, and identifiers never grant authority.
Start with the smallest useful status model and explicit transition rules. Add industry-specific fields as secured dynamic fields instead of forking the native lifecycle.
A renderer being bundled in the tenant application does not make its data visible in every app. The server returns only components and records authorized for the current app and signed-in user; unavailable or unauthorized blocks fail closed.
Capability review: 2026-09-14. For exact current technical availability, use the generated API Map and first-class module inventory.