# Requests inbox

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

Source: https://littleworks.app/docs/view-inbox

![Requests inbox — local preview with synthetic data](https://littleworks.app/images/views/inbox.png)

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

1. 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.
2. 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.
3. 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.
4. 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.
5. 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.

> **Example boundaries**
> Deploy the five self-contained works, then replace each binding version with its returned deployment ID. Invoke the create work with sampleInput to add synthetic data. Use its returned ID in the detail sample. Create more records to explore pagination. The list scans at most four storage pages per request; a page can be empty while nextCursor still points to more matches. Continue with the cursor. Closing more than 10 records uses multiple atomic batches; partial completion is possible. Reload records before deciding whether to retry. Public reply is only a stored field in this starter. No customer message is sent.

## Load quote requests

### Work manifest

```json
{
  "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

```javascript
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

```json
{
  "filters": {},
  "search": "",
  "sort": null,
  "after": null,
  "limit": 25
}
```

## Load request details

### Work manifest

```json
{
  "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

```javascript
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

```json
{
  "filters": {},
  "search": "",
  "sort": null,
  "after": null,
  "limit": 25,
  "requestId": "REPLACE_WITH_CREATED_ID"
}
```

## Add an internal quote request

### Work manifest

```json
{
  "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

```javascript
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

```json
{
  "requirements": "Quote 40 embroidered hoodies for our sample team",
  "priority": false
}
```

## Update a quote request

### Work manifest

```json
{
  "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

```javascript
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

```json
{
  "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

```javascript
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

> **Replace placeholder versions**
> Every version below is a placeholder. Use the exact deployment ID for the named work. baseVersion:null creates a new view; when editing, read get_view and use its current version.

### validate_view / deploy_view arguments

```json
{
  "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"
    ]
  }
}
```
