> ## Documentation Index
> Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt
> Use this file to discover all available pages before exploring further.

# App Manifest (app.json)

> The canonical reference for every field declared in an app.json manifest

# App Manifest

Every LaunchMyStore app ships a single `app.json` file at the root of the
app package. The manifest declares the app's identity (handle, name,
version) and every extension surface and function the app contributes.

At install time the platform writes the manifest into
`extensions/{domainSlug}/{appHandle}/app.json` per merchant.
At render time the storefront, checkout, admin, and customer-account
hosts read this file (through the local-manifest cache, 60s TTL) to
discover which extensions and functions to load. If `app.json` is
missing or unparseable, the app is silently skipped.

<Note>
  All keys under `extensions` are **camelCase**. (Directories under
  `extensions/{domainSlug}/` whose name starts with `_` are skipped at
  manifest read time — but manifest *fields* are not filtered by prefix.)
</Note>

## Top-level fields

<ParamField path="handle" type="string" required>
  Lowercase machine name for the app, e.g. `product-reviews-pro`. Must
  match the directory name under `extensions/{domainSlug}/`.
  Uniqueness is enforced per merchant.
</ParamField>

<ParamField path="name" type="string" required>
  Human-readable name shown in the merchant admin and the marketplace
  listing.
</ParamField>

<ParamField path="version" type="string">
  Semver version string (e.g. `1.4.2`). Used by the versioning system to
  reconcile installed vs. published builds.
</ParamField>

<ParamField path="description" type="string">
  Short marketing copy displayed on the marketplace card and admin
  detail page.
</ParamField>

<ParamField path="iconUrl" type="string">
  Absolute URL to a square icon (PNG/SVG, recommended 256×256). Used by
  the customizer block picker and admin sidebar.
</ParamField>

<ParamField path="developer" type="string">
  Publisher name displayed on the marketplace listing.
</ParamField>

<ParamField path="developerUrl" type="string">
  Public website URL for the publisher.
</ParamField>

