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
| Attribute | Required | What it is |
|---|---|---|
data-key | Yes | Your publishable key, pk_live_…. Never your secret key (sk_live_…). |
data-product-id | Yes | Your product id or SKU (up to 200 characters). Used in reporting and events. |
data-garment-image | Yes | The 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-name | No | Used in the popup copy ("See the Linen overshirt on you."). |
data-button-label | No | Button 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.
| Event | event.detail | When |
|---|---|---|
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
- Online Store → Themes → Customize.
- Open a product page template (Products → Default product).
- In the product information section, Add block → Custom Liquid, and place it under the Buy buttons.
- 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
- Your site's exact origins are on your key (
https://brand.co.zaandhttps://www.brand.co.zaare different). - Your product image host is on your image allowlist.
- Your domain is on APEIRO's Turnstile hostname list (APEIRO does this at onboarding).
- The button shows on a product page, the browser console has no
[apeiro-tryon]warnings, and a test try-on works.
Questions: support@apeiro.clothing