View examples
Requests inbox
A searchable inbox with filters, cursor pagination, a details modal, editable requests, and bulk closing. All records stay internal to this starter.

A searchable inbox with filters, cursor pagination, a details modal, editable requests, and bulk closing. All records stay internal to this starter.
Build this example
- Read get_context and list_views. Choose an unused view name; if adapting the name, update every work’s trigger.view and all work bindings together. Inspect existing works before deploying.
- Deploy each manifest and self-contained JavaScript source below with deploy_function. No terminal, imports, dependencies, or bundler is needed. Record each returned deployment ID.
- Test unpublished data works with their sample input and a fresh idempotency key. Only run sample actions when requested; they write synthetic records. Do not pass placeholder IDs or stale revisions.
- Replace each source/action version in the view definition, including modal bindings, with the matching deployment ID. Call validate_view with the deployment object; fix any issues, then call deploy_view.
- Open the returned manageUrl in Shopify to inspect the draft. Test empty states, form errors and refresh behaviour. Publish only when requested, using the returned version and current publishedVersion. A published page consumes one slot; its modals do not.
Load quote requests
Work manifest
{
"name": "views.requests-inbox.list",
"title": "Load quote requests",
"feature": "starter_inbox",
"environment": "preview",
"trigger": {
"type": "view",
"view": "requests-inbox",
"role": "data"
},
"permissions": {
"shopify": false
},
"inputSchema": {
"type": "object",
"properties": {
"filters": {
"type": "object",
"properties": {
"status": {
"type": "string"
}
},
"additionalProperties": false
},
"after": {
"type": [
"string",
"null"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 25
},
"search": {
"type": "string",
"maxLength": 200
},
"sort": {
"type": [
"object",
"null"
]
}
},
"required": [
"filters",
"after",
"limit"
],
"additionalProperties": false
}
}Self-contained JavaScript source
export default async function ({input, db}) {
const records = db.collection('requests'), items = [];
let cursor = input.after || undefined;
// Bound each search to four storage pages. A non-empty cursor means more data remains.
for (let scanned = 0; scanned < 4; scanned++) {
const page = await records.list({limit: input.limit - items.length,
...(cursor ? {after: cursor} : {}),
...(input.filters.status ? {where: {status: input.filters.status}} : {}),
...(input.sort ? {orderBy: input.sort} : {}),
});
for (const record of page.items) {
if (input.search && !String(record.data.requirements || '').toLowerCase().includes(input.search.toLowerCase())) continue;
const row = {...record.data, id: record.id, revision: record.revision, createdAt: record.createdAt};
row.customer = record.data.customerId ? {id: record.data.customerId, title: 'Open customer'} : null;
row.items = (record.data.items || []).map(item => ({id: item.productId, title: item.productTitle || 'Product', subtitle: `${item.quantity} × ${item.variantTitle || 'Default'}`}));
items.push(row);
}
cursor = page.nextCursor;
if (!cursor || items.length >= input.limit) break;
}
return {items, nextCursor: cursor || null};
}
Sample invocation input
{
"filters": {},
"search": "",
"sort": null,
"after": null,
"limit": 25
}Load request details
Work manifest
{
"name": "views.requests-inbox.detail",
"title": "Load request details",
"feature": "starter_inbox",
"environment": "preview",
"trigger": {
"type": "view",
"view": "requests-inbox",
"role": "data"
},
"permissions": {
"shopify": false
},
"inputSchema": {
"type": "object",
"properties": {
"filters": {
"type": "object",
"properties": {
"status": {
"type": "string"
}
},
"additionalProperties": false
},
"after": {
"type": [
"string",
"null"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 25
},
"search": {
"type": "string",
"maxLength": 200
},
"sort": {
"type": [
"object",
"null"
]
},
"requestId": {
"type": "string",
"minLength": 1,
"maxLength": 200
}
},
"required": [
"filters",
"after",
"limit",
"requestId"
],
"additionalProperties": false
}
}Self-contained JavaScript source
export default async function ({input, db}) {
const record = await db.collection('requests').get(input.requestId);
if (!record) return Response.json({error: 'This request no longer exists. Close this dialog and refresh the list.'}, {status: 404});
const customer = record.data.customerId ? {id: record.data.customerId, title: 'Open customer'} : null;
const items = (record.data.items || []).map(item => ({id: item.productId, title: item.productTitle || 'Product', subtitle: `${item.quantity} × ${item.variantTitle || 'Default'}`}));
return {...record.data, customer, items, id: record.id, revision: record.revision, createdAt: record.createdAt};
}
Sample invocation input
{
"filters": {},
"search": "",
"sort": null,
"after": null,
"limit": 25,
"requestId": "REPLACE_WITH_CREATED_ID"
}Add an internal quote request
Work manifest
{
"feature": "starter_inbox",
"environment": "preview",
"name": "views.requests-inbox.create",
"title": "Add an internal quote request",
"description": "Create an internal quote request from validated merchant input without contacting a customer.",
"trigger": {
"type": "view",
"view": "requests-inbox",
"role": "action"
},
"permissions": {
"shopify": false
},
"inputSchema": {
"type": "object",
"properties": {
"requirements": {
"type": "string",
"minLength": 1,
"maxLength": 2000
},
"budget": {
"type": "number",
"minimum": 0
},
"priority": {
"type": "boolean"
},
"dueDate": {
"type": "string",
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
}
},
"required": [
"requirements",
"priority"
],
"additionalProperties": false
}
}Self-contained JavaScript source
export default async function ({input, db}) {
const requirements = input.requirements.trim();
if (!requirements) return Response.json({error: 'Describe the request.'}, {status: 422});
const record = await db.collection('requests').create({
requirements, status: 'new', customerId: null, items: [],
priority: input.priority,
...(input.budget !== undefined ? {budget: {amount: input.budget, currencyCode: 'CAD'}} : {}),
...(input.dueDate ? {dueDate: input.dueDate} : {}),
internalNote: 'Created in Shopify admin. Internal request only.',
});
return {id: record.id, revision: record.revision};
}
Sample invocation input
{
"requirements": "Quote 40 embroidered hoodies for our sample team",
"priority": false
}Update a quote request
Work manifest
{
"feature": "starter_inbox",
"environment": "preview",
"name": "views.requests-inbox.update",
"title": "Update a quote request",
"description": "Apply an allowed merchant status, internal note, or public reply change using the current record revision.",
"trigger": {
"type": "view",
"view": "requests-inbox",
"role": "action"
},
"permissions": {
"shopify": false
},
"inputSchema": {
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"revision": {
"type": "integer",
"minimum": 1
},
"status": {
"type": "string",
"enum": [
"new",
"reviewing",
"replied",
"closed"
]
},
"publicReply": {
"type": "string",
"maxLength": 4000
},
"internalNote": {
"type": "string",
"maxLength": 2000
}
},
"required": [
"id",
"revision"
],
"additionalProperties": false
}
}Self-contained JavaScript source
const TRANSITIONS = {
new: ['reviewing', 'closed'],
reviewing: ['replied', 'closed'],
replied: ['reviewing', 'closed'],
closed: ['reviewing']
};
const MAX_DOCUMENT_BYTES = 16 * 1024;
const fail = (status, code, error) => Response.json({ code, error }, { status });
const has = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
const requestReference = id => `QR-${id.replace(/[^a-z0-9]/gi, '').slice(-12).toUpperCase()}`;
export default async function ({ input, db }) {
if (!has(input, 'status') && !has(input, 'publicReply') && !has(input, 'internalNote')) {
return fail(422, 'NO_CHANGES', 'Provide a status, public reply, or internal note to update.');
}
const requests = db.collection('requests');
const current = await requests.get(input.id);
if (!current) return fail(404, 'REQUEST_NOT_FOUND', 'Quote request not found.');
if (current.revision !== input.revision) {
return fail(409, 'REVISION_CONFLICT', 'This quote request changed. Reload it before updating.');
}
const nextStatus = has(input, 'status') ? input.status : current.data.status;
if (nextStatus !== current.data.status && !TRANSITIONS[current.data.status]?.includes(nextStatus)) {
return fail(422, 'INVALID_TRANSITION', `Cannot move a quote request from ${current.data.status} to ${nextStatus}.`);
}
const next = { ...current.data, status: nextStatus };
if (has(input, 'publicReply')) {
const publicReply = input.publicReply.trim();
if (publicReply) next.publicReply = publicReply;
else delete next.publicReply;
}
if (has(input, 'internalNote')) {
const internalNote = input.internalNote.trim();
if (internalNote) next.internalNote = internalNote;
else delete next.internalNote;
}
if (nextStatus === 'replied' && !next.publicReply) {
return fail(422, 'REPLY_REQUIRED', 'Add a public reply before marking this request replied.');
}
if (new TextEncoder().encode(JSON.stringify(next)).byteLength > MAX_DOCUMENT_BYTES) {
return fail(422, 'REQUEST_TOO_LARGE', 'This update would make the quote request too large. Shorten the reply or note.');
}
let updated;
try {
updated = await requests.update(current.id, next, { revision: input.revision });
} catch (error) {
if (error?.code === 'WRITE_CONFLICT') {
return fail(409, 'REVISION_CONFLICT', 'This quote request changed. Reload it before updating.');
}
throw error;
}
return {
id: updated.id,
reference: requestReference(updated.id),
revision: updated.revision,
status: updated.data.status,
publicReply: updated.data.publicReply || null,
internalNote: updated.data.internalNote || null,
updatedAt: updated.updatedAt
};
}
Close selected quote requests
Work manifest
{
"feature": "starter_inbox",
"environment": "preview",
"name": "views.requests-inbox.close",
"title": "Close selected quote requests",
"description": "Close selected requests using their current revisions; inspect partial results before retrying.",
"trigger": {
"type": "view",
"view": "requests-inbox",
"role": "action"
},
"permissions": {
"shopify": false
},
"inputSchema": {
"type": "object",
"properties": {
"records": {
"type": "array",
"minItems": 1,
"maxItems": 25,
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"revision": {
"type": "integer",
"minimum": 1
}
},
"required": [
"id",
"revision"
],
"additionalProperties": false
}
}
},
"required": [
"records"
],
"additionalProperties": false
}
}Self-contained JavaScript source
export default async function ({input, db}) {
const requests = db.collection('requests'), operations = [], seen = new Set();
for (const selected of input.records) {
if (seen.has(selected.id)) return Response.json({error: 'Choose distinct records.'}, {status: 422});
seen.add(selected.id);
const current = await requests.get(selected.id);
if (!current || current.revision !== selected.revision) return Response.json({error: 'A selected request changed. Refresh before trying again.'}, {status: 409});
if (current.data.status !== 'closed') operations.push({type: 'update', collection: 'requests', id: current.id, revision: current.revision, data: {...current.data, status: 'closed'}});
}
// Each batch is atomic; multiple batches may partially complete. Do not blindly retry.
for (let offset = 0; offset < operations.length; offset += 10) await db.batch(operations.slice(offset, offset + 10));
return {closed: operations.length};
}
View definition
validate_view / deploy_view arguments
{
"baseVersion": null,
"definition": {
"apiVersion": 3,
"name": "requests-inbox",
"title": "Requests inbox",
"actions": [
{
"id": "close",
"label": "Close selected",
"work": "views.requests-inbox.close",
"version": "00000000-0000-4000-8000-000000000004",
"scope": "bulk",
"source": "records",
"input": {
"records": {
"source": "selection",
"fields": {
"id": "id",
"revision": "revision"
}
}
},
"confirmation": "Close the selected requests? They will remain in your records.",
"refresh": [
"records"
]
}
],
"layout": {
"type": "stack",
"main": [
"table"
],
"aside": [],
"elements": {
"table": {
"type": "Records",
"props": {
"columns": [
{
"field": "requirements",
"label": "Request",
"format": "text"
},
{
"field": "status",
"label": "Status",
"format": "badge",
"badges": [
{
"value": "new",
"label": "New",
"tone": "info"
},
{
"value": "reviewing",
"label": "Reviewing",
"tone": "warning"
},
{
"value": "replied",
"label": "Replied",
"tone": "success"
},
{
"value": "closed",
"label": "Closed",
"tone": "neutral"
}
]
},
{
"field": "createdAt",
"label": "Submitted",
"format": "date"
}
],
"source": "records",
"heading": "Requests inbox",
"empty": {
"heading": "No matching records",
"message": "Try another filter, or return when new submissions arrive."
},
"bulkActions": [
"close"
],
"onRowClick": "openDetails"
}
}
}
},
"sources": [
{
"id": "records",
"work": "views.requests-inbox.list",
"version": "00000000-0000-4000-8000-000000000002",
"filters": [
{
"field": "status",
"label": "Status",
"options": [
{
"value": "new",
"label": "New"
},
{
"value": "reviewing",
"label": "Reviewing"
},
{
"value": "replied",
"label": "Replied"
},
{
"value": "closed",
"label": "Closed"
}
]
}
],
"search": {
"label": "Search requests"
},
"sorts": [
{
"id": "newest",
"label": "Newest first",
"field": "$createdAt",
"direction": "desc"
},
{
"id": "oldest",
"label": "Oldest first",
"field": "$createdAt",
"direction": "asc"
}
]
}
],
"modalLinks": [
{
"id": "openDetails",
"label": "Open request",
"modal": "details",
"scope": "row",
"source": "records",
"input": {
"requestId": {
"source": "row",
"field": "id"
}
}
},
{
"id": "newRequest",
"label": "Add request",
"modal": "create",
"scope": "page",
"input": {}
}
],
"modals": [
{
"id": "details",
"title": "Quote request",
"inputs": {
"requestId": "string"
},
"sources": [
{
"id": "detail",
"work": "views.requests-inbox.detail",
"version": "00000000-0000-4000-8000-000000000004",
"inputBindings": {
"requestId": "requestId"
}
}
],
"actions": [
{
"id": "update",
"label": "Update request",
"work": "views.requests-inbox.update",
"version": "00000000-0000-4000-8000-000000000001",
"input": {
"id": {
"source": "data",
"field": "id"
},
"revision": {
"source": "data",
"field": "revision"
},
"status": {
"source": "form",
"field": "status"
},
"publicReply": {
"source": "form",
"field": "reply"
},
"internalNote": {
"source": "form",
"field": "note"
}
},
"fields": [
{
"name": "status",
"label": "Status",
"type": "select",
"required": true,
"initialField": "status",
"options": [
{
"value": "new",
"label": "New"
},
{
"value": "reviewing",
"label": "Reviewing"
},
{
"value": "replied",
"label": "Replied"
},
{
"value": "closed",
"label": "Closed"
}
]
},
{
"name": "reply",
"label": "Reply visible to the customer",
"type": "textarea",
"initialField": "publicReply",
"maxLength": 4000
},
{
"name": "note",
"label": "Internal note",
"type": "textarea",
"initialField": "internalNote",
"maxLength": 2000
}
],
"confirmation": "Save these changes to the request? The reply is visible to the customer; the internal note stays private.",
"scope": "page",
"source": "detail",
"refreshPage": [
"records"
],
"refresh": [
"detail"
],
"closeModal": false
}
],
"pageActions": [
"update"
],
"layout": {
"type": "stack",
"main": [
"section"
],
"elements": {
"section": {
"type": "Section",
"props": {
"heading": "Request details"
},
"children": [
"detail"
]
},
"detail": {
"type": "Details",
"props": {
"source": "detail",
"fields": [
{
"field": "requirements",
"label": "Requirements",
"format": "text"
},
{
"field": "items",
"label": "Requested products",
"format": "list"
},
{
"field": "status",
"label": "Status",
"format": "badge",
"badges": [
{
"value": "new",
"label": "New",
"tone": "info"
},
{
"value": "reviewing",
"label": "Reviewing",
"tone": "warning"
},
{
"value": "replied",
"label": "Replied",
"tone": "success"
},
{
"value": "closed",
"label": "Closed",
"tone": "neutral"
}
]
},
{
"field": "publicReply",
"label": "Customer reply",
"format": "text"
},
{
"field": "internalNote",
"label": "Internal note",
"format": "text"
},
{
"field": "budget",
"label": "Budget",
"format": "money"
},
{
"field": "priority",
"label": "Priority",
"format": "boolean"
},
{
"field": "dueDate",
"label": "Follow-up date",
"format": "date"
}
]
}
}
}
}
},
{
"id": "create",
"title": "Add an internal request",
"inputs": {},
"sources": [],
"actions": [
{
"id": "create",
"label": "Add internal request",
"work": "views.requests-inbox.create",
"version": "00000000-0000-4000-8000-000000000003",
"scope": "page",
"input": {
"requirements": {
"source": "form",
"field": "requirements"
},
"budget": {
"source": "form",
"field": "budget"
},
"priority": {
"source": "form",
"field": "priority"
},
"dueDate": {
"source": "form",
"field": "dueDate"
}
},
"fields": [
{
"name": "requirements",
"label": "Request",
"type": "textarea",
"required": true,
"maxLength": 2000
},
{
"name": "budget",
"label": "Budget",
"type": "money",
"currency": "CAD",
"min": 0,
"step": 0.01
},
{
"name": "priority",
"label": "Priority request",
"type": "checkbox",
"defaultValue": false
},
{
"name": "dueDate",
"label": "Follow-up date",
"type": "date"
}
],
"refreshPage": [
"records"
],
"closeModal": true
}
],
"layout": {
"type": "stack",
"main": [
"internal"
],
"elements": {
"internal": {
"type": "Section",
"props": {
"heading": "Add an internal request"
},
"children": [
"description",
"create"
]
},
"description": {
"type": "Text",
"props": {
"text": "Record a request for your team. This creates an internal record and does not contact a customer.",
"subdued": true
}
},
"create": {
"type": "Form",
"props": {
"action": "create"
}
}
}
}
}
],
"pageActions": [
"newRequest"
]
}
}