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

# Storefront Embeds

> Overlay scripts and inline HTML injected at <head> or before </body>, opt-in per theme

# Storefront Embeds

Storefront embeds are **per-theme overlays** — floating widgets, analytics
snippets, consent banners — that the merchant turns on in the theme
customizer. Unlike [App Scripts](/extensions/app-scripts) which load on
every page automatically, embeds are off-by-default and require the
merchant to enable them per theme.

Use embeds when the merchant should control whether your overlay runs
(e.g. a cookie banner that conflicts with their existing consent tool),
or when settings differ per theme (light vs. dark variants).

## When to Use Embeds vs. App Scripts

| Need                                                         | Use                                          |
| ------------------------------------------------------------ | -------------------------------------------- |
| Per-theme on/off toggle in the customizer                    | Storefront embed                             |
| Settings (colour, position) merchant can change per theme    | Storefront embed                             |
| Inline HTML overlay (badge, ribbon, banner)                  | Storefront embed                             |
| Pixel or chat widget that should always be on once installed | [App Script](/extensions/app-scripts)        |
| Sandboxed customer-event listener                            | [Web Pixel](/extensions/web-pixel)           |
| Liquid block dropped into a theme section                    | [Storefront Block](/extensions/theme-blocks) |

## How Embeds Work

1. Declare embeds in `app.json` under `extensions.storefrontEmbeds`.
2. After install, embeds appear in the theme customizer under **App embeds**.
3. The merchant flips a toggle (and optionally edits per-theme settings).
   The state is persisted to `templates → config/settings_data.json` under
   `current.blocks["<appHandle>/<embedHandle>"]`.
4. On every storefront render, the platform's embed loader
   injects enabled embeds into the rendered HTML according to each entry's
   `target`.

Default: an embed with **no entry** in `settings_data.json` is **disabled**.
Merchants must opt in to each embed; nothing renders until they enable it
from the theme customizer.

## Manifest

```json theme={null}
{
  "handle": "my-popup-app",
  "name": "Welcome Popup",
  "version": "1.0.0",
  "extensions": {
    "storefrontEmbeds": [
      {
        "handle": "welcome-popup",
        "title": "Welcome popup",
        "target": "body",
        "inlineHtml": "<div class='welcome-modal'>Welcome — use code {{settings.code}}</div>",
        "schema": {
          "name": "Welcome popup",
          "settings": [
            { "type": "text",  "id": "code",  "label": "Coupon code", "default": "WELCOME10" },
            { "type": "color", "id": "accent", "label": "Accent",     "default": "#3B82F6" }
          ]
        },
        "enabled_on": { "templates": ["index", "product"] }
      }
    ]
  }
}
```

### Fields

