APEIRO Demo Store

Add try-on to your store

One script and one element on your product pages. Shoppers tap Try it on, add a photo, and see themselves in the item in a few seconds. Before you start, APEIRO sets up your brand and sends you your publishable key (pk_live_…). It only works on your own domains, so it is safe to put in your theme.

1. The embed

<script src="https://vto.apeiro.clothing/widget/v1/apeiro-vto.js" async></script>

<apeiro-tryon
  data-key="pk_live_…"
  data-product-id="SKU-123"
  data-product-name="Linen overshirt"
  data-garment-image="https://cdn.shopify.com/s/files/1/…/overshirt.jpg">
</apeiro-tryon>

The script is small, loads asynchronously, and makes no network requests until a shopper opens the try-on. If you'd rather pin a version, use the content-hashed file APEIRO gives you (/widget/v1/apeiro-vto.<hash>.js, cached for a year). The plain URL always serves the latest v1, cached for 5 minutes.

2. Attributes

AttributeRequiredWhat it is
data-keyYesYour publishable key, pk_live_…. Never your secret key (sk_live_…).
data-product-idYesYour product id or SKU (up to 200 characters). Used in reporting and events.
data-garment-imageYesThe product image to try on: a clear, front-facing photo of the garment. Must be https:// and on the image hosts APEIRO set up for you. Protocol-relative (//…) and relative URLs are made absolute automatically.
data-product-nameNoUsed in the popup copy ("See the Linen overshirt on you.").
data-button-labelNoButton text. Default "Try it on".

If a required attribute is missing or invalid, nothing is shown and a warning explains why in the browser console. Attributes are watched, so if your theme swaps the product image when a shopper picks a colour, update data-garment-image and the next try-on uses it:

// When the shopper picks another colour, point the widget at that variant's image.
document.querySelector('apeiro-tryon')
  .setAttribute('data-garment-image', 'https://…/overshirt-navy.jpg');

3. Look and feel

The widget uses your site's font by default and keeps its own styles inside a shadow root, so your theme can't break it and it can't affect your theme. Customise it with these CSS properties and the button part:

apeiro-tryon {
  --apeiro-accent: #1f3d2b;      /* buttons, checkbox */
  --apeiro-accent-text: #ffffff;
  --apeiro-radius: 4px;
  --apeiro-font: "Your Brand Sans", sans-serif;
}
apeiro-tryon::part(button) {     /* restyle the "Try it on" button fully */
  width: 100%;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

4. Events

The element dispatches these DOM events (they bubble, so you can listen on document). They never contain the shopper's photo, the result image or your key.

Eventevent.detailWhen
apeiro:open{ productId }The shopper opened the popup
apeiro:tryon-complete{ productId, tryonId, fromCache }A result is showing
apeiro:add-to-cart{ productId, tryonId }The shopper pressed "Add to cart" in the popup
apeiro:error{ productId, code }Something failed (code from our API, or network_error / poll_timeout)
apeiro:close{ productId, step }The popup closed (step: where the shopper was)

The widget never touches your cart. Listen for apeiro:add-to-cart and add the item your usual way (examples below). For analytics:

document.addEventListener('apeiro:tryon-complete', (e) => {
  // e.detail = { productId, tryonId, fromCache }
  window.dataLayer?.push({ event: 'apeiro_tryon_complete', ...e.detail });
});

5. Shopify

  1. Online Store → Themes → Customize.
  2. Open a product page template (Products → Default product).
  3. In the product information section, Add block → Custom Liquid, and place it under the Buy buttons.
  4. Paste this, with your key:
<script src="https://vto.apeiro.clothing/widget/v1/apeiro-vto.js" async></script>
<apeiro-tryon
  data-key="pk_live_…"
  data-product-id="{{ product.id }}"
  data-product-name="{{ product.title | escape }}"
  data-garment-image="https:{{ product.featured_image | image_url: width: 1600 }}">
</apeiro-tryon>
<script>
  // "Add to cart" in the try-on popup → press the theme's own Add to cart button,
  // so the selected variant and the theme's cart drawer behave as usual.
  document.addEventListener('apeiro:add-to-cart', function () {
    var btn = document.querySelector('form[action*="/cart/add"] [type="submit"]');
    if (btn) btn.click();
  });
</script>

image_url returns a protocol-relative URL (//your-store/cdn/shop/files/…); the https: in front makes it absolute. APEIRO adds both of Shopify's image hosts for your store (your-domain/cdn/shop/ and cdn.shopify.com/s/files/1/<shop-id>/). If you prefer the Cart AJAX API over clicking the theme's button:

document.addEventListener('apeiro:add-to-cart', async () => {
  const variantId = document.querySelector('form[action*="/cart/add"] [name="id"]')?.value;
  if (!variantId) return;
  await fetch(window.Shopify.routes.root + 'cart/add.js', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ items: [{ id: Number(variantId), quantity: 1 }] }),
  });
  window.location.href = window.Shopify.routes.root + 'cart';
});

6. WooCommerce

Add this to your child theme's functions.php (or a small plugin). It loads the script on product pages, puts the button in the product summary, and wires "Add to cart" to WooCommerce's own button so the selected variation is used.

<?php
// functions.php of a child theme, or a small plugin.
add_action('wp_enqueue_scripts', function () {
    if (is_product()) {
        wp_enqueue_script('apeiro-vto', 'https://vto.apeiro.clothing/widget/v1/apeiro-vto.js',
            [], null, ['strategy' => 'async', 'in_footer' => true]);
    }
});

add_action('woocommerce_single_product_summary', function () {
    global $product;
    $image = wp_get_attachment_image_url($product->get_image_id(), 'large');
    if (!$image) {
        return;
    }
    printf(
        '<apeiro-tryon data-key="%s" data-product-id="%s" data-product-name="%s" data-garment-image="%s"></apeiro-tryon>',
        esc_attr('pk_live_…'),
        esc_attr($product->get_sku() ?: (string) $product->get_id()),
        esc_attr($product->get_name()),
        esc_url($image)
    );
}, 35);

add_action('wp_footer', function () {
    if (!is_product()) {
        return;
    } ?>
    <script>
      document.addEventListener('apeiro:add-to-cart', function () {
        var btn = document.querySelector('form.cart .single_add_to_cart_button');
        if (btn) btn.click();
      });
    </script>
<?php });

7. Content-Security-Policy

If your site sends a CSP header, add these sources:

script-src  https://vto.apeiro.clothing https://challenges.cloudflare.com
frame-src   https://challenges.cloudflare.com
connect-src https://api.vto.apeiro.clothing
img-src     https://gulhkzwuhmyvuouuahln.supabase.co blob:

blob: is for the preview of the shopper's own photo. The widget's styles use constructable stylesheets, which need no style-src change in current browsers (Safari before 16.4 falls back to a <style> element, which needs style-src 'unsafe-inline'). Turnstile (Cloudflare's bot check) needs the challenges.cloudflare.com entries; see Cloudflare's CSP notes.

8. Privacy

Shoppers agree before their photo is used. Their photo is sent to APEIRO only to create the try-on and is deleted from our servers straight after; it stays in their browser tab (sessionStorage on your site) until they close the tab or tap "Forget my photo", so trying on the next product is one tap. Link to How we use your photo from your privacy policy if you like.

9. Checklist

Questions: support@apeiro.clothing