<ParamField path="appUrl" type="string">
  Base URL the merchant is redirected to after install completes (the
  app's embedded admin entry point).
</ParamField>

<ParamField path="permissions" type="string[]">
  OAuth scopes the app requires (e.g. `read_products`, `write_orders`).
  See [OAuth scopes](/getting-started/authentication#scopes).
</ParamField>

<ParamField path="extensions" type="object">
  Container for every extension/function the app contributes. See the
  per-key tables below.
</ParamField>

### Minimal example

```json theme={null}
{
  "handle": "my-app",
  "name": "My App",
  "version": "1.0.0",
  "extensions": {}
}
```

## Extensions

The `extensions` object groups every extensible surface the app
contributes. Every key is optional; an app can use as few or as many as
it needs.

| Key | Type | Purpose | Page |
| - | - | - | - |
| `storefrontBlocks` | array | Liquid blocks merchants drop into theme sections (marketplace metadata) | [Theme Blocks](/extensions/theme-blocks) |
| `appBlocks` | array | **Delivery** form of the above — the URLs the installer fetches block files from. Required for externally-hosted apps | [appBlocks](#appblocks) |
| `appEmbeds` | array | Delivery form for snippets (source URL in `inlineHtml`) | [appBlocks](#appblocks) |
| `storefrontSnippets` | array | Reusable Liquid includes (`{% render 'foo' %}`) | [Storefront Snippets](/extensions/storefront-snippets) |
| `storefrontEmbeds` | array | Overlay/floating scripts injected at `<head>` or before `</body>` | [Storefront Embeds](/extensions/storefront-embeds) |
| `appScripts` | array | JS files auto-injected on every storefront page | [App Scripts](/extensions/app-scripts) |
| `checkoutExtensions` | array | Sandboxed iframe at a named slot inside the checkout | [Checkout UI](/extensions/checkout-ui) |
| `postPurchaseExtensions` | array | Iframe rendered on the order status (thank-you) page right after checkout | [Post-Purchase](/extensions/post-purchase) |
| `adminExtensions` | array | Iframe embedded on admin resource detail pages | [Admin Blocks](/extensions/admin-blocks) |
| `adminActions` | array | Button on an admin resource page that opens a modal iframe (install-only — persisted as per-handle schema files) | [Admin Actions](/extensions/admin-actions) |
| `printActions` | array | Button that opens a printable view (install-only — persisted as per-handle schema files) | [Admin Print Actions](/extensions/admin-print-actions) |
| `customerAccountExtensions` | array | Iframe blocks on the new customer-account pages | [Customer Account](/extensions/customer-account) |
| `webPixels` | array | Sandboxed customer-event listener scripts | [Web Pixels](/extensions/web-pixel) |
| `posExtensions` | array | Iframe extensions for the Point-of-Sale app | [POS Extensions](/extensions/pos-extensions) |
| `functions` | array | Declarative WASM functions (cart transform, discount, shipping, etc.) | [Functions Overview](/functions/overview) |
| `orderRoutingRules` | array | JSON matchers that pick a fulfillment location at order placement | [Order Routing](/extensions/order-routing) |
| `emailTemplates` | array | Override transactional emails for named events (install-only) | [Email Templates](/extensions/email-templates) |
| `webhooks` | array | Webhook subscriptions — each entry is `{ topic, endpoint }` | [Webhooks](/api-reference/webhooks/overview) |

<Note>
  `adminActions`, `printActions`, and `emailTemplates` are accepted by
  the install endpoint but are **not** stored inline in `app.json`
  after install — they are persisted as per-handle schema files under
  `admin-actions/`, `print-actions/`, and `email-templates/`
  respectively, and surfaced through the `admin-extensions` API.
</Note>

### storefrontBlocks

Liquid blocks merchants insert into theme sections via the customizer.
Each entry produces a `blocks/{handle}.aqua` template and a
`blocks/{handle}.schema.json` block schema.

<Warning>
  **Externally-hosted apps must also declare `appBlocks`.**

  `storefrontBlocks` carries the marketplace-facing metadata. The install
  fan-out that actually writes block files to the merchant's store reads
  **`appBlocks`** (and `appEmbeds` for snippets) — it fetches each entry's
  `template` and `schema` over HTTP and writes them to
  `public/extensions/{shop}/{app}/blocks/`.

  Apps whose files live in the platform's own marketplace directory are
  copied from disk, so `storefrontBlocks` alone is enough for them. An app
  served from your own domain that declares only `storefrontBlocks` installs
  successfully and ships an **empty** `blocks/` directory — the block exists
  in your repo and nowhere a merchant can reach it, with no error anywhere.

  See [appBlocks](#appblocks) below.
</Warning>

```json theme={null}
"storefrontBlocks": [
  {
    "handle": "product-reviews",
    "title": "Product Reviews",
    "target": "product",
    "schema": {
      "name": "Product Reviews",
      "settings": [
        { "id": "limit", "type": "range", "min": 1, "max": 50, "default": 10, "label": "Reviews per page" }
      ]
    }
  }
]
```

| Field | Type | Purpose |
| - | - | - |
| `handle` | string | Required. Block machine name. Becomes the filename (`blocks/{handle}.aqua`). |
| `title` | string | Display label in the customizer block picker. |
| `target` | string | Template the block can be inserted into (`product`, `index`, `collection`, …). |
| `schema` | object | Theme-block schema (`name`, `settings[]`, `presets[]`). |

See [Theme Blocks](/extensions/theme-blocks) for the full block schema
spec, supported setting types, and target reference.

### appBlocks

The **delivery** form of a storefront block, for apps served from your own
domain. Where `storefrontBlocks` describes the block for the marketplace,
`appBlocks` tells the platform where to fetch the actual files at install
time. Declare both.

Each entry's `template` and `schema` are fetched over HTTP during install and
written into the merchant's `public/extensions/{shop}/{app}/blocks/`
directory, so your app must serve them publicly (no auth) at stable URLs.

```json theme={null}
"appBlocks": [
  {
    "handle": "product-reviews",
    "title": "Product Reviews",
    "target": "product",
    "template": "https://your-app.example.com/blocks/product-reviews.aqua",
    "schema": "https://your-app.example.com/blocks/product-reviews.schema.json"
  }
]
```

| Field | Type | Purpose |
| - | - | - |
| `handle` | string | Required. Must match the `storefrontBlocks` handle. Becomes `blocks/{handle}.aqua`. |
| `target` | string | Template the block may be placed on — `product`, `index`, `collection`, … |
| `template` | string | Absolute URL to the `.aqua` source. Fetched at install; also accepts inline Liquid. |
| `schema` | string | Absolute URL to the block's `.schema.json`. Also accepts an inline object. |

Snippets use the same pattern under `appEmbeds`, with the source URL in
`inlineHtml`:

```json theme={null}
"appEmbeds": [
  { "handle": "review-stars", "title": "Star rating", "inlineHtml": "https://your-app.example.com/snippets/review-stars.aqua" }
]
```

<Note>
  Block templates use plain `<style>` and `<script>` tags, **not** the
  `{% stylesheet %}` / `{% javascript %}` tags a theme uses. Those two tags
  are a theme-bundling feature: the theme installer concatenates them into
  the theme's asset bundles, and nothing performs that step for app blocks —
  so their contents render to the page as visible text and your CSS and JS
  never run.
</Note>

<Note>
  Merchants place app blocks inside an `apps` section. That section type is
  provided by the platform, so it works on every theme whether or not the
  theme author shipped a `sections/apps.aqua` of their own (a theme's own
  file always takes precedence).
</Note>

### storefrontSnippets

Reusable Liquid partials any theme block or section can `{% render %}`.
Snippet handles share a single global namespace across all installed
apps — the install endpoint logs a warning when two apps register the
same handle and the filesystem-order winner is unpredictable. Prefix
your handles with the app handle to be safe.

```json theme={null}
"storefrontSnippets": [
  { "handle": "my-app-stars", "description": "Renders a 5-star rating" }
]
```

| Field | Type | Purpose |
| - | - | - |
| `handle` | string | Required. `{% render 'my-app-stars' %}` invokes it. |
| `description` | string | Optional documentation displayed in the admin. |

### storefrontEmbeds

Overlay/floating scripts (chat widgets, cookie banners, A/B tools)
injected at `<head>`, `body`, or `compliance_head` based on the
`target` field. Merchants toggle each embed on/off in the customizer
"App embeds" tray.

```json theme={null}
"storefrontEmbeds": [
  {
    "handle": "review-popup",
    "title": "Review Popup",
    "target": "body",
    "scriptSrc": "https://my-app.example.com/embed.js",
    "schema": { "name": "Review Popup", "settings": [] }
  }
]
```

| Field | Type | Purpose |
| - | - | - |
| `handle` | string | Required. Unique per app. |
| `target` | string | `head` \| `body` \| `compliance_head`. Default `body`. |
| `scriptSrc` | string | Absolute URL to a JS file (always emitted as `<script async>`). |
| `inlineHtml` | string | Alternative to `scriptSrc` — raw HTML/CSS injected directly. |
| `enabled_on` | object | Restrict to specific templates/section groups: `{ templates: [], groups: [] }`. |
| `disabled_on` | object | Same shape as `enabled_on`; exclusions win on conflict. |
| `schema` | object | Settings schema for the customizer's "App embeds" panel. |

See [Storefront Embeds](/extensions/storefront-embeds) for full
schema and rendering behaviour.

### appScripts

JavaScript files that the platform auto-injects on every storefront
page render. Useful for installing pixels and chat widgets without
editing the theme. The `loadStrategy` controls how the `<script>` tag
is emitted: `async` (independent) or `defer` (preserves order, runs after
DOM). The loader itself defers injection to `requestIdleCallback`, so
scripts are already low-priority.

```json theme={null}
"appScripts": [
  {
    "handle": "loader",
    "src": "https://my-app.example.com/loader.js",
    "loadStrategy": "defer",
    "position": "head",
    "config": { "shopId": "auto" }
  }
]
```

| Field | Type | Purpose |
| - | - | - |
| `src` | string | Required. Absolute URL to the script. |
| `loadStrategy` | string | `async` \| `defer`. Defaults to `defer`. |
| `position` | string | `head` \| `body`. Default `body`. |
| `config` | object | Free-form config surfaced in the extensions API response; not injected onto the script tag. |

See [App Scripts](/extensions/app-scripts).

### checkoutExtensions

Sandboxed iframes injected at named slots inside the checkout.
The host communicates with the iframe through
[App Bridge](/app-bridge/overview) over `postMessage`.

```json theme={null}
"checkoutExtensions": [
  {
    "handle": "trust-badges",
    "target": "checkout-payment-after",
    "iframeUrl": "https://my-app.example.com/checkout/trust-badges.html",
    "settings": { "showCount": 4 }
  }
]
```

| Field | Type | Purpose |
| - | - | - |
| `handle` | string | Required. Unique per app. |
| `target` | string | Required. Named slot — see [Checkout UI targets](/extensions/checkout-ui#targets). |
| `iframeUrl` | string | URL loaded into the sandboxed iframe. |
| `settings` | object | Stored on the manifest but **not** forwarded to the iframe. Encode config into `iframeUrl` yourself — see [Checkout UI](/extensions/checkout-ui#reading-settings-from-appjson). |

### postPurchaseExtensions

Iframe rendered on the order status (thank-you) page right after checkout (e.g. one-click upsells).
Smaller wired-action set than checkout — focused on order summary +
upsell purchase.

```json theme={null}
"postPurchaseExtensions": [
  {
    "handle": "upsell",
    "target": "post-purchase",
    "iframeUrl": "https://my-app.example.com/postpurchase.html",
    "timeout": 60000
  }
]
```

| Field | Type | Purpose |
| - | - | - |
| `handle` | string | Required. |
| `target` | string | Default `post-purchase`. |
| `iframeUrl` | string | Required. Iframe entry URL. |
| `timeout` | number | Accepted by the manifest API but **not enforced** by the current host — the extension stays mounted until the customer dispatches `DONE`/`REDIRECT` or navigates away. |

### adminExtensions

Iframes embedded inline on admin resource detail pages — product,
order, customer, collection, discount, etc. The host
(`<AdminExtensionSlot target="…" />`) fetches all extensions for a
target and renders each in a sandboxed iframe.

```json theme={null}
"adminExtensions": [
  {
    "handle": "reviews-panel",
    "target": "admin.product-details.block.render",
    "title": "Reviews",
    "url": "https://my-app.example.com/admin/reviews"
  }
]
```

| Field | Type | Purpose |
| - | - | - |
| `handle` | string | Required. |
| `target` | string | Required. See [Admin Block targets](/extensions/admin-blocks#targets). |
| `title` | string | Displayed in the admin panel header. |
| `url` | string | Iframe URL (relative URLs are absolutized to the LaunchMyStore origin). |
| `iconUrl` | string | Optional icon for the panel header. |
| `navigationLabel` | string | Sidebar/tab label override. |
| `permissions` | object | Per-target permission flags consumed by the host. |

### adminActions

Buttons on admin pages that open a modal iframe on click. Declared in
the manifest but persisted to disk as `admin-actions/{handle}.schema.json`
during install — the admin-extensions API merges them with
`adminExtensions` and tags them with `type: 'admin_action'`.

```json theme={null}
"adminActions": [
  {
    "handle": "refund-tool",
    "target": "admin.order-details.action.render",
    "title": "Issue partial refund",
    "appUrl": "https://my-app.example.com/admin/refund",
    "icon": "https://my-app.example.com/icons/refund.svg"
  }
]
```

### printActions

Buttons that open a printable view in a new window. Manifest carries
either a Liquid `template` (rendered server-side) or an `appUrl`
(iframe).

```json theme={null}
"printActions": [
  {
    "handle": "packing-slip",
    "target": "admin.order-details.print.render",
    "title": "Packing slip",
    "template": "{{ order.name }} — {{ order.line_items | size }} items"
  }
]
```

### customerAccountExtensions

Iframe blocks rendered on the new customer-account pages (order
status, dashboard, profile, order list). Same iframe contract as
checkout extensions — bearer-token gated.

```json theme={null}
"customerAccountExtensions": [
  {
    "handle": "review-prompt",
    "target": "customer-account.order-status.block.render",
    "iframeUrl": "https://my-app.example.com/account/review-prompt.html",
    "permissions": ["read_orders"]
  }
]
```

See [Customer Account](/extensions/customer-account) for the full
target list.

### webPixels

Sandboxed customer-event listener scripts loaded into an isolated
iframe. Subscribe to events like `page_viewed`, `cart_updated`,
`checkout_completed`. The manifest accepts either a remote `scriptSrc`
URL or an `inlineScript` string which the installer writes to
`web-pixels/{handle}.js`.

```json theme={null}
"webPixels": [
  {
    "handle": "review-prompt-pixel",
    "scriptSrc": "https://my-app.example.com/web-pixel.js",
    "events": ["checkout_completed"],
    "settings": { "delay": 5000 },
    "permissions": ["customer_email"]
  }
]
```

See [Web Pixels](/extensions/web-pixel).

### posExtensions

Iframe extensions for the Point-of-Sale app. Same shape as
`adminExtensions`; targets start with `pos.`. The admin-extensions API
tags these with `type: 'pos_extension'` so the POS host can tell them
apart from regular admin blocks.

```json theme={null}
"posExtensions": [
  {
    "handle": "loyalty-lookup",
    "target": "pos.checkout.block.render",
    "title": "Loyalty",
    "url": "https://my-app.example.com/pos/loyalty"
  }
]
```

See [POS Extensions](/extensions/pos-extensions).

### functions

Declarative WASM functions that fire in the cart → checkout →
order-placement pipeline. Each entry maps to one of the nine
[supported function types](/functions/overview#function-types). The
install endpoint validates `type`, `handle`, `inputFields`, and
`network_access` / `allowed_hosts` and rejects the install on any
violation.

```json theme={null}
"functions": [
  {
    "type": "discount",
    "handle": "tiered-discount",
    "title": "Tiered Volume Discount",
    "entrypoint": "dist/discount.wasm",
    "inputFields": {
      "cart": { "lines": { "id": true, "quantity": true, "merchandise": { "price": true } } }
    },
    "network_access": false
  }
]
```

| Field | Type | Purpose |
| - | - | - |
| `type` | string | Required. One of the [9 function types](/functions/overview#function-types). |
| `handle` | string | Required. Unique per app. |
| `title` | string | Display name shown to the merchant. |
| `entrypoint` | string | Relative path to the compiled WASM module (uploaded separately). |
| `inputFields` | object | Subtree projection of the REST input — see [Input fields](/functions/input-fields). |
| `network_access` | boolean | Opt in to outbound `fetch_url`. Requires `allowed_hosts`. |
| `allowed_hosts` | string\[] | Non-empty list of hostnames the function may call. Validated at install. |

<Warning>
  Function active-install caps apply per shop, per type — see
  [Function active limits](/functions/active-limits). Installs over the
  cap fail with HTTP 409.
</Warning>

### orderRoutingRules

Declarative matchers that select a fulfillment location at order
placement. Internally flattened into the `functions` dispatch list as
type `fulfillment_location_rule`.

```json theme={null}
"orderRoutingRules": [
  {
    "handle": "us-east-warehouse",
    "title": "Route US-East orders to NYC warehouse",
    "entrypoint": "rules/us-east.json"
  }
]
```

See [Order Routing](/extensions/order-routing).

### emailTemplates

Per-event transactional email overrides. Each entry replaces the
default template for one event (`order_confirmation`,
`shipping_confirmation`, etc.).

```json theme={null}
"emailTemplates": [
  {
    "event": "order_confirmation",
    "subject": "Thanks for your order, {{ customer.first_name }}!",
    "htmlBody": "<h1>Order {{ order.name }}</h1>...",
    "enabled": true
  }
]
```

<Note>
  The authoritative copy of the email template is stored by the
  platform — the install endpoint also deploys a read-only preview at
  `email-templates/{event}.json` in your extension bundle.
</Note>

See [Email Templates](/extensions/email-templates).

## Validation rules

The install endpoint (`POST /api/apps/install-extensions`) enforces
the following at install time. Failures return HTTP 400.

<AccordionGroup>
  <Accordion title="Handle uniqueness">
    Within a single `app.json`, every `handle` inside one extension
    array must be unique. Across apps, snippet handles (`storefrontSnippets`)
    share a global namespace per merchant — collisions are logged but
    do not fail the install.
  </Accordion>

  <Accordion title="Function manifest validation">
    Every entry under `functions` must have:

    * `type` ∈ `cart_transform`, `discount`, `shipping_rate`,
      `payment_customization`, `delivery_customization`,
      `order_validation`, `fulfillment_constraints`,
      `local_pickup_options`, `pickup_point_options`
    * A non-empty `handle` string
    * Valid `inputFields` (every leaf is `true`/`false`, internal nodes
      are plain objects) if present
    * When `network_access: true`, a non-empty `allowed_hosts` array of
      valid hostnames (no scheme, no path)
  </Accordion>

  <Accordion title="Function active limits">
    The backend `installApp()` checks the merchant's currently active
    apps for each function type. If installing this app would push the
    count over the per-type cap, install fails with HTTP 409. See
    [Function active limits](/functions/active-limits) for the per-type
    caps.
  </Accordion>

  <Accordion title="Target validation">
    `target` strings on `checkoutExtensions`, `adminExtensions`,
    `posExtensions`, `customerAccountExtensions`, and
    `adminActions`/`printActions` are not validated against a closed
    enum — but unknown targets simply won't be picked up by any host.
    Refer to the target lists on the relevant extension pages.
  </Accordion>

  <Accordion title="Snippet handle conflicts">
    Snippet handles share one Liquid resolver namespace across all apps
    on a merchant. Installation continues on conflict but logs a
    warning and render order is filesystem-dependent. Prefix snippet
    handles with the app handle to avoid clashes.
  </Accordion>

  <Accordion title="Reserved directory names">
    Directories under `extensions/{domainSlug}/` whose name
    starts with `_` (underscore) are ignored at manifest read time and
    can be used for scratch / debug data.
  </Accordion>
</AccordionGroup>

## Full example

```json theme={null}
{
  "handle": "product-reviews-pro",
  "name": "Product Reviews Pro",
  "version": "2.1.0",
  "description": "Verified product reviews with photo support.",
  "iconUrl": "https://reviews.example.com/icon.png",
  "developer": "Example Inc.",
  "developerUrl": "https://example.com",
  "appUrl": "https://reviews.example.com/admin",
  "permissions": ["read_products", "write_metafields"],
  "extensions": {
    "storefrontBlocks": [
      {
        "handle": "review-widget",
        "title": "Review Widget",
        "target": "product",
        "schema": {
          "name": "Reviews",
          "settings": [
            { "id": "limit", "type": "number", "default": 10, "label": "Reviews per page" }
          ]
        }
      }
    ],
    "storefrontSnippets": [
      { "handle": "reviews-pro-stars" }
    ],
    "appScripts": [
      { "src": "https://reviews.example.com/loader.js", "loadStrategy": "defer" }
    ],
    "adminExtensions": [
      {
        "handle": "reviews-panel",
        "target": "admin.product-details.block.render",
        "title": "Reviews",
        "url": "https://reviews.example.com/admin/reviews"
      }
    ],
    "webPixels": [
      { "handle": "review-prompt", "scriptSrc": "https://reviews.example.com/pixel.js", "events": ["checkout_completed"] }
    ],
    "functions": [
      {
        "type": "discount",
        "handle": "reviewer-discount",
        "title": "5% off for reviewers",
        "entrypoint": "dist/discount.wasm"
      }
    ],
    "emailTemplates": [
      {
        "event": "order_confirmation",
        "subject": "Don't forget to review your order!",
        "htmlBody": "<p>...</p>"
      }
    ]
  }
}
```

## See also

<CardGroup cols={2}>
  <Card title="Extensions Overview" icon="puzzle-piece" href="/extensions/overview">
    Every extension surface in one place.
  </Card>

  <Card title="Functions Overview" icon="bolt" href="/functions/overview">
    Declarative WASM functions for cart, shipping, discounts, validation.
  </Card>

  <Card title="App Types & Tiers" icon="layer-group" href="/getting-started/app-types-and-tiers">
    Public / private / first-party and the four pricing tiers.
  </Card>

  <Card title="App Lifecycle" icon="rotate" href="/getting-started/app-lifecycle">
    Install, update, uninstall, and the version reconciliation pipeline.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.