Add a fitting room to your store in two script tags.
Load one script, mount a button, and your shoppers can try any garment on their own photo — in a modal that opens right on your page, themed to your brand. No redirects, no SDK build step.
What you're integrating
The Trywear SDK (tryon.js) adds a
Try it on button next to your existing buttons. When a
shopper taps it, the studio opens in an overlay on your own page,
pre-loaded with that product. The shopper adds a photo, and the AI
renders them wearing it — garment and identity preserved.
It's built to feel like the rest of your store, not a third-party pop-up. Set your colours and font once in your Console under Brand and every shopper's studio wears them — see Theming.
You'll need an API key. Generate one in your Console under Integration. It's shown once — store it like any secret. The key identifies your account and, once you're live, locks the SDK to your authorized domains.
Three steps to your first try-on
Paste these into any product page. Point src
at wherever you host the studio, and use public image URLs for the
product.
<script> tag, loaded once per page.TryOn.init with your API key.TryOn.mount beside Add to Cart.<!-- 1 · Load the SDK -->
<script src="https://trywear.co.za/tryon.js"></script>
<!-- 2 · Initialise, then 3 · mount a button -->
<script>
TryOn.init({ apiKey: "tos_live_..." });
TryOn.mount({
anchor: "#add-to-cart", // element to sit beside
productId: "SKU-2481",
images: ["https://cdn.yourstore.com/tee.png"]
});
// Brand lives in your Console, under Brand — no theme option needed here.
</script>That's the whole integration. Everything below is optional polish — trying on a whole cart, pinning your brand, and locking down the key for production.
Product images must be public URLs. The render fetches them
server-side, so a localhost or
behind-login path won't load. Use your CDN or storage URLs — the same
ones your product page already shows.
How the handoff works
You never touch our API directly from the button — the SDK does it. Here's the full path a tap takes:
Try on the whole bag
Give the SDK a way to read the cart and it can style everything in it at
once. Register a getCart provider — the SDK
calls it fresh each time, so it always reflects the live cart — then mount
a single button in your cart drawer.
TryOn.init({
apiKey: "tos_live_...",
// Return your live cart. Tag each item with a category —
// it's how the SDK knows what can be tried on (see below).
getCart: () => store.cart.items.map(i => ({
productId: i.sku,
name: i.title,
category: i.category, // "Clothing", "Electronics"…
images: [i.image]
}))
});
// One button for the whole bag. Non-wearable items are skipped.
TryOn.mountCart({ anchor: "#cart-footer" });Prefer to pass the cart at click time instead of a provider? Call
TryOn.openCart({ cart: [...] }) from your own
handler — same filtering, no provider needed.
Only clothes reach the studio — and only clothes cost anything
A real cart has things you can't wear — a charger, a gift card, a mug. Filtering these out isn't just about a clean experience: the AI classifier is the only step that costs money, so the SDK is built to reject obvious non-garments before it's ever called.
1 · Category filter — runs in the browser, costs nothing
Each cart item carries a category (or
name). The SDK checks it against a standard
e-commerce taxonomy — the same top-level departments Shopify,
BigCommerce and Google Shopping already use — so most stores can pass
their existing category field straight through with no mapping work.
A match either way settles it instantly, with zero network call. Force
it explicitly any time with tryable: true /
false.
2 · AI classifier — the authoritative last check, called only when needed
Anything with no category, or one the taxonomy doesn't recognise, still reaches the studio — and is classified server-side the first time it's ever seen. That result is cached globally by image, so the same product is never classified twice, on your store or anyone else's. If the model decides it isn't wearable, it's dropped silently and the shopper sees a quiet “Skipped 1 item that can't be tried on.”
You don't have to tag perfectly. Tagging makes filtering instant and free; leaving an item untagged just costs one classification, ever. Either way, a non-garment never reaches the try-on — and a real garment is never wrongly hidden by an imperfect category.
What's in, what's out
The full list the client-side filter checks. Map your store's category
field to these where you can — an exact or partial match (e.g. your
"Men's Footwear" matches "footwear")
is enough.
✓ Wearable — reaches the studio
✗ Not wearable — rejected, no AI call
* A department being excluded doesn't hide a real garment inside it — a
product named "Team Jersey" under
"Sporting Goods" still matches the wearable
word list and is kept. The category check only settles items the name
doesn't already answer for.
Wear your brand
Your Console owns the studio's brand. Set it once under Brand — paste your store address into Match my website, or pick an accent by hand — check the live preview, and press Publish. Every shopper's studio picks it up from there; no code change, no redeploy. Publish nothing and nothing changes: the studio keeps its own look rather than guessing at yours.
The SDK does not read your page or try to detect your brand. It used to, and it was the wrong call: it picked whatever coloured button it found first, and on storefronts that paint through a canvas (Flutter, Unity, some SPAs) it found nothing at all — leaving retailers unable to tell a bad guess from a broken feature. Your brand is what you publish, and you see it before you publish it.
Want it pinned in code instead? Pass the tokens directly — per call, or
once on TryOn.init to cover every open:
TryOn.mount({
anchor: "#add-to-cart",
images: [product.image],
theme: {
brand: "#1F6FEB", // accent / buttons
ink: "#10151F", // text
paper: "#FFFFFF", // background
font: "Inter", // body font family
radius: 12 // corner rounding
}
});Accepts #RGB,
#RRGGBB, or rgb().
Any token you leave out falls back to the brand saved in your Console.
API reference
The global TryOn object, available as soon as
tryon.js loads.
Configure the SDK once per page.
- apiKey
- Your
tos_live_…key. - studioUrl ?
- Studio origin. Defaults to where
tryon.jswas loaded from. - getCart ?
- Function returning the live cart array — used by
mountCart. - theme ?
- Pin the brand in code for every open: a token object, or
"house"for the default studio. Omit it — the normal case — and the studio wears what you published in Console → Brand. - preboot ?
- Set
falseto stop the studio loading in the background while the shopper browses. On by default — it's what makes Try On open instantly. - verifyUrl ?
- Override the key-verification endpoint (self-host only).
Tell us a try-on converted. Call it from your own Add to Cart handler — we can't see that click, so without this your Analytics funnel stops at "generated a look" and the tried-but-not-bought list stays empty. Fire-and-forget: never blocks, never throws.
- productId
- The SKU that was added. Match the
productIdyou pass tomountso the two line up in your reports.
Insert a Try-On button after an anchor element, for one product.
- anchor
- CSS selector or element to place the button beside.
- images
- Array of public product image URLs.
- productId ?
- Your SKU, passed through for reference.
- theme ?
- Override this open's brand: a token object, or
"house". Omit to use your published Console brand. - label · color · radius ?
- Style the button itself.
Insert a “Try on your bag” button that styles every wearable item in the cart.
- anchor
- Where the button goes (e.g. your cart drawer).
- cart ?
- Explicit cart array. Omit to use the
getCartprovider. - theme · label ?
- As above.
Open the studio from your own click handler, without mounting a button.
open takes one product's images;
openCart takes a cart (or uses the provider) and filters it.
Close the studio overlay programmatically.
How the key is protected
Your key is created server-side and only ever shown once — we store just a SHA-256 hash of it, so a database leak can never expose a working key. You can regenerate or revoke it anytime from the Console.
The SDK verifies the key on init against a public endpoint that checks it against your authorized domains and returns your saved brand. It returns no secrets, so it's safe to call from the browser:
curl -X POST https://us-central1-ai-try-on-be537.cloudfunctions.net/verifyKey \
-H "Content-Type: application/json" \
-d '{ "apiKey": "tos_live_...", "origin": "https://yourstore.com" }'
# → { "valid": true, "retailer": "acme", "brand": { ... } }Domain enforcement is live
Authorization is checked twice, and the second check is the one that
matters. verifyKey runs at init so a
misconfigured integration is obvious in your console during
development — but it's cosmetic, and anyone can skip it. The
enforced gate runs inside staticTryOn
and requestMotion: the calls that actually
spend money. Every embedded render carries your key plus the host
page's origin, and both must pass before a single token is billed.
The policy, exactly:
- ✓Your storefront must be on the list. A domain matches itself and its subdomains (
shop.you.compasses underyou.com). - ✓An empty list authorizes nothing. There is no "unset means allow" — if you save no domains, no storefront renders. Add your domains before you ship.
- ✓Removing a domain takes effect immediately. The next GENERATE from that storefront is refused. No cache, no propagation delay.
- ✓App Check runs underneath all of it. Every function requires a valid device-attestation token, so scripted callers never reach the gate in the first place.
localhost always works — zero setup.
localhost and 127.0.0.1
skip the domain list entirely, so your developers can integrate and test before
any domain is saved. Enforcement applies to real storefront origins.
What a shopper sees when a store isn't authorized
Nothing about your configuration. A shopper on a blocked storefront cannot fix the problem and has no business knowing which lock is open, so the studio tells them only that something went wrong on the store's side, that it isn't their photo, and gives them a short reference code:
TRY-ON IS UNAVAILABLE HERE
This store's try-on needs attention — nothing to do with your photo.
REF A3F91C [ REPORT THIS ]
The real cause — unknown key, unauthorized origin, empty domain list — is written to our logs under that same code. Set a Support contact on your Integration page and REPORT THIS mails your inbox with the code attached; forward it to us and we can pinpoint the exact request in seconds. Leave it blank and the shopper simply gets the code.
You still get the full diagnosis — from verifyKey.
The init handshake returns the precise reason ("origin x not authorized",
"no authorized domains saved"), so a misconfigured integration is obvious
in your console at development time. Only the shopper-facing render error
is generic.
Mobile apps
A native app has no origin to check — so what replaces the domain? Its signed application identity. A web page proves where it is; an app proves what it is, and the operating system does the proving. That's a stronger guarantee than an origin string, not a weaker one: an origin is asserted by the page, whereas an app's identity is attested by Google or Apple and signed.
The shape it takes:
- ✓Authorized Origins is one list — and it's live in the Console today. The card holds web domains (
yourstore.com) and app IDs (com.yourstore.shop) side by side. You add an app exactly the way you add a domain, and removing it cuts that app off exactly the same way. You can register your app IDs now, ahead of the SDK. - ✓The OS attests the app. Play Integrity on Android, App Attest / DeviceCheck on iOS, both through Firebase App Check — which our backend already enforces on every function today. Your app registers its package name and signing certificate (Android) or bundle ID (iOS).
- ✓The gate doesn't change. The render path already matches a caller's origin against your list; for an app it matches the attested app ID instead. Same list, same enforcement, same immediate revocation.
- ✓WebView embeds are covered too. An app that hosts the existing web SDK in a WebView passes its app identifier through the same attestation, so it authorizes as an app rather than as a spoofable page origin.
The mobile SDK itself isn't released yet — but the authorization side is already real: the Console's Authorized Origins card accepts app IDs today, and the backend needs no new architecture because App Check is already the gate on every call. When the SDK lands, your registered app IDs simply start being enforced. Building a native app against Trywear? Tell us — we'll size it with you.
Going live
- ✓Host
tryon.jsand the studio. Point the scriptsrcat that host; the SDK infersstudioUrlfrom it. - ✓Generate your API key in Console → Integration, and drop it into
TryOn.init. - ✓Add your storefront domains — enforcement is live, and a key with an empty list renders nothing. See Domain enforcement.
- ✓Set a support contact so a shopper who ever hits a blocked store can report it to you in one tap.
- ✓Use public product image URLs — the same CDN URLs your store already serves.
- ✓Tag cart items with a category for instant, free wearable filtering.