Widget reference¶
The Ziex Try-On widget is one script that adds a try-on window to a product page. It draws a launcher button, a window with the upload step, the progress and the result, and an optional "My looks" gallery. It talks to the Ziex Try-On API on its own; your page only configures it and listens to its events.
For a first working page see the HTML quickstart. If a CMS module loads the widget on demand, read Lazy loading and pickup. A store with a Content-Security-Policy needs the directives on the CSP page.
Add the widget¶
<script src="https://api.ziex-tryon.com/embed/tryon-embed.js"></script>
<script>
window.TryOn.init({ apiKey: 'pk_YOUR_PUBLISHABLE_KEY' });
</script>
- The script is one URL for every shop. It is versioned by
TryOn.version, not by the address. - Use the publishable key (
pk_…) on a public page. Never put a secret key (tryon_…) in page source: it also signs in to the client portal. A publishable key works only from the domain it is bound to. - A key that starts with
pk_test_(ortryon_test_) is a test key: the window shows a "TEST" badge in its header and a try-on is answered with a placeholder picture, with no charge. - The widget talks to the origin it was loaded from. Set
apiUrlonly if you serve the script from another address.
TryOn.init(options)¶
init() starts the widget. Only apiKey is required; without it the widget logs an error to the console and does
nothing. A repeated init() replaces the whole configuration, nothing carries over from the previous call (use
update() for a partial change). A try-on that has already been sent is the one exception: it
keeps the key, the API address and the product it was sent with.
Connection and product¶
| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
string | – (required) | The publishable key of your shop. |
apiUrl |
string | origin of the script's src |
Base URL of the API. A trailing slash is removed. |
productImage |
string | detected from the page | Image of the garment: an https:// URL or a data: URI. Detected from the page's product markup (JSON-LD Product, og:image, then the first large image) when omitted. |
productTitle |
string | detected from the page | Name of the product. Detected from JSON-LD, og:title, product microdata or the page's <h1> when omitted. |
productUrl |
string | the page's own address | Link to the product. It is stored with the try-on and shown to you next to the job. Tracking parameters are removed from the default. Only http(s) links are kept. |
productSku |
string | detected from the page | Product SKU. Accepted for compatibility; the window does not show it today. |
productPrice |
string | detected from the page | Product price, exactly as given. Accepted for compatibility; the window does not show it today. |
modelImage |
string | none | URL or data: URI of a ready model photo. The shopper can replace it with their own. With a publishable key the photo is always sent as an upload, so the URL must be readable cross-origin (CORS). |
category |
string | tops |
Garment category: tops, outerwear, bottoms, one-pieces or auto. The old value dresses is read as one-pieces. The shopper sees the categories your account supports. |
lockCategory |
boolean | false |
With a category given, hides the category choice and uses that category as is. |
mode |
string | quality |
quality or balanced. Any other value falls back to quality. The shopper is not asked. |
integration |
string | widget/<TryOn.version> |
Who drives the widget, as name/version (for example shop-theme/2.3). The name is lower-case, the whole value at most 64 characters. Anything else is replaced by the default. It is stored with the job so we can help you when something goes wrong. |
Look and behaviour¶
| Option | Type | Default | Description |
|---|---|---|---|
lang |
string | from <html lang> |
ru or en. ru when the page's language starts with ru, otherwise en. |
launcher |
boolean | true |
false draws no floating button and makes no request on init(); your page opens the window with TryOn.open(). |
launcherPosition |
string | right |
right or left. |
theme |
string | light |
light, dark or auto. auto follows the brightness of the page's body (then html) each time the window opens; an unreadable background counts as light. |
accent |
string | ink (#18181B) |
Any CSS colour. It colours the main button, the progress bar and the focus ring. The text colour on it is chosen for readable contrast. An invalid value is ignored. |
container |
string | body |
CSS selector of the element the widget is attached to. If nothing matches, body is used. |
cabinet |
boolean | false |
true turns on "My looks" and "Remember this photo": results and, only if the shopper ticks the box, their photo are kept in the shopper's browser (IndexedDB) for up to 30 days. With false the widget keeps nothing in the browser except the record of a try-on in progress (see A try-on outlives its window). |
debug |
boolean | false |
Writes diagnostic lines prefixed [TryOn] to the console. |
Time limits¶
All of these take a positive number of milliseconds (a numeric string is converted). Anything else is replaced by the
default in init() and ignored in update().
| Option | Default | What it bounds |
|---|---|---|
submitTimeoutMs |
30000 |
The wait for the answer to sending a try-on, and for the headers of the saved result image. |
pollTimeoutMs |
15000 |
Each status request. |
bodyTimeoutMs |
15000 |
Reading a small JSON answer after its headers have arrived. |
resultImageTimeoutMs |
120000 |
Downloading the result image for the "My looks" copy. |
attemptMaxMs |
2700000 (45 min) |
How long a sent try-on is followed, counted from the moment it was sent. After that the attempt ends with an error whose code is TIMEOUT. |
lookupGraceMs |
120000 (2 min) |
How long "not found" is not yet final when the widget looks for a try-on whose answer was lost, and how long it waits for any reply from the server before it reports a lost connection. |
overloadedWindowMs |
60000 |
The window in which a refused try-on is sent again (see A busy server). |
You rarely need to change these. attemptMaxMs matches the longest time a job can take on our side.
Example¶
<script src="https://api.ziex-tryon.com/embed/tryon-embed.js"></script>
<script>
window.TryOn.init({
apiKey: 'pk_YOUR_PUBLISHABLE_KEY',
productImage: 'https://shop.example/img/blue-jacket.jpg',
productTitle: 'Blue jacket',
productUrl: 'https://shop.example/blue-jacket',
category: 'outerwear',
lockCategory: true,
lang: 'en',
theme: 'auto',
accent: '#0F766E',
cabinet: true
});
</script>
Methods and properties¶
| Member | Description |
|---|---|
TryOn.init(options) |
Starts the widget. See above. |
TryOn.open() |
Opens the window. Does nothing before init(). This is how you open it when launcher is false. |
TryOn.close() |
Closes the window. A try-on that was already sent keeps running (see below). |
TryOn.update(options) |
Changes the running configuration without redrawing. Accepts the options of init(); only the keys you pass are changed. Called before init(), it behaves as init(). |
TryOn.destroy() |
Removes the widget from the page, and ends a try-on that was sent. Also removes every event listener. |
TryOn.on(event, fn) |
Subscribes to an event. The same function is subscribed once. |
TryOn.off(event, fn) |
Unsubscribes. Without fn it removes every listener of that event. |
TryOn.version |
The widget's version as a string in semver form (for example 1.2.0). Readable before init(). |
update() or destroy()¶
update() and destroy() are not interchangeable.
update()only patches the configuration. Nothing is redrawn, and a try-on that was already sent keeps its key, API address and product and runs to the end. The next opening or send uses the new values. An open window shows the new garment picture straight away.destroy()ends a sent try-on. Its job is already created and charged, so the result is lost, and the record the widget keeps for it is deleted. A followinginit()picks nothing up.
Single-page sites
When the shopper switches product or variant, or the route changes, call TryOn.update({ … }). Do not call
destroy() followed by init(), and do not call destroy() from a closed listener. Either one throws away a
try-on the shopper has already paid for.
// Variant switched on a single-page product page:
window.TryOn.update({
productImage: variant.imageUrl,
productTitle: variant.title,
productUrl: variant.url
});
Events¶
window.TryOn.on('result', function (data) {
console.log(data.result_url, data.job_id, data.processing_time_ms, data.background);
});
window.TryOn.on('error', function (err) {
console.warn(err.code, err.message, err.detail, err.background);
});
| Event | Data | When |
|---|---|---|
opened |
none | The window was opened. |
closed |
none | The window was closed. |
upload_started |
none | The shopper pressed "Try on" and the widget started sending. Once per try-on. |
upload_completed |
the status of the finished job | The job finished successfully. Comes together with result. |
result |
{ result_url, job_id, processing_time_ms } |
The result is ready. |
error |
an Error |
The try-on failed or the wait ran out. |
ready |
{ job_id, kind, background } |
An outcome is waiting for the next opening of the window. See below. |
Details:
result:result_urlis the address of the picture. Treat it as temporary and do not build your own storage on it.processing_time_msis the job's processing time.erroris anErrorobject.err.messageis the text the widget itself shows the shopper in the page's language.err.codeisTIMEOUTwhen the wait ran out,OVERLOADEDwhen the server stayed busy through all repeats, and otherwise the code of the failed job.err.detailholds the server's raw text, for your logs only: do not show it to shoppers.background: true. A try-on that was sent is followed even when the window is closed. Events that arrive then carrybackground: truein their data (err.backgroundforerror). The flag is absent when the window is open, so listeners written without it keep working.ready. An outcome (a result or an error) arrived while the window was closed, or on an earlier page of the same tab.kindisresultorerror;backgroundis alwaystrue. It is sent after the outcome's ownupload_completed,resultorerror, so a page that opens the window in areadylistener never receives those events flagged as background for an outcome that is already on screen. A later page of the tab does not emit it again.- Timing. When a job finishes while the window is open,
resultcomes a fraction of a second after the status becomes final, and up to 5 seconds later if the picture loads slowly.
Exceptions thrown by a listener are caught and do not affect the widget.
A try-on outlives its window¶
From the moment the widget sends a try-on, the job exists on our side and is charged. So the widget does not cancel anything when the shopper closes the window:
- The widget keeps following the job in the background.
upload_completed,resultanderrorstill arrive, withbackground: true. - The outcome is shown the next time the window opens. If the "My looks" gallery is on, a result is saved there as usual.
- Photos that are still being prepared are different: nothing has been sent yet, so closing the window cancels that attempt and sends nothing.
- Only one try-on runs at a time. While one is running, a second press of "Try on" is refused.
It survives a page change in the same tab. Before it sends, the widget writes a small record into the tab's
sessionStorage: the idempotency key, the job id once it is known, when it was sent, the estimate, the category and the
product. It never holds the photo, the API key or the API address. The next page of the site that calls TryOn.init()
with the same key picks the record up and follows the job on. A page reload, a click to another product page and a
single-page navigation all count. A closed tab loses the record; the result is then visible only in the client portal
and the job history.
While an outcome waits, the widget:
- puts a badge on its launcher and changes the launcher's accessible label;
- sets
data-tryon-readyon the<html>element:resultorerror; - emits
ready(once, on the page where the outcome arrived).
/* Mark your own button, with launcher: false or a custom trigger */
html[data-tryon-ready] .my-tryon-button::after {
content: "";
display: inline-block;
width: 8px;
height: 8px;
margin-left: 6px;
border-radius: 50%;
background: #16a34a;
}
html[data-tryon-ready="error"] .my-tryon-button::after { background: #dc2626; }
The flag stays set on every later page of the tab until the shopper opens the window and sees the outcome. An outcome's events go out once, on the page where it arrived; later pages only show the badge and the flag.
If the answer to the shopper's send is lost (a proxy error, a dropped connection, a timeout), the widget looks the
try-on up by its idempotency key instead of reporting a failure, so one press never becomes two charges and a result is
not lost. If the server cannot be reached at all for lookupGraceMs, the widget reports a lost connection.
A busy server¶
When every image slot on the server is busy, the API refuses a try-on with 503 and the code OVERLOADED. Nothing is
created or charged, so the widget sends the same request again with the same idempotency key after the Retry-After
delay (at least 1 second, at most 30, and 5 seconds if the header is missing). It repeats at most 3 times and only while
the repeat still falls within 60 seconds of the first send (overloadedWindowMs). During the wait the window says that
there are many try-ons right now, shows no percentage and asks the shopper to stay on the page. If the repeats run out,
the attempt ends with an error whose code is OVERLOADED and a retry button. Closing the window does not stop the
repeats; destroy() does.
Test keys¶
A key that starts with pk_test_ or tryon_test_ puts a "TEST" badge into the window header. A try-on made with a test
key is not sent to a model and is not charged; the result is a fixed placeholder picture. The badge follows the key the
current try-on was sent with, so update({ apiKey }) shows or removes it in an open window at once. tryon_test
without the trailing underscore is an ordinary key.
Styling¶
The window is light by default and is drawn only from CSS variables. The widget puts the class ziex-tryon-host on its
host element. Set the variables on that class from your own stylesheet:
.ziex-tryon-host {
--tryon-accent: #0F766E;
--tryon-radius: 8px;
--tryon-font: "Inter", system-ui, sans-serif;
--tryon-launcher-bottom: 96px;
}
| Variable | Controls |
|---|---|
--tryon-accent, --tryon-accent-fg |
Main button, progress bar, focus ring, and the text colour on them. The accent option sets both; if you set only --tryon-accent in CSS, set --tryon-accent-fg too (it defaults to white on the light theme). |
--tryon-bg, --tryon-surface, --tryon-fg, --tryon-muted, --tryon-border |
Window background, panels, text, secondary text, lines. |
--tryon-danger, --tryon-success |
Error and success colours. |
--tryon-font |
Font family. |
--tryon-radius |
Corner radius of the window and its controls. |
--tryon-scrim |
Colour of the dimmed page behind the window. |
--tryon-shadow-window, --tryon-shadow-launcher |
Shadows. |
--tryon-launcher-bottom, --tryon-launcher-side |
Distance of the launcher from the page edge. |
Colour variables you set apply in both themes, so when you set them yourself check contrast in the theme your
shoppers see. The accent option is the safer route: it also picks a readable text colour.
On a phone (480 px and narrower) the window becomes a full-height sheet and the page behind it does not scroll while it is open.
Compatibility¶
TryOn.version follows semantic versioning. Within a major version the options and events only grow: new optional
options and new events may appear, existing ones keep their meaning. The pickup record, the html[data-tryon-ready]
flag, the ready event and the loader's interface are frozen for version 1; see
Lazy loading and pickup.