Extensions
Extensions let your app inject UI or behaviour into a merchant’s store. Each extension type maps to a specific surface — storefront, checkout, post-purchase, admin — and is declared in your app’sapp.json.
For declarative business logic (cart transforms, discounts, shipping
rates, validation), see Functions — those run
server-side and return JSON, not UI.
Extension Types
Storefront Blocks
Liquid blocks merchants drop into theme sections (e.g. product page,
homepage).
Checkout UI
Sandboxed iframe rendered at a named slot inside the checkout.
Talks back to the host via App Bridge.
Post-Purchase
Iframe rendered on the order status (thank-you) page right after checkout. Smaller wired-action set
than checkout UI.
Admin Blocks
Iframe embedded on admin resource detail pages (product, order,
customer, etc.).
Admin Actions
Button on an admin resource page that opens an iframe modal.
Admin Print Actions
Generate printable templates for orders, invoices, packing slips.
Email Templates
Override the merchant’s transactional emails for named events.
App Scripts
JavaScript files auto-injected on every storefront page — pixels,
chat widgets, floating buttons. No theme edits.
Web Pixels
Sandboxed customer-event listeners (
page_viewed, cart_updated,
checkout_completed).Customer Account
Iframe blocks on the new customer-account dashboard, order status,
profile, and order list pages.
Order Routing
Rules that pick the fulfillment location for an order at checkout.
App Proxy
Serve dynamic content from your app server on the merchant’s own
domain — tracking widgets, account pages, AJAX endpoints.
app.json:
- Storefront snippets — reusable
Liquid includes (
{% render 'foo' %}). - Storefront embeds —
overlay/floating scripts injected at
<head>or before</body>per the merchant’s app-embed toggle. - Functions (nine WASM function types; a tenth
declarative
fulfillment_location_ruleis accepted at install but not yet dispatched at runtime) — declarative modules that fire on cart verification / order placement.
How It Works
- Declare extensions in your app’s
app.json(manifest). - Install them into a merchant’s store via
POST /api/apps/install-extensions— either inline (extensions: {…}) or by referencing the marketplace catalog (fromCatalog: true). - The platform writes the files under
extensions/{domainSlug}/{appHandle}/. - The matching storefront / checkout / admin surface picks them up via
/api/apps/extensions,/api/apps/checkout-extensions,/api/apps/admin-extensions, etc.
Extension Directory Layout
After install, every app lives at one path on disk:Where to store app data
Apps do not write to the platform filesystem for runtime state. Every piece of merchant- or customer-scoped data your app needs to read back later — install config, feature toggles, per-product counters, per-customer state, event logs — is persisted as a metafield. There is no app-owned database on the platform, so apps push public summaries (and private blobs) into metafields scoped to a resource (shop, product, customer, order, …).
Accessing metafields
Your app reads and writes metafields through the OAuth Metafields API —/api/v1/metafields.json — authenticated
with your Bearer access token and the read_metafields / write_metafields
scopes.
Namespace convention
Use one namespace per app, formatted asapp_<handle_with_underscores>:
The
app_* prefix is the reserved per-app namespace convention on the
platform; it keeps your app’s data isolated from custom (merchant-managed)
and from other apps’ namespaces.
A few apps additionally publish a public summary under a second,
shorter namespace so themes can read it via Aqua:
Owner-type scoping
Pick the owner that matches the scope of the data:
Compound config is fine — store the whole blob as a single
type: "json" metafield under one key like config. The metafield
validator decodes JSON on read so consumers get back an object.
Example: write install config
Example: append to a per-product log
For log-shaped data (click logs, recent reviews, subscriber lists), read the existing JSON array, append, and write back. Cap the array length to avoid unbounded growth — there is no platform-side trimming.Cache invalidation
Every metafield write fires automatic cache invalidation — both the cached copy of the owning resource and any cached page HTML that rendered it. The next storefront render sees the new value. You do not need to manually invalidate anything.What not to store as metafields
- Large blobs (raw images, video, full PDF documents). Use the file reference type instead — upload to R2 / CDN and store the URL.
- High-churn counters that update on every page view (
view_count). Batch into hourly aggregates or use a backend counter service. - Cross-store data. Metafields are scoped per store; if you need global app state, run your own backend.
Manifest
app.json declares all extensions in one place. Keys under extensions
are camelCase:
admin-actions/ and print-actions/ — not as inline arrays in
app.json. The install endpoint writes them; see
Admin Actions and
Admin Print Actions.
Installing Extensions
Two install modes are supported byPOST /api/apps/install-extensions.
This is a server-to-server endpoint (no CORS): inline uploads require
the platform’s internal API key and are performed by the backend as
part of the app install flow — not called directly from a browser with a
merchant token. The only browser-accepted path is Mode B (fromCatalog: true) with a merchant Bearer whose store matches domainSlug.
Mode A — inline upload (internal, server-to-server)
For platform-side deploys that deliver arbitrary extension files:blocks/{handle}.aqua+blocks/{handle}.schema.jsonsnippets/{handle}.aquaassets/{filename}(fetched from URL)admin-actions/{handle}.schema.jsonprint-actions/{handle}.schema.jsonemail-templates/{event}.json
Mode B — fromCatalog
For apps already published to the marketplace catalog:app.json, blocks/, snippets/, functions/ from the catalog
into the per-merchant directory. This is the one path that accepts a
merchant Bearer instead of the internal API key — the backend verifies the
merchant owns the domainSlug store before copying.
Email templates are persisted by the platform during install.
The file written here is a read-only preview — the authoritative copy
is the stored template record.
Uninstalling
extensions/{domainSlug}/{appHandle}/
directory. Asset cache + manifest cache are invalidated automatically.
Storefront Block Schemas
Storefront blocks define settings the merchant can configure in the theme editor:.aqua template, settings are available as block.settings.*
(or block.<id> — both work; the Aqua engine auto-resolves to .settings.<id>).
Targets — Quick Reference
Storefront blocks
index— Homepageproduct— Product pagescollection— Collection pagescart— Cart pagearticle— Blog articlespage— Custom pagesblog— Blog listingsearch— Search results
schema.target is a passthrough hint (not enforced at install). Full
list: Storefront Blocks.
Checkout UI (currently wired slots)
checkout-contact-aftercheckout-shipping-aftercheckout-shipping-method-beforecheckout-payment-beforecheckout-payment-aftercheckout-order-summary-beforecheckout-order-summary-afterpurchase.checkout.cart-line-list.render-afterpurchase.checkout.reductions.render-afterpurchase.checkout.actions.render-beforepurchase.thank-you.block.renderpurchase.order-status.block.renderpurchase.thank-you.cart-line-list.render-afterpurchase.order-status.cart-line-list.render-after
Admin blocks
product.details.blockorder.details.blockcustomer.details.blockcollection.details.blockdiscount.details.block- 26 total — see Admin Blocks.
Admin actions
admin.order-details.action.render(more shipping as resource pages wire up)
Admin print actions
admin.order-details.print.render
Email templates
Replace built-in transactional emails for named events:order_confirmation(MVP-wired)
See Also
- App Bridge Overview — postMessage contract used by checkout, post-purchase, and admin iframes.
- Functions Overview — declarative server-side business logic, not UI.