Skip to content

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_ (or tryon_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 apiUrl only 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 following init() 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_url is the address of the picture. Treat it as temporary and do not build your own storage on it. processing_time_ms is the job's processing time.
  • error is an Error object. err.message is the text the widget itself shows the shopper in the page's language. err.code is TIMEOUT when the wait ran out, OVERLOADED when the server stayed busy through all repeats, and otherwise the code of the failed job. err.detail holds 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 carry background: true in their data (err.background for error). 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. kind is result or error; background is always true. It is sent after the outcome's own upload_completed, result or error, so a page that opens the window in a ready listener 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, result comes 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, result and error still arrive, with background: 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-ready on the <html> element: result or error;
  • 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.