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.

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 and analytics

Use updateTranslations() to deep-merge custom text for either widget:
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.

Enable debug logging

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

Next steps