Skip to main content

Admin Block Extensions

An admin block is a sandboxed iframe the host renders inline on a merchant admin page (product, order, customer, etc.). Use blocks for UI that is always present on the page — review summaries, fulfilment status, warehouse KPIs, or anything you want the merchant to see without clicking a button. If you want a button that opens a modal on demand, use an Admin Action instead.

How the Host Renders It

<AdminExtensionSlot target="..." resourceId resourceType domainSlug /> mounts a sandboxed iframe per registered extension:
Default height is 200px; resize requests are clamped to a maximum of 2000px (see Resize below). The host calls GET /api/apps/admin-extensions?target=<target>&domainSlug=<slug> to discover registered extensions, then renders one iframe per result.

Available Targets

Targets are filtered by exact string match against the wired slots in the LaunchMyStore admin. Every slot uses the admin.<resource>.render naming convention — your manifest’s target must match exactly. 26 admin-block slots are currently wired (plus separate slots for admin actions and print actions):
Building a backend entry for a product page — like an “Edit SEO”, “Reviews summary”, or “Inventory note” panel that staff see while viewing a product? Use target: "admin.product-details.block.render". The host appends resourceId (the product UUID) and resourceType=product to your iframe URL, so you can fetch the right product immediately without a separate routing step.

Resource Detail Pages

List Pages

Settings Pages

Analytics

Other

The slot string is matched exactlyproduct.details.block won’t render at admin.product-details.block.render. Always copy the target value verbatim from the table above.
Targets not in the tables above won’t render even if the API accepts the upload — only the wired slots resolve. If you need a new target, open a support ticket with the page and resource type you want.

Manifest

Declare admin blocks under extensions.adminExtensions[] in your app’s app.json:
Relative URLs (e.g. /extensions/.../iframe.html) are absolutised by the host before being returned to the admin — you always get back an absolute URL, so there are no domain-resolution surprises inside the iframe. Use HTTPS for production apps; the absolutizer passes absolute URLs through untouched and does not enforce the scheme.

Iframe URL Parameters

The host appends the following query params when building the iframe src:
For convenience, when resourceType is product, order, or customer the host also sets a typed alias param — productId, orderId, or customerId respectively (mirroring the SDK useApi().data shape). These carry the same value as resourceId. locale is not passed. Whichever param you read, always re-resolve the resource server-side using your session token before trusting it.

Bootstrap

Initialise the App Bridge SDK with the apiKey you registered the app under, and the base64 host from the URL:
app.dispatch(action, payload) and app.dispatchAndWait(action, payload) take an action string and a payload object — not a {type, payload} envelope.

Resizing the Iframe

The iframe starts at 200px. To grow it, post the resize message directly — this one is not routed through app.dispatch:
The host clamps the height to 2000px and applies it only if extensionId matches. Omit extensionId and the resize is ignored — your iframe stays empty-looking at 200px. A typical auto-resize hook using ResizeObserver:

App Bridge Actions (Admin host)

Admin blocks can use the full admin App Bridge action set. See App Bridge Overview for the complete reference. Common calls:

Show a toast

Open a modal

Resource picker

Session token (for backend calls)

The SDK does expose LOADING_START / LOADING_STOP actions (dispatched by the React useLoading hook) that draw a thin progress bar on the admin host’s title bar. They’re for host-level affordance — inside your block iframe, render loading skeletons in your own UI rather than relying on the global bar.

Example: Reviews Panel

Discovery API

GET /api/apps/admin-extensions?target=<target>&domainSlug=<slug> returns all installed extensions for that target. Pass &type=admin_block to exclude action and print-action entries that also live in /api/apps/admin-extensions.
The url and appUrl fields are server-absolutised so the admin always receives an iframe-able HTTPS URL.

Security

Admin blocks render with allow-scripts allow-same-origin allow-forms allow-popups. The bridge postMessage protocol is the only cross-frame channel.
Anything the iframe sends to your backend should be authorised with a fresh session token (app.getSessionToken()) and verified against your app’s clientSecret server-side.
Always serve url / appUrl over HTTPS in production so the iframe loads on an HTTPS admin. Note the install pipeline does not reject HTTP schemes — absolute URLs are passed through as-is (and local dev URLs may resolve to http), so treat HTTPS as your responsibility.
resourceId and domainSlug come from the merchant’s browser. Always re-fetch the resource server-side with the session token before trusting it.

Installing

Admin blocks are declared in app.json; the install pipeline doesn’t write per-handle schema files for them (unlike admin actions and print actions). Instead, each adminExtensions[] entry is fanned out into a registry row scoped to (appId, storeId, handle, target) so the discovery API can return it. Registry fan-out runs automatically on both install paths:
  • OAuth install — when the merchant completes POST /apps/oauth/token with grant_type=authorization_code, the install pipeline creates the installation, then synchronously registers every adminExtensions[] entry from your app’s published manifest.
  • Direct/developer installPOST /apps/store/install/:appId runs the same fan-out.
Re-running install (reinstall, or the developer pushing a new app version) is idempotent: rows keyed by (appId, storeId, handle, target) are upserted, not duplicated. Uninstall destroys all registry rows for that (appId, storeId) pair. The target column accepts any string verbatim — there is no enum cap on what you can register — but as noted above the host only renders entries whose target matches one of the wired slot tables.

See Also