Skip to main content

Overview

The Visual Search widget gives shoppers three ways to discover products:
  • Image search modal: shoppers upload an image or select an area of an image to find visually similar products.
  • Shop-similar modal: shoppers open a product-specific view of similar products.
  • Similar-products preview: you can display a product grid directly on a page and let shoppers open the full modal.
The widget is a standalone JavaScript module served from the Layers CDN. The Layers app embed does not load it. The embed writes the storefrontApiToken that the widget can use and loads the storefront pixel, which forwards widget events to Clarity or Blotout when those integrations are enabled. Add the widget script to your theme yourself. See What the app embed handles.

Prerequisites

  • Install Layers and enable the app embed if you want it to provide the storefront access token and forward widget events.
  • Have a storefront access token available. When the app embed is enabled, the widget can read its storefrontApiToken automatically. You can also provide a token on the widget script or through the widget manager.
  • Add the widget script to your Shopify theme. Enabling the app embed alone does not add the widget.

Installation

Add the script to layout/theme.liquid, before the closing </body> tag:
The CDN build is an ES module, so use type="module". Module scripts defer automatically; you do not need to add defer. Once the script loads, you can add the custom elements where you want the entry points and preview to appear. See Image search, Shop similar, and Similar-products preview.

Script attributes

Put these attributes on the script element with id="layers-script".

API key resolution and load order

The widget resolves its access token whenever it makes an API request, in this order:
  1. widgetManager.config.apiKey, set with setApiKey(), configure({ apiKey }), or by a non-empty data-api-key value read when the widget initializes.
  2. The current non-empty data-api-key value on #layers-script.
  3. The storefrontApiToken value in the app embed’s <script id="layers-config" type="application/json"> element.
If you inject data-api-key after the widget loads and no key was configured at initialization, the widget can use the new value on its next request. A key already stored in config.apiKey remains higher priority than later changes to the attribute. The widget caches the app embed token after it first parses a valid value. Missing or invalid embed JSON does not prevent initialization. data-debug is read once when the widget initializes. In contrast, data-hide-branding is checked when each modal renders, unless you explicitly configure hideBranding.

JavaScript configuration

The widget manager is available at window.Layers.widgetManager. Configure it after the module has loaded:
configure() accepts these options: The currencyFormat object accepts: For example, configure({ currencyFormat: { currency: 'EUR' } }) keeps the default style: 'currency' and currencyDisplay: 'symbol'. You can also use updateCurrencyFormat() to merge currency-format properties. Other available methods include:
  • setApiKey(apiKey) and getApiKey() to set or resolve the storefront access token.
  • createImageSearchToggle(options), createSimilarProductsToggle(options), and createSimilarProductsPreview(options) to add widgets from JavaScript.
  • updateTheme(properties) to set CSS custom properties at runtime.
  • updateTranslations(widgetType, translations) to merge translations for image-search or similar-products.
  • debugTheme() to log the theme values the widget manager tracks.

Image search toggle

Use <layers-image-search-toggle> to add a button that opens the image-search modal.
Both toggles show a pointer cursor on hover. The widget sets it with a zero-specificity rule, so any cursor rule in your theme CSS wins.

Restoring results after navigation

When an image search returns results, the widget adds the uploaded image ID to the page URL as ?layers_image_search=<id>. It uses history.replaceState, so it keeps your existing query parameters and does not add a history entry. It also saves a smaller copy of the shopper’s photo (up to 1000px) in the browser’s sessionStorage for the current tab. When a shopper opens a product from the results and presses Back, the first <layers-image-search-toggle> on the page reopens the modal. Before searching, the widget requires a matching image ID in the current tab’s sessionStorage. With that saved state, it searches again without uploading the photo a second time. The saved copy restores the photo preview and the Crop Image button. A URL image ID alone cannot restore results.
  • Missing saved state: if storage is missing, blocked, full, or contains a different image ID, the widget removes the URL parameter and stays on the upload step without searching. A new tab usually has separate storage, but a tab opened from another tab can receive a copy of its opener’s sessionStorage and restore matching state.
  • Expired image ID: Layers keeps an uploaded image for about one hour. After that, the widget uploads the saved copy again and shows fresh results. If the matching stored state has no image copy, the widget removes the parameter and shows the upload step.
  • Temporary errors: network errors, rate limits, and server errors keep the image ID in the URL and show the error state, so Retry runs the same search.
  • Clearing: closing the modal, starting a new search, or going back to the upload step removes the parameter and the saved copy.
  • Client-side navigation: if your theme re-renders the toggle when the route changes, the new toggle restores the search too. Only one toggle restores at a time, so pages with several toggles open one modal.
You don’t need to change your theme for this. It works as soon as the toggle is on the page the shopper returns to.

Custom upload content