| Field         | Type                                    | Default       | Description                                                                                                                                                           |
| ------------- | --------------------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `handle`      | string                                  | —             | Unique within your app. Combined with the app handle to form the embed key (`<appHandle>/<embedHandle>`). **Required.**                                               |
| `title`       | string                                  | `handle`      | Label shown in the theme customizer's app-embeds panel.                                                                                                               |
| `target`      | `"head" \| "body" \| "compliance_head"` | `"body"`      | Where the embed injects. `head` runs early, `body` ships just before `</body>`, `compliance_head` injects synchronously near the top of `<head>` for consent banners. |
| `inlineHtml`  | string                                  | —             | Raw HTML wrapped in a `<div data-lms-app-embed="…">…</div>`. Supports `{{settings.<id>}}` placeholders, escaped at render time.                                       |
| `scriptSrc`   | string                                  | —             | URL appended as `<script async src="…" data-config="…">`. Merchant settings are serialised onto `data-config` as JSON.                                                |
| `schema`      | object                                  | —             | Liquid-style schema (`{ name, settings }`) used by the customizer. Same input types as [theme block schemas](/extensions/theme-blocks#schema-settings).               |
| `enabled_on`  | object                                  | —             | `{ templates: ["index", "product"] }` — embed runs only on listed page types. Use `"*"` for all.                                                                      |
| `disabled_on` | object                                  | —             | `{ templates: ["404"] }` — embed runs everywhere except listed. Used when `enabled_on` is absent.                                                                     |
| `appId`       | string                                  | `<appHandle>` | Surfaced on the injected tag as `data-app-id`.                                                                                                                        |

You can declare both `inlineHtml` and `scriptSrc` on the same entry — both
tags will be injected.

## Template Filtering

`enabled_on.templates` and `disabled_on.templates` accept the same page
type strings the renderer uses:

* `index`, `product`, `collection`, `cart`, `page`, `blog`, `article`,
  `search`, `404`, `password`
* `customers/account`, `customers/login`, `customers/register`

Wildcard `*` matches every template (useful for "all pages except checkout").

```json theme={null}
"enabled_on": { "templates": ["product", "collection"] }
```

## Settings Substitution

For `inlineHtml`, `{{settings.<id>}}` is replaced with the merchant's
value at render time. Common picker shapes are coerced to strings:

| Setting type                                             | Substituted value           |
| -------------------------------------------------------- | --------------------------- |
| `text`, `textarea`, `select`, `radio`, `range`, `number` | The raw string/number       |
| `checkbox`                                               | `"true"` / `"false"`        |
| `color`                                                  | `#RRGGBB` hex               |
| `image_picker`, `file_picker`                            | The picker's `src` or `url` |

For `scriptSrc`, merchant settings are JSON-stringified onto a
`data-config` attribute on the script tag:

```html theme={null}
<script async src="https://my-app.example.com/embed.js"
        data-lms-app-embed="my-popup-app/welcome-popup"
        data-app-id="my-popup-app"
        data-config="{&quot;code&quot;:&quot;WELCOME10&quot;}"></script>
```

Read it back inside your embed script via:

```js theme={null}
const el = document.currentScript;
const cfg = JSON.parse(el.dataset.config || '{}');
```

## Inject Targets

| `target`          | Injection point                       | Use for                                                                           |
| ----------------- | ------------------------------------- | --------------------------------------------------------------------------------- |
| `head`            | Inside `<head>` after the loader stub | Standard async pixels and trackers                                                |
| `body`            | Just before `</body>`                 | Floating widgets, modals, chat bubbles                                            |
| `compliance_head` | Top of `<head>`, synchronous          | Consent banners, cookie blockers — runs before other scripts so it can block them |

## Verifying Installation

```bash theme={null}
# Manifest is surfaced
curl 'https://acme-store.launchmystore.io/api/apps/extensions?domainSlug=acme-store' \
  | jq '.embeds[] | select(.embedId == "my-popup-app/welcome-popup")'
```

To confirm the merchant has enabled it on the active theme, read the
theme's `config/settings_data.json` (via the theme file API or CLI) and
check `current.blocks["my-popup-app/welcome-popup"]`.

The settings\_data.json entry should look like:

```json theme={null}
{
  "type": "@app",
  "disabled": false,
  "settings": { "code": "WELCOME10", "accent": "#3B82F6" }
}
```

## Customizer State

Embed state is namespaced by the theme it was enabled on:

```
<theme>/config/settings_data.json
  ↳ current.blocks
    ↳ "<appHandle>/<embedHandle>"
      ↳ { type: "@app", disabled: false, settings: { ... } }
```

When a merchant duplicates a theme, the embed state copies with it — the
merchant can enable an embed on a staging theme without affecting
production.

## Canonical Example

The **Channels Hub** marketplace app is a full storefront-embed
example. It uses an embed (rather than an app script) so the
merchant can pick which chat provider runs per theme.

## See Also

* [App Scripts](/extensions/app-scripts) — auto-load on every page, no
  merchant toggle.
* [Theme Blocks](/extensions/theme-blocks) — Liquid blocks merchants drop
  into theme sections, not floating overlays.
* [Web Pixels](/extensions/web-pixel) — sandboxed customer-event listeners.
