Storefront and admin extensions
An app on LaunchMyStore isn't just a remote service — it can ship 11 different extension types that install onto a merchant's store. Storefront blocks render inside the theme. Admin blocks appear inside the merchant admin. Six declarative functions run server-side during checkout. This guide walks through every extension type and where each one shows up.
The 11 extension types
- Storefront block — a Liquid block (
.aqua) merchants drop into a theme section. - Storefront snippet — a reusable Liquid include other Liquid files reference.
- Checkout extension — UI injected at one of checkout's extension points.
- Post-purchase extension — a page rendered immediately after order placement.
- Admin block — iframe injected at an admin extension target.
- Cart transform function — rewrites cart line items during cart validation.
- Shipping rate function — emits custom shipping rates.
- Payment customization function — hides/renames/reorders payment methods.
- Delivery customization function — hides/renames/reorders shipping options.
- Order validation function — blocks order placement on rule violation.
- Discount function — emits declarative discount entries.
Functions are covered in depth in our app functions guide; this article focuses on the first five surfaces and on the install/uninstall mechanics.
Storefront block (.aqua)
A storefront block is a small Liquid file plus a JSON schema. Merchants add it to a section in the theme editor; the block renders inline with the theme.
{% comment %} blocks/reviews-widget.aqua {% endcomment %}
<div class="reviews-widget" data-product="{{ product.id }}">
<h3>{{ block.settings.heading }}</h3>
<!-- review content -->
</div>
// blocks/reviews-widget.schema.json
{
"name": "Reviews widget",
"target": "product",
"settings": [
{ "id": "heading", "type": "text", "default": "Customer reviews" }
]
}
The target declares which template the block is eligible for (product, collection, index, etc.). Settings appear in the theme editor sidebar.
Storefront snippet
Snippets are reusable fragments other Liquid files can {% render %}. Useful for shared components like a star-rating row. They install into the storeโs snippet directory and themes pull them in with the standard {% render %} rules.
Checkout extension
Checkout extensions inject UI at named extension points during the checkout flow (after shipping address, before payment, etc.). They consume cart and customer state and can present custom fields, banners, or upsells. The checkout host loads them via CheckoutExtensionSlot and supports App Bridge for host-side actions.
Post-purchase extension
Post-purchase extensions render after order placement but before the order confirmation page. The classic use case is a single-click upsell: "Add this complementary item for 20% off?" The extension has access to the just-placed order and can append additional items to it server-side. Loaded via PostPurchaseExtensions.
Admin block
Admin blocks are iframes embedded in the merchant admin at one of 26 verified injection points:
- product.details.block, order.details.block, customer.details.block, collection.details.block
- discount.details.block, gift-card-list, blog-list, contact-list
- analytics, sales-analytics, inventory
- shipping-settings, payment-settings, pos-settings, account-settings
- collection-list, order-list, abandoned-order-list, order-create
- ... and more (the full list is in the extensions reference)
Your extension manifest declares its target:
{
"type": "admin_block",
"target": "product.details.block",
"url": "/admin/product-analytics.html",
"settings": []
}
The host renders an iframe pointing at your url, passes apiKey and host query parameters, and you bootstrap with App Bridge. The iframe self-resizes by posting APP_BRIDGE_RESIZE with its matching extensionId.
How extensions install
App install is a two-step fan-out:
- Install record — LaunchMyStore writes the install with the granted scopes and stores the extension manifest against the app.
- Storefront deploy — the install triggers
POST /api/apps/install-extensions, which copies every block, snippet, and asset into the store so the theme renderer can include them.
Both steps must succeed. The deploy step means storefront blocks are available in the theme editor immediately and at render time with the same caching as theme files.
How uninstall works
Uninstall reverses the same fan-out: the extension files are removed from the store, the OAuth access token is revoked, the install row is marked uninstalled, and the app receives an app/uninstalled webhook identifying the merchant.
Configuring extension settings
Each extension declares its merchant-configurable settings in its schema (storefront blocks → theme editor sidebar; admin blocks → in-iframe forms; functions → the app's settings UI). Merchant settings are stored against the install row and passed to the extension at render or run time.
FAQ
Where do admin extensions show up in the merchant admin?
One of 26 injection points across the admin: every detail page (product, order, customer, collection, discount, blog), every list page (orders, abandoned orders, contacts, gift cards), and settings pages (shipping, payment, POS, account, analytics). Each extension's target is declared in its manifest.
Can a single app ship more than one extension?
Yes — that's the normal case. A reviews app might ship a storefront block (the widget), a snippet (the star icon), an admin block (the moderation queue), and a discount function (loyalty discounts). They all install together when the merchant approves.
Do storefront blocks have access to product data?
Yes — they're Liquid, so they can read every global object the theme can: product, collection, cart, customer, shop, plus all metafields via the standard {owner}.metafields.{namespace}.{key} pattern.
How do I update an extension after install?
Push a new app version from the developer portal. Merchants on auto-update receive the new manifest immediately; those who paused auto-update see an "Update available" prompt. The install fan-out runs again to refresh the on-disk files.
Why is my admin block iframe stuck at 200px?
Almost always an extensionId mismatch on the resize message. Your iframe URL has an ?extensionId= query parameter; you must echo that exact value back in the APP_BRIDGE_RESIZE payload, otherwise the host ignores the resize.
Can extensions share state across the admin and storefront?
Yes, via metafields. Your admin extension writes a metafield (e.g. my_app.product_badge) using your write_metafields scope; your storefront block reads it via product.metafields.my_app.product_badge. Metafields are the canonical bridge between the two surfaces.