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.
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
storefrontApiTokenautomatically. 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 tolayout/theme.liquid, before the closing </body> tag:
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 withid="layers-script".
API key resolution and load order
The widget resolves its access token whenever it makes an API request, in this order:widgetManager.config.apiKey, set withsetApiKey(),configure({ apiKey }), or by a non-emptydata-api-keyvalue read when the widget initializes.- The current non-empty
data-api-keyvalue on#layers-script. - The
storefrontApiTokenvalue in the app embed’s<script id="layers-config" type="application/json">element.
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 atwindow.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)andgetApiKey()to set or resolve the storefront access token.createImageSearchToggle(options),createSimilarProductsToggle(options), andcreateSimilarProductsPreview(options)to add widgets from JavaScript.updateTheme(properties)to set CSS custom properties at runtime.updateTranslations(widgetType, translations)to merge translations forimage-searchorsimilar-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
sessionStorageand 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.
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:
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:
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 sendcontext.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:
currencyFormat.currency, if you explicitly configure it.- The response’s
_meta.pricing.currency_code. window.Shopify.currency.active.USD.
currencyFormat.locale, if you explicitly configure it.window.Shopify.locale, withwindow.Shopify.countryappended when the locale has no region subtag, such asen-CA.- The bare
window.Shopify.locale. en-US.
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.
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 withconfigure():
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 error state in both widgets always shows the configured
results.states.error.defaultText. Shoppers never see the raw API error message.
Analytics
The widget exposeswindow.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.liquidincludes the module script and that itssrcURL 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
Checkwindow.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
Setdata-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_searchafter Back. Theme code that rewrites the URL withhistory.replaceStateorpushStatecan drop the parameter. - Confirm the current tab has saved image-search state matching the URL image ID. If
sessionStorageis 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
Setdata-debug="true" on #layers-script before the module initializes, or enable logging later:
Next steps
- What the app embed handles for token delivery, storefront pixel behavior, and event forwarding.
- Theme implementation checklist for the wider Shopify theme integration.
- Shopify Markets for market and currency setup.
- Liquid integration for reading Layers configuration and metaobjects in your theme.