Skip to content

Wix

Wix gives you no server-side product template to edit, so the two snippets from Storefront integration can’t be pasted in as static HTML — every product page is the same page, and the bootstrap script needs a different productId on each one. The path that works is Velo: a custom element that renders the button in the page’s own DOM, and page code that tells it which product it’s showing.

1. Sync products, using a Wix identifier as external_id

Section titled “1. Sync products, using a Wix identifier as external_id”

Read your catalog with Wix Stores’ Query Products, then create a matching TryOn product for each one. Wix REST calls authenticate with an API key from the API Keys Manager plus your site id — note the key goes in Authorization bare, with no Bearer prefix, unlike TryOn’s:

Read the Wix catalog
curl -X POST https://www.wixapis.com/stores/v3/products/query \
-H "Authorization: WIX_API_KEY" \
-H "wix-site-id: WIX_SITE_ID" \
-H "Content-Type: application/json" \
-d '{"query": {"filter": {"visible": {"$eq": true}}, "cursorPaging": {"limit": 100}}}'
Create the TryOn product
curl -X POST https://api.integration.tryonvirtual.com/v1/products \
-H "Authorization: Bearer tryon_sk_your_key" \
-H "Content-Type: application/json" \
-d '{
"title": "Classic Fit Shirt — Red, XL",
"category": "clothes",
"external_id": "SHIRT-XL-RED",
"images": ["https://static.wixstatic.com/media/shirt-xl-red.jpg"]
}'

category is required and drives which AI model processes the product — map it from your Wix collections or product type, it won’t come across automatically. Once images finish processing and swap_ready turns true, PATCH the product to enable try-on exactly as on any other platform; see Product lifecycle for both calls.

For a one-time load you don’t have to script against the Wix API at all — export the catalog to CSV from the store dashboard and feed it through the bulk sync loop on the WooCommerce page.

Whatever you store as external_id has to be a value the product page can read back in step 4. Velo’s getProduct() hands you both sku and _id, so either works — with one wrinkle: in the V3 catalog, SKUs are a variant field, and Query Products doesn’t return variant data, so syncing by SKU means an extra per-product call to read it. Syncing by the Wix product id avoids that. Either way the invariant is the same: the value you store as external_id must be the exact value your page code reads back. See Using your own IDs.

public/custom-elements/tryon-button.js
const SHOP_SLUG = 'YOUR_SHOP_SLUG';
class TryOnButton extends HTMLElement {
static get observedAttributes() {
return ['product-id'];
}
attributeChangedCallback(name, previous, productId) {
if (name !== 'product-id' || !productId || productId === previous) return;
this.render(productId);
}
render(productId) {
// No shadow root on purpose — the button and the bootstrap script both
// belong in the page's own DOM.
this.innerHTML = '';
const button = document.createElement('button');
button.type = 'button';
button.textContent = 'Try On';
button.disabled = true; // until the bootstrap script has loaded
button.addEventListener('click', () => window.openTryOn());
this.appendChild(button);
// One bootstrap script per product. Wix moves between products without a
// full page load, so drop the previous one before loading the next.
this.script?.remove();
const script = document.createElement('script');
script.async = true;
script.src =
'https://widget.tryonvirtual.com/api/v1/tryon/scripts/embed/bootstrap.js' +
`?shop_slug=${SHOP_SLUG}&productId=${encodeURIComponent(productId)}`;
script.onload = () => {
button.disabled = false;
};
this.script = script;
document.head.appendChild(script);
}
}
customElements.define('tryon-button', TryOnButton);

Find YOUR_SHOP_SLUG in the panel under Settings → Shop.

Add it to the product page from the editor — Add Elements → Embed → Popular Embeds → Custom Element in the Wix Editor, Add Elements → Embed & Social → Custom Element in Wix Studio — then Choose Source → Velo file, pick the file, and enter tryon-button as the Tag Name. It has to match the string in customElements.define() exactly. (Wix’s own walkthrough is Add a Custom Element.)

Two details in that code are deliberate:

  • The click handler calls window.openTryOn() instead of using data-tryon-product-id. The automatic binding described in Storefront integration scans the page once, on DOMContentLoaded. This element renders later — after Velo has fetched the product and set the attribute — so its button was never scanned and would never be bound. Calling the global directly is the documented way out, the same one a client-routed app needs (see Custom storefront).
  • The old script tag is removed before a new one is added. Every bootstrap script defines the same window.openTryOn global, so a page carrying two of them offers try-on for whichever loaded last, not for both — try-on is one product per page.

Page code and the custom element both run in the visitor’s browser, so an API key put in either one is public — and a tryon_sk_ key can create and modify every product in your shop. Store it in the Wix Secrets Manager (dashboard → Settings → Secrets Manager) and do the readiness check in a backend web module instead:

backend/tryon.web.js
import { Permissions, webMethod } from 'wix-web-module';
import { getSecret } from 'wix-secrets-backend';
import { fetch } from 'wix-fetch';
export const isTryOnReady = webMethod(Permissions.Anyone, async (externalId) => {
const apiKey = await getSecret('tryon_api_key');
const response = await fetch(
'https://api.integration.tryonvirtual.com/v1/products' +
`?external_id=${encodeURIComponent(externalId)}`,
{ headers: { Authorization: `Bearer ${apiKey}` } }
);
if (!response.ok) return false;
const { products } = await response.json();
const product = products?.[0];
// Both gates, not either/or — see Storefront integration.
return Boolean(product && product.tryon_status === 'active' && product.swap_ready);
});

The lookup goes through ?external_id= rather than /v1/products/{id} because the value the page has on hand is your identifier, not TryOn’s UUID — REST paths only ever take the UUID (Using your own IDs). And both fields are checked because a product can be active with swap_ready: false, which opens an empty experience for the customer — why both gates matter.

As written this calls the TryOn API on every product page view, which is fine for a small catalog. For anything busier, mirror tryon_status and swap_ready into a Wix Data collection from a scheduled sync job and have this function read that instead — same reasoning as Storefront integration.

In Dev Mode, open the Product Page and add its page code:

Product Page — page code
import { isTryOnReady } from 'backend/tryon.web';
$w.onReady(async () => {
const product = await $w('#productPage1').getProduct();
const externalId = product.sku; // the value you stored as external_id in step 1
if (!externalId) return;
if (!(await isTryOnReady(externalId))) return;
// The element renders nothing until it receives this attribute, so a product
// that isn't ready simply never shows a button.
$w('#customElement1').setAttribute('product-id', externalId);
});

#productPage1 and #customElement1 are Wix’s default element ids — confirm both in the editor’s Properties panel, since a page with more than one of either will number them differently.

Because the readiness check gates the setAttribute() call, there’s no separate “only render when ready” step here: an unready product never gets a product-id, and the element renders nothing.

Wix’s Embed HTML element looks like a shortcut for pasting the two snippets, but it renders its contents inside a cross-origin sandboxed iframe, which breaks try-on twice over. The experience is confined to the embed box instead of covering the page, and webcam AR needs the iframe to carry allow="camera" — a Permissions Policy delegation the parent page has to grant, which the Wix editor gives you no way to set. The custom element above has neither problem: it runs in the page’s own document.

Custom storefront — the platform-agnostic version of the same integration, plus notes for single-page apps.