Add a <template> and reference its id with template-id to display custom content in the upload step:

Reference images and area-selection events

Use <layers-image-search-reference-image> to open image search from an image:
The custom element copies its attributes to an image and opens the search flow when a shopper clicks it. The widget also emits a bubbling layers-image-search-area-selected event after the shopper selects an image area:

Similar-products toggle

Use <layers-similar-products-toggle> to add an entry point for a product’s similar-products modal:
You can add a filter group as a child script. The widget validates this JSON before using it:
Set conditional to "AND" or "OR". Each expression has a string property, a string operator, and a values array containing strings, numbers, or booleans.

Similar-products preview

Use <layers-similar-products-preview> to render a product grid with a button that opens the similar-products modal:
The preview also accepts a child <script type="layers/filter-groups"> with a filter group in the same format as the similar-products toggle.

Prices, currency, and Shopify Markets

The image-search and similar-products requests send context.market when window.Shopify.country contains a country code. The widget trims and uppercases the value, for example "ca" becomes "CA". If Shopify country is unavailable or empty, the widget omits context.market. The API then uses the request’s geo country, or the US if no country is available, and returns prices localized for that market. When the response includes _meta.pricing.currency_code, the widget uses it to format the returned prices. It resolves currency in this order:
  1. currencyFormat.currency, if you explicitly configure it.
  2. The response’s _meta.pricing.currency_code.
  3. window.Shopify.currency.active.
  4. USD.
The locale order is:
  1. currencyFormat.locale, if you explicitly configure it.
  2. window.Shopify.locale, with window.Shopify.country appended when the locale has no region subtag, such as en-CA.
  3. The bare window.Shopify.locale.
  4. en-US.
If the requested locale is invalid, the widget tries the bare Shopify locale and then en-US. Set explicit formatting when your storefront needs a fixed currency or locale:
configure() merges currencyFormat properties, so setting only currency keeps the existing currency style and symbol display. For more about configuring Shopify market context, see Shopify Markets.

Styling

The widget applies theme values across its shadow roots. Set supported variables on :root in your theme CSS, or call window.Layers.widgetManager.updateTheme() to change them at runtime. The theme manager reads computed values from document.documentElement and observes changes to its style and class attributes.
The table lists every supported variable with its default. updateTheme() does not track --layers-widget-transition-properties; set that one in CSS.

Translations

You can change every piece of text the widgets show, including the modal heading, upload and crop steps, instructions, results, empty and error states, the clipboard and file messages, and the labels screen readers announce. Overrides are deep-merged over the built-in English text, so pass only the strings you want to change. Set copy for both widgets with configure():
updateTranslations(widgetType, translations) does the same for one widget at a time. Changes apply to open modals without a reload. To expose copy as theme settings, pass a JSON object on the image-search toggle with the translations attribute. Escape it so the JSON stays inside the attribute:
The main image-search groups are: The error state in both widgets always shows the configured results.states.error.defaultText. Shoppers never see the raw API error message.

Analytics

The widget exposes window.Layers.analytics.subscribe(handler) for its analytics events. When Clarity or Blotout is enabled, the app embed can also forward widget events to those services when their scripts are present and consent allows it. See app embed integrations.

Troubleshooting

The widget does not appear

  • Confirm that layout/theme.liquid includes the module script and that its src URL loads successfully.
  • Confirm that your page includes the appropriate custom element. The similar-products toggle and preview also need a product-id.
  • Check the browser console for script, configuration, or network errors.

The console says “No API key found”

Confirm that at least one token source is available: data-api-key on #layers-script, storefrontApiToken in the app embed’s #layers-config JSON, or window.Layers.widgetManager.setApiKey() / configure({ apiKey }). A token configured at initialization takes priority over later attribute changes.

Prices use the wrong currency

Check window.Shopify.country, window.Shopify.locale, and window.Shopify.currency.active on the storefront. Review the API response’s _meta.pricing.currency_code and any explicit currencyFormat settings, which take priority over Shopify’s active currency. See Shopify Markets.

Branding is still visible

Set data-hide-branding="true" on the script element with id="layers-script". If you set hideBranding with configure(), its boolean value overrides the script attribute.

Back does not reopen image search results

  • Confirm the page the shopper returns to includes <layers-image-search-toggle>. The toggle is what reopens the modal.
  • Check that the URL still contains layers_image_search after Back. Theme code that rewrites the URL with history.replaceState or pushState can drop the parameter.
  • Confirm the current tab has saved image-search state matching the URL image ID. If sessionStorage is unavailable, full, cleared, or mismatched, the widget removes the parameter and leaves the upload step active. The URL alone does not restore results.

Enable debug logging

Set data-debug="true" on #layers-script before the module initializes, or enable logging later:

Next steps