> ## 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**. Fields prefixed with
  `_` (underscore) at any depth are reserved for tooling and ignored at
  install time.
</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                                                                 | [Theme Blocks](/extensions/theme-blocks)               |
| `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)         |

<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.

```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.

### 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 (loaded with `<script defer>`).                       |
| `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), `defer` (preserves order, runs after
DOM), or `idle` (lowest priority via `requestIdleCallback`).

```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` \| `idle`. Default `defer`.       |
| `position`     | string | `head` \| `body`. Default `body`.                    |
| `config`       | object | Free-form config passed via `data-config` attribute. |

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](/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 | Forwarded to the iframe via query string + App Bridge.                             |

### 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 | Maximum render time in ms before the iframe is replaced with a skip. Default `60000`. |

### 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": "product.details.block",
        "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>
