# Cabina — installing the virtual try-on widget on any store

Written for an AI agent configuring a merchant's store. It states the complete
integration contract. Human version, with screenshots: https://cabina.io/docs/en

## The whole integration, in one tag

```html
<script
  src="https://www.cabina.io/widget.iife.js"
  crossorigin="anonymous"
  async
  data-api-key="cab_live_YOUR_API_KEY">
</script>
```

Put it anywhere inside `<head>`, or immediately before `</body>`. Both work:
the script is `async` and waits for `DOMContentLoaded` itself.

Add nothing else. In particular there is **no** `data-cabina` attribute, **no**
`data-product-id`, and no button for the merchant to write: Cabina injects its
own. Older documentation said otherwise; it was wrong.

Do **not** add an `integrity` attribute either. The bundle URL is not
versioned, so a pinned hash would break the widget silently at the next release.

## Two things must be true before the button appears

1. **An API key**, generated at `https://www.cabina.io/dashboard/install`. It starts with
   `cab_live_`. It is public by design — it lives in the page source. Do not
   treat it as a secret and do not try to proxy it server-side.
2. **The store origin is authorised**, at `https://www.cabina.io/dashboard/widget`.
   With an empty list the API returns **no CORS headers**, the browser drops the
   response, and the widget disappears **without an error message**.
   `https://shop.com` and `https://www.shop.com` are distinct origins: add both
   if both serve the storefront. Only `https` is accepted, plus
   `http://localhost[:port]` for development. Maximum 50 origins.

If you cannot complete step 2, stop and tell the merchant. Everything else you
do will look installed and be inert.

## Where to paste it, per platform

### Shopify

1. In your Shopify admin, go to Online Store → Themes.
2. On your live theme, open the ⋯ menu and choose Edit code.
3. Open layout/theme.liquid.
4. Paste the snippet just before the closing </head> tag.
5. Save. The change is live immediately — there is no theme to publish.

> Shopify emits og:image on every product page, so the garment image is found automatically.

### WooCommerce

1. In WordPress, install a code-snippet plugin (WPCode, Code Snippets) — or use your child theme.
2. Create a snippet that runs in the site header and paste the tag there.
3. Child theme instead? Add it to functions.php on the wp_head hook.
4. Save and open a product page.

> WooCommerce does not emit og:image without an SEO plugin. Cabina falls back to the gallery image — see “The garment image”.

### Wix

1. In your Wix dashboard, go to Settings → Custom Code.
2. Click Add Custom Code, paste the tag, and name it “Cabina”.
3. Under “Add Code to Pages” choose All pages, and place it in the Head.
4. Apply, then Publish the site — on Wix nothing is live until you publish.

> Wix renders product pages from its own template: if the button lands at the bottom of the page, use data-cabina-target.

### PrestaShop

1. Go to Design → Theme & Logo, then open your theme files (or use FTP).
2. Open templates/_partials/head.tpl in your active theme.
3. Paste the tag before the closing {/block} of the head block.
4. Save, then clear the cache under Advanced Parameters → Performance.

### Any other theme

1. Find the template that produces the <head> of every page — the filename changes with the platform.
2. Paste the tag anywhere inside <head>, or immediately before </body>. Both work: the script is async and waits for the DOM itself.
3. Paste it once. A second copy stops itself and warns in the console, but it is still worth removing.
4. Reload a product page.

> Cabina needs no template variable, no product ID and no extra markup. One script tag is the whole integration.

## Where the button lands

The widget injects `<button data-cabina-widget-btn>` right **after** the first
element it finds, in this order:

| Selector | Typical platform |
|---|---|
| `.product-form__buttons` | Shopify |
| `form[action*="/cart/add"] .product-form__submit` | Shopify |
| `form[action*="/cart/add"]` | Shopify |
| `form.cart` | WooCommerce |
| `.single_add_to_cart_button` | WooCommerce |
| `.product-add-to-cart` | PrestaShop |
| `#add-to-cart-or-refresh` | PrestaShop |

If none of them matches, the button is appended to `document.body` — a strip at
the bottom of the page, below the fold. Nothing is broken; it is simply in the
wrong place, and on a custom theme this is the most common outcome.

To choose the position yourself, put an empty element carrying
`data-cabina-target` in the product template:

```html
<div data-cabina-target></div>
```

That attribute **overrides every anchor above**, and the button is placed
**inside** that element, not next to it.

## Where the garment image comes from

Cabina reads the garment from the product page itself, in this order:

1. `<meta property="og:image">`
2. `<meta name="twitter:image">`
3. the first `<img>` matching one of these selectors:

| Selector | Typical platform |
|---|---|
| `.woocommerce-product-gallery__image img` | WooCommerce |
| `img.wp-post-image` | WooCommerce |
| `.wp-block-woocommerce-product-image img` | WooCommerce |
| `.product__image img` | Shopify |
| `.product-image img` | Shopify |
| `.product-photo img` | Shopify |
| `.product-single__photo img` | Shopify |
| `[data-product-image] img` | Shopify |
| `.product-featured-image img` | Shopify |
| `.product-gallery img` | Shopify |
| `.product-img img` | Shopify |
| `.product__media img` | Shopify |
| `.main-product-image img` | Shopify |

URLs are resolved against the page URL, and `http://` is promoted to
`https://` (Shopify emits `og:image` over http). Images whose URL contains
`placeholder`, `icon-`, `logo`, `spacer`, `1x1`, `pixel`, `blank`,
`no-image` or `noimage` are skipped.

If none of the three yields an image the button still appears and the try-on
fails partway through. The fix is to emit an Open Graph image:

```html
<meta property="og:image" content="https://your-store.com/images/product.jpg">
```

Shopify does this out of the box. WooCommerce does not, unless an SEO plugin
(Yoast, RankMath) is active — installing one is usually the shortest fix, and it
improves Facebook and WhatsApp previews too.

## Decision tree — the button does not appear

Check in this order, and stop at the first that is false.

1. Is the `<script>` tag in the **served** HTML? Use `view-source`, not the
   theme editor — some builders strip tags they do not recognise.
2. Console shows `[widget] error=WIDGET_CONFIG_MISSING`?
   → `data-api-key` is missing or empty on the tag.
3. Console shows a CORS error naming `www.cabina.io`?
   → the origin is not authorised. Go add it. This is the most likely cause.
4. Console shows `[widget] warn=GIA_CARICATO`?
   → the snippet is on the page twice. Remove one.
5. Nothing in the console at all, and still no button?
   → the plan's sessions or the credits are exhausted. Check
   `https://www.cabina.io/dashboard/billing`. That state is silent by design: a
   merchant's storefront must keep working when Cabina cannot.

## Symptom table

| Symptom | Console | Cause and fix |
|---|---|---|
| No button anywhere on the product page | `a CORS error from your browser, naming www.cabina.io` | Your store domain is not on the authorised list, so the API answers without CORS headers and the browser drops the response. Add the exact origin at https://www.cabina.io/dashboard/widget. https://shop.com and https://www.shop.com are two different origins — add both if both serve the store. |
| No button, and no CORS error either | `[widget] error=WIDGET_CONFIG_MISSING` | The API key is missing, or it was not read from the script tag. Check that data-api-key sits on the <script> tag itself and starts with cab_live_. |
| No button, nothing in the console at all | _(nothing)_ | Your plan has run out of sessions, or your credits are exhausted. The widget hides itself on purpose, so your store keeps working. Check https://www.cabina.io/dashboard/billing. |
| The button sits at the very bottom of the page | _(nothing)_ | None of the known anchors matched your theme, so the button was appended to <body>. Add <div data-cabina-target></div> where you want it. The button goes inside that element. |
| Two buttons, and sessions counted twice | `[widget] warn=GIA_CARICATO` | The snippet is on the page twice — often an old copy plus a plugin. Remove one of the two <script> tags. |
| The button opens, but the try-on fails partway | _(nothing)_ | Cabina could not find the garment image on the page. Add an og:image meta tag to your product template — see “The garment image”. |

## Verifying from a terminal

```bash
# 1. the tag is actually served
curl -s https://your-store.com/products/some-product | grep -o 'widget.iife.js'

# 2. the page exposes a garment image
curl -s https://your-store.com/products/some-product | grep -o 'og:image[^>]*'

# 3. the bundle is reachable
curl -sI https://www.cabina.io/widget.iife.js | head -1
```

The first two are the ones that fail. The third practically never does.

## What this widget does not need

- No product ID, no SKU, no catalogue sync for a basic install.
- No server-side code, no webhook, no call from the merchant's backend.
- No CSS. The button inherits `font-family` and takes its colour from the
  merchant's dashboard settings.
- No change to the cookie banner for the button itself: nothing leaves the
  device until the shopper uploads a photo and ticks the consent box on that
  screen.

## Beyond installation

Metrics, sessions, size tables and webhooks are a REST API keyed by
`X-Api-Key`; the OpenAPI description is at `https://www.cabina.io/api/v1`. There is also
an MCP server at `https://www.cabina.io/api/mcp` for configuring Cabina in natural
language. Both need an admin key (`cab_admin_`), which is **not** the widget key
and must stay secret.
