# Embeds: product cards, buy buttons and checkout links

> Sell from any site with a product card, a buy button or a checkout link, with events your page can listen to.

Source: https://shopfa.st/docs/guides/embeds

Build snippets in **Settings → Developers → Embeds**: pick a product, a type and the look, preview it live, copy.

Want checkout inside your page, in your own layout and brand? See [Elements](https://shopfa.st/docs/elements), and try them in the
[playground](https://shopfa.st/docs/elements/playground).

## Product card

An iframe with the cover, title, price, options and a buy button, sized to its content:

```html
<div data-shopfast-embed data-shopfast-product="amalfi-golden-hour-presets"
     data-shopfast-store="https://lumenfold.shopfa.st"
     data-shopfast-layout="compact"
     data-shopfast-cta="Get the presets"
     data-shopfast-accent="#6230fa"></div>
<script src="https://lumenfold.shopfa.st/_sf/buy-button.js" async></script>
```

| Attribute | Values |
| --- | --- |
| `data-shopfast-product` / `data-shopfast-bundle` | The product or bundle handle |
| `data-shopfast-store` | Your store's origin (defaults to where the script came from) |
| `data-shopfast-layout` | `card` (default), `compact`, `row` |
| `data-shopfast-cta` | Button label, up to 24 characters |
| `data-shopfast-accent` | `#rgb` or `#rrggbb` for the button |
| `data-shopfast-hide` | Comma list of `description`, `rating`, `cover` |
| `data-shopfast-width` | Max width in px (default 380) |

## Buy button

A link the loader turns into a styled button that opens checkout in a popup (a new tab when popups are blocked, and a
plain link without JavaScript):

```html
<a data-shopfast-product="amalfi-golden-hour-presets" data-shopfast-store="https://lumenfold.shopfa.st"
   data-shopfast-color="#6230fa">Buy now</a>
<script src="https://lumenfold.shopfa.st/_sf/buy-button.js" async></script>
```

Add `data-shopfast-style="none"` to keep your own styles, or `data-shopfast-mode="tab"` to open a tab instead of a popup.

## Checkout link

No script at all: `https://lumenfold.shopfa.st/cart?buy=amalfi-golden-hour-presets&qty=1` puts the product in the cart and
opens checkout. Add `&variant=var_…` for an option.

Checkout always happens on your store's own origin, where its cookies are first-party, so it works in every browser
and keeps buyers' sessions safe.

## Events

The loader re-dispatches what embeds post as DOM events on the embed element. They bubble, so one listener on
`document` catches every embed:

| Event | `detail` |
| --- | --- |
| `shopfast:ready` | `{ height }` once the card loaded |
| `shopfast:resize` | `{ height }` when the content height changes |
| `shopfast:checkout` | `{ handle, variant }` when the buyer starts checkout |

```js
document.addEventListener('shopfast:checkout', (e) => {
  analytics.track('Begin checkout', { product: e.detail.handle });
});
```

Messages from frames are only accepted from the origin the loader framed, and only from that frame.

## Allowed sites

By default any site can frame your product cards. List your own origins in **Allowed sites** to stop others from
framing them (the dashboard preview stays allowed). Buy buttons and checkout links are plain links and work anywhere.
The [embedded checkout](#embedded-checkout) is stricter: it opens only on sites in this list.

The same list decides where [Elements](https://shopfa.st/docs/elements) open. With an empty list, live Elements open only on localhost,
the docs playground and the dashboard.

## Embedded checkout

The whole checkout, in a modal on your page: the buyer never leaves your site. It is the store's real checkout (your
checkout settings, method order and rules, Fraud Shield, ShopFast Pay limits and the same fees), rendered compact and
framed only by your allowed sites.

```html
<button type="button" data-shopfast-checkout="amalfi-golden-hour-presets"
        data-shopfast-store="https://lumenfold.shopfa.st"
        data-shopfast-theme="dark" data-shopfast-accent="#6230fa">Buy now</button>
<script src="https://lumenfold.shopfa.st/_sf/checkout.js" async></script>
```

| Attribute | Values |
| --- | --- |
| `data-shopfast-checkout` | The product handle (`data-shopfast-product` works too) |
| `data-shopfast-variant` | An option id (`var_…`), optional |
| `data-shopfast-qty` | Quantity, 1 to 100 |
| `data-shopfast-store` | Your store's origin (defaults to where the script came from) |
| `data-shopfast-theme` | `light` or `dark`; leave it out to use your store's look |
| `data-shopfast-accent` | `#rrggbb` for buttons and focus |

A buy button from `buy-button.js` opens it too with `data-shopfast-mode="embed"` when `checkout.js` is on the page.

### From JavaScript

```js
const checkout = window.ShopFastCheckout.open({
  store: 'https://lumenfold.shopfa.st',
  items: [{ product: 'ironveil-pro', variant: 'var_…', quantity: 1 }],
  theme: 'dark',
  onSuccess: ({ sessionId, orderNumber, totalMinor, currency }) => { showThanks(orderNumber); },
  onClose: ({ reason }) => {},
  onError: ({ reason }) => {},
});
// checkout.close() closes it from your code.
```

The same moments bubble as DOM events from the button (or `document`): `shopfast:checkout:ready`,
`shopfast:checkout:success`, `shopfast:checkout:close` and `shopfast:checkout:error` (`not-allowed` when your site is
not in the list). A checkout Fraud Shield refuses shows its message inside the frame. Amounts are in minor units.

### How it pays

Card processors' hosted pages cannot be framed, so for them the frame opens the payment in a secure popup and waits;
it updates on its own when the payment confirms. Test payments (the sandbox and store test mode) are approved inside
the frame. Either way the order, its receipt email and its delivery are exactly those of a normal checkout.

### Security

- Only origins in **Allowed sites** (and the dashboard preview) may frame it. Each page sends
  `frame-ancestors <your site>`, and an unlisted site gets a short "not available here" page instead of the form.
- A framed page has no cookies, so the cart travels in signed form fields bound to your site's origin; copied to
  another site, they stop working. Tokens expire after two hours.
- Messages go only to your site's origin, and the loader accepts messages only from the store origin and from the frame
  it opened.
- Form posts are accepted only from the frame itself.
