Skip to content

UI extensions

An extension registers a path on your App URL’s origin against a target — one of 15 slots in the OneBooks UI. Manage extensions on the developer console’s Embedding tab with Add extension (org admins only; there’s no CLI command for this today). Each is { target, label, path, height?, isActive, position }, up to 20 per app. Your app needs an App URL first.

KindRenders asSize
ActionAn entry in the record’s Apps menu; opens your page in a dialog framed by OneBooks’ chrome bar, which the merchant closes with its × button, Escape or a click outsideStarts 480 px tall; your page can change it with app.resize() / app.autoResize() (60–1600 px), but the dialog never grows past the viewport — a taller page scrolls under the fixed chrome
BlockAn inline card on the record page — or, for DASHBOARD_BLOCK, in a From your apps section on the dashboardStarts at the height you register — 120–800 px, 240 px if you leave it empty — and your page can change it with app.resize() / app.autoResize() (60–1600 px)

Blocks load lazily as the merchant scrolls to them, so keep each one cheap to render. Size the initial height to your content’s usual size — a block that starts far too short or tall jumps when it first resizes.

app.autoResize() measures your page’s <body> content, so a block or dialog shrinks as well as grows with it — as long as <body> keeps its natural height: don’t give it a fixed or 100% height (see Sizing a block or dialog).

When an action dialog on a record closes, OneBooks reloads every block and the App data card on that record, so a block always shows what an action just changed.

A block card is headed by your extension’s label, with one light line under it naming who provides it — Provided by <your app> (<your organization>), not OneBooks, using your registered names. Your page can’t change or hide that line.

TargetKindRenders onInstallation needs
DASHBOARD_BLOCKBlockThe merchant’s dashboard—
INVOICE_ACTIONActionInvoice detail — Apps menuinvoices:read
INVOICE_BLOCKBlockInvoice detail — inline cardinvoices:read
QUOTE_ACTIONActionQuote detail — Apps menuquotes:read
QUOTE_BLOCKBlockQuote detail — inline cardquotes:read
CUSTOMER_ACTIONActionCustomer detail — Apps menucustomers:read
CUSTOMER_BLOCKBlockCustomer detail — inline cardcustomers:read
SUPPLIER_ACTIONActionSupplier detail — Apps menusuppliers:read
SUPPLIER_BLOCKBlockSupplier detail — inline cardsuppliers:read
PURCHASE_ACTIONActionPurchase detail — Apps menupurchases:read
PURCHASE_BLOCKBlockPurchase detail — inline cardpurchases:read
SALES_RETURN_ACTIONActionSales return (credit note) detail — Apps menusales-returns:read
SALES_RETURN_BLOCKBlockSales return detail — inline cardsales-returns:read
PURCHASE_RETURN_ACTIONActionPurchase return (debit note) detail — Apps menupurchase-returns:read
PURCHASE_RETURN_BLOCKBlockPurchase return detail — inline cardpurchase-returns:read

Scopes gate where an extension appears. An extension on a record renders only for businesses whose installation of your app holds that record type’s read scope (the last column — the same mapping App data uses); the dashboard block needs none.

The console enforces the other half when you save. Creating an extension on a record target, moving one to another target, or switching one back on fails with 400 EXTENSION_SCOPE_REQUIRED unless your app requests that target’s read scope:

A INVOICE_ACTION extension requires the "invoices:read" scope. Add it to your app's scopes first.

Add the scope on the OAuth tab first. An extension whose scope your app has since dropped can still be switched off, relabelled or reordered, and its path or height edited — it simply never renders.

An extension also renders only while your app is active, installed in that business and not suspended, and only while its URL stays on your App URL’s origin (see Paths) — and an Apps menu only appears on a record once some installed app has an action for it.

path is resolved against your App URL’s origin (not appended to the App URL’s own path) and must stay on it. Saving a path that breaks a rule fails with a 400:

Rulecode
Required, and starts with /EXTENSION_PATH_INVALID
No backslashes, spaces or control charactersEXTENSION_PATH_INVALID (path must not contain backslashes, spaces or control characters.)
Resolves to a page on your App URL’s own originEXTENSION_PATH_INVALID (path must resolve to a page on the app’s own origin.)
At most 500 charactersEXTENSION_PATH_TOO_LONG
Starts with a single / — never //EXTENSION_PATH_PROTOCOL_RELATIVE
No . or .. segmentsEXTENSION_PATH_TRAVERSAL
No scheme (/javascript:…)EXTENSION_PATH_HAS_SCHEME

OneBooks checks the origin again every time it renders an extension, and leaves out any whose URL would resolve anywhere else.

Use a query string if one page serves several extensions — the host adds its own target, resourceType and resourceId parameters too (see Embedded apps), so you can also branch on those.

label is a { en, ar?, es?, fr?, pt? } object of at most 40 characters per language — the same localized-text shape used across the platform (listings, app data field names). English is required; the others fall back to English automatically when omitted, so you can ship with English only and add translations later without breaking anything:

{
"target": "INVOICE_ACTION",
"label": {
"en": "Sync to Acme",
"ar": "مزامنة مع Acme",
"es": "Sincronizar con Acme",
"fr": "Synchroniser avec Acme",
"pt": "Sincronizar com a Acme"
},
"path": "/extensions/invoice-action"
}

For an action, the label is the Apps menu entry; for a block, it heads the card.

position orders your extensions on the same target (lower first). isActive: false hides an extension everywhere without deleting it — handy while you fix a broken page.

Embedded apps for the iframe contract every extension page follows, and the end-to-end guide for building an INVOICE_ACTION from scratch.