Blocks and extension targets
A block is how an app shows something inside ARC without sending markup. The app (or ARC, from a layout in the app's package) produces a tree of fixed components. ARC checks the tree, resolves every record link in the viewer's own scope, and each ARC screen draws it with its own components. Your app never sends HTML, styles or scripts, and never formats money or dates.
Where a block can appear
Declare each place in your manifest's extensions list, one entry per place. The place is the
target.
| Target | Surface | How it is drawn | Opened on |
|---|---|---|---|
admin.order.details.block | Admin console | Native slot: an iframe url, or a layout / blockEndpoint | The order |
admin.product.details.block | Admin console | Native slot: an iframe url, or a layout / blockEndpoint | The product |
admin.app.home | Admin console | Native slot: an iframe url, or a layout / blockEndpoint | — |
admin.order.details.action | Admin console | Iframe url | The order |
admin.print.action | Admin console | Iframe url | — |
admin.order.index.selection-action | Admin console | Iframe url, with resources | The selected rows |
buyer.account.block | Buyer app, portal | Block | — |
buyer.cart.block | Buyer app, portal | Block | — |
buyer.header.badge | Buyer app, portal | Block (one line) | — |
buyer.order.details.block | Buyer app (Track order), portal (order page) — not drawn yet | Block | The order |
product.details.app_block | Buyer app, portal | Block | The product |
storefront.footer.block | Portal, signed-out visitors too | Block | — |
sales.order.block | Sales app | Block | The order |
sales.customer.block | Sales app | Block | The customer |
sales.visit.action | Sales app | Native button through the app proxy | The visit |
A block entry carries exactly one of:
blockEndpoint: a path on your verified origin (appUrl). ARC POSTs a signed request to it and you answer with the component tree.layout: a fileblocks/<name>.jsonin your app's package. ARC renders it itself; you need no server.
A native admin slot (admin.order.details.block, admin.product.details.block,
admin.app.home) carries exactly one of url (your own page in a sandboxed iframe), layout or
blockEndpoint. The other admin targets take only a url.
Not drawn yet. The console does not draw a
layoutorblockEndpointon a native admin slot yet. A manifest may declare one, but the slot shows nothing for it until native drawing ships. Aurlentry draws as before.
A buyer block opened on a record checks that record against the viewer: a buyer only ever gets blocks for their own orders.
Components
A block answer is { "title"?: string, "components": [...] }. Each component has a type.
| Type | Fields | Notes |
|---|---|---|
text | text, variant? (body, heading, caption), tone? | |
number | value, label?, unit? | Never money |
money | amount, currencyCode, label? | amount is a decimal string such as "1250.50"; ARC formats it |
rating | value, max? (1–10, default 5), label? | 0 ≤ value ≤ max |
action | label, path, tone? | path goes through the app proxy |
entityRef | ref: { type, id }, label? | customer, order or visit; dropped if the viewer cannot see it |
list | items, label? | Holds the components above; lists do not nest |
table | variant, columns, rows, label? | Top level only |
form | formKey, submitLabel, fields, label? | Admin console only, from a layout only |
Tables
Not drawn yet. ARC accepts tables today, but no screen draws them yet. A block made only of tables shows nothing until table drawing ships.
{
"type": "table",
"variant": "steps",
"columns": [
{ "key": "step", "label": "Step", "kind": "text" },
{ "key": "at", "label": "When", "kind": "date" }
],
"rows": [
{ "state": "done", "cells": [{ "key": "step", "text": "Order placed" }, { "key": "at", "date": "2026-11-10" }] },
{ "state": "current", "cells": [{ "key": "step", "text": "Dispatched" }, { "key": "at", "date": "2026-11-11T11:05:00.000Z" }] },
{ "state": "upcoming", "cells": [{ "key": "step", "text": "Delivered" }, { "key": "at" }] }
]
}
variantisgrid(plain rows) orsteps(a journey, one row per step).- Each column has a
key, alabeland akind:textordate. At most 6 columns. - Each row has
cells. A cell'skeynames a column, and its value matches that column's kind:textin a text column,datein a date column. A cell may be empty. At most 20 rows. - In
steps, every row has astate:done,currentorupcoming. The first text column is the step's label and the first date column its time. Agridrow has nostate. - Keys are 1–63 lowercase letters, digits or underscores, starting with a letter.
- A table counts as
1 + rowscomponents toward the 50-component limit.
Dates
A date cell holds one of two things:
- A day:
YYYY-MM-DD, a real calendar date. Drawn as a date only, never shifted by a time zone. Use it for things that happen on a day, such as a packing date. - A moment: a full ISO date-time with
Zor an offset, such as2026-11-11T11:05:00.000Z(whatDate.toISOString()gives). Drawn as date and time in the viewer's time zone.
Anything else is refused. Never send a formatted date; each ARC screen formats it.
Forms
A form lets an admin edit some of your app's own custom fields on the record the block is open on.
{
"type": "form",
"formKey": "driver",
"submitLabel": "Save driver",
"fields": [
{ "key": "driver_name", "label": "Driver name", "kind": "text", "value": "Sam Lee" },
{ "key": "driver_phone", "label": "Driver phone", "kind": "phone" }
]
}
- Fields are
textorphone, at most 6, each value at most 200 characters. A form with no fields is a single button. - Forms appear only in the admin console, and only from an ARC-hosted
layout. AblockEndpointanswer that holds a form is refused, and the whole block is hidden. - A form writes only your app's own custom fields. It never sets a price, a total, a date or a status on an order.
Limits
| Limit | Value |
|---|---|
| Components in one block (list items, table rows and form fields count) | 50 |
| Answer size | 32 KB |
Time ARC waits for your blockEndpoint | 3 s |
| Text | 500 characters |
| Label | 80 characters |
A block that breaks a rule, times out or fails is hidden. ARC never shows half a block, and an older app build skips a component type it does not know.
Apps with no server
A manifest with no appUrl, no extension url or blockEndpoint, no proxyPaths and no webhooks
is server-less: everything it shows comes from layouts in its package. Such a manifest may leave
out redirectUrls. Every other manifest needs 1 to 10. Which apps may publish a server-less manifest
is decided when the version is published.