Skip to main content

Install Zoovu Search on your site

Use this guide to set up Zoovu Search on your website. The steps cover design customization, integration with your site's search box, and script installation.

Go to Search Studio > Design & Publish. Use the built-in Search Designer to adjust the search interface:

  • Choose how results should be displayed: in an overlay, embedded on the page, or in fullscreen mode.
  • Modify colors, placeholders, and the layout of filters and results.

You can preview changes in the designer.

Zoovu Search must be linked to an input field or trigger on your site.

  • If your site already has a search bar, load your site in the top bar of the Search Studio. It will try to detect your existing search elements automatically.
  • If you have multiple search boxes, go to General > Search box and button selectors, and enter the CSS selectors manually.
  • If your site does not have a search box, add this code snippet to the desired location (e.g. your site header):
<section role="search" data-zoovu="true">  
<input type="search" id="searchBox" placeholder="Search…">
<button id="searchButton"></button>
</section>

You can also use the "Add a search bar to my website" option and specify whether the search bar should appear before or after an existing element on your page.

Step 3: Install the search script

This guide refers to the latest version of the search script (v14).

When you're done customizing, click Publish in the top-right corner of the Search Studio. This generates a personalized code snippet for your project.

To apply the search to your site, copy the single-line JavaScript snippet and place it before the closing </body> tag.

<script async src="https://js.search.zoovu.com/plugin/bundle/0000.js">
</script>

Replace 0000.js with the actual bundle ID generated for your site.

This script loads your configuration dynamically. If you update the design later, just click Publish again—no need to edit the code manually.

If your platform does not allow direct access to source code, try adding the snippet using a code block widget or similar feature.

Using multiple configurations

You are not limited to one configuration. You can create additional ones for different use cases:

  • Run separate configurations for staging and production.
  • Use different setups for different domains or sections of your site.
  • Add another search box with a different design and behavior.

Each configuration gets its own code snippet and settings. To create a new one:

  • In Design & Publish, click Main search in the top-left corner.
  • Select Add new configuration.
  • Copy the original configuration’s settings if needed, or start fresh.

Each configuration works independently and can be updated separately.

Advanced configuration

Use the Advanced Configuration tab in the Search Designer to fine-tune the behavior of Zoovu Search. You can define parameters for how search suggestions appear, how results are displayed, and how your search input behaves.

  • Edit the zSearchConfig object to add or override settings. (Inline comments in the configuration code explain how each option works.)
  • Check the full configuration reference if you need detailed control over appearance, filters, sorting, or analytics integrations.
<script>
// create a settings object
var zSearchConfig = {
showErrors: false, // whether to show implementation errors, set to false for production
allowCookies: true, // whether to allow the javascript to set cookies
suggestions: {
show: true, // whether to show search suggestions
trigger: undefined, // a suggestion overlay trigger
showOnMobile: true, // whether to show seearch suggestions on mobile devices (less than 768px), default: true
url: '', // the URL that provides the data for the suggest
maxQuerySuggestions: 3, // the maximum number of query suggestions
querySuggestionHeadline: undefined, // the headline of the query suggestions, leave blank if no headline should be shown
emptyQuerySuggestions: undefined, // suggestions to be shown if the search box is empty, a JSON object mapping content group names to array of suggestions, e.g. suggests:{_:[{name:"The Title",link:"https://mylink.de/",image:"https://placekitten.com/150/150"}]}
showImages: true, // show images in search suggestions
equalSearch: false, // whether suggestions should be handled separately or whether the search results should be the suggestions
num: 6, // the maximum number of search suggestions to be shown
minChars: 3, // minimum number of characters before the suggestions shows, default: 3,
maxWidth: 'auto', // the maximum width of the suggest box, default: as wide as the input box, at least 275px
throttleTime: 300, // the number of milliseconds before the suggest is triggered after finished input, default: 300ms
highlight: true, // whether matched words should be highlighted, default: true
viewAllLabel: 'Show all results', // the label of a 'View All' button shown at the end of the suggestion list, default: undefined - no 'View All' button will be added
forceBelow: false, // whether to force the suggestions to be shown below the search box (default: false)
mobileScrollOnFocus: true, // whether to scroll the page in order for the search box to be to the top of the window (on screens below 768 px)
triggersSearch: false, // whether to trigger the full search instead of search suggestions
viewKeyMappings: undefined, // a map of result group to view name
suggestTemplate: { // suggestion template
template: undefined,
preRenderCallback: undefined,
templateBuiltCallback: undefined,
postRenderCallback: undefined
},
fuzziness: undefined, // the suggestion fuzziness (between 0 and 5)
maxSuggestions: {}, // a mapping of content group names to max number of suggestions to be shown, this setting has higher priority than contentGroups.include and data-zoovu-include attribute, only content groups from this setting (if not empty) will be shown in search suggestions
maxSearchHistoryEntries: 5, // how many recent queries to show when the user focuses a search box (set to 0 to disable)
searchHistoryLabel: 'You recently searched for:', // the label of recent queries
linksOpenNewTab: false, // should clicking on the result links open a new tab/window?
source: { // blocks to be displayed in suggestions
emptyState: {
layoutType: 1,
content: [
{
type: 'searchHistory',
categoryStyle: 'titles',
style: 'compact'
}
]
},
queryBased: {
layoutType: 1,
content: [
{
type: 'resultGroup',
categoryStyle: 'titles',
style: 'compact'
}
]
}
},
allResultsName: 'All', // the name of the all results tab
focusLayer: false, // whether a focus layer should be rendered (everything on the page should be darkened/blurred after a search box is focused)
focusLayerStyle: 'darken', // focus layer style - 'darken' or 'blur'
noSuggestsText: undefined, // the text to be displayed if no suggestions are available
showEmptyStateSuggestions: true, // whether to show empty state suggestions
filters: undefined // filters to be applied to the suggestion request
},
style: {
accentColor: '#3d8fff', // the accent color, used for CTAs, prices, and other highlighted elements
themeColor: '#4a4f62', // the theme color, used for dark icons and headings
defaultCss: true, // whether to include the default CSS,
loaderType: 'skeleton', // can be "circle", "square" or "skeleton"
animationSpeed: 250, // speed of the animations in milliseconds
additionalCss: undefined, // additional CSS to add to the plugin's stylesheets: e.g. '#zoovu-layer{background: red;}'
redrawTrigger: undefined // a CSS selector to an element which triggers redrawing of the applied styles
},
searchBox: {
placeholder: undefined, // the placeholder to display
autofocus: false, // if true, the search box will get focus after initialization
selector: '#searchBox', // the selector to the search box
searchButton: '#searchButton', // CSS selector of search buttons
focusLayer: false, // if true, a layer will be shown when the user focuses on the search input
preventFormParentSubmit: true, // whether to disable submitting of parenting form
searchButtonLabel: undefined, // the label of the search button in Zoovu Search custom search inputs, if not set, a magnifying glass icon will be rendered
animatedPlaceholder: {
enabled: false, // whether to simulate queries being entered as a placeholder
queries: [], // the queries to display, if empty, queries will be taken from the query log
maxRotations: 10, // the maximum number of times a query being entered as a placeholder should be repeated
onlyFirstVisit: false // whether to only simulate queries being entered on the first visit
},
inject: false, // whether to inject a search bar to the page
injectTo: undefined, // a css selector where the search bar should be injected
},
results: {
embedConfig: undefined, // {'url':undefined,'contentBlock':'.page-content-body'}, // if url is given the page will change to that URL and look for the content block there to insert the results
fullScreenConfig: undefined, // {trigger: '#zoovu-search-trigger', caption: 'Search this site'}, trigger is the CSS selector to the element that starts the search full screen overlay and searchCaption the caption on the full screen search page
caption: 'Found #COUNT# search results for <span class="_bprefix_-layer__heading-query">"#QUERY#"</span>', // the caption of the search results
group: true, // whether results should be grouped if content groups are available
filters: undefined, // filters to be sent with every search request
mergePresetFilters: false, // whether preset filters and filters selected by the user should be merged together in the search request
num: 9999999, // the maximum number of search results to be shown
highlightQueryTerms: true, // whether to highlight the query terms in search results
moreResultsButton: 'See more', // HTML for the more results button, all results will be shown if this is null
noResultsText: 'Sorry, we have not found any matches for your query.', // the text to show when there are no results
noResultsRedirect: undefined, // the page to redirect to when there are no results
queryCorrectionText: 'Did you mean "#CORRECTION#"?', // #CORRECTION# will be replaced automatically by the corrected query
queryCorrectionRewrite: 'Showing results for "#CORRECTION#"', // #CORRECTION## will be replaced automatically with the rewritten/corrected query
queryCorrectionOverride: 'Search for "#ORIGINAL_QUERY#" instead',
searchQueryParamName: 'zQuery', // the name of the search query parameter
linksOpenNewTab: false, // should clicking on the result links open a new tab/window?
showSearchBoxLayover: true, // whether to show search box in search result layover
showSearchBoxEmbed: false, // whether to show search box in search result block for embed search results
moreResultsPagingSize: 12, // the number of new results to show each time the more results button is pressed (max: 24)
orderByRelevanceText: 'Relevance', // the text to be shown in order select box to describe 'order by relevance' option
orderOptionsAsTabs: false, // whether to show order options as tabs (only applied if a single result group is retrieved)
defaultOrderOptionLabel: 'Default', // the label of the "Default" order option tab
redirectOnSingle: false, // whether to redirect instead of showing a single search result
limitPerGroup: true, // if set to true, the maximum number of search results will be applied to every single content group, otherwise the limit will be spread across all groups, default: true
stripHttp: false, // if set to true the protocol part (http:// or https://) will be removed from the visible url shown in the search results
layoverTrigger: undefined, // a CSS selector that points to an element which should trigger the showing of the layover search layer
lazyLoadImages: true, // whether to lazily load images (once they become nearly visible, instead of loading them all at once)
infiniteScroll: false, // whether to show more search results once the user scrolls to the end of the result block (will only work if the navigation type is 'tabs' or only one content group has been retrieved)
hideResultsWithoutImage: false, // whether to hide all results that don't have any image or have a broken image
sortingLabel: 'Sorting:', // a text label to show next to the sorting selection
sorting: undefined, // the default sorting option to apply
cta: [], // array of CTAs to render in the search results, every cta is an object with the following structure: {text: 'The text of the CTA Button', link: 'The link to redirect to after the CTA is clicked (default: no redirect), use #RESULT_URL# to redirect to the result page', renderAsButton: true /* whether to render the cta with button-like style, default: true */, icon: 'zoovu:arrow' /* the icon to show inside of the CTA button ('zoovu:arrow' - triangle arrow, 'zoovu:shopping-cart' - shopping cart icon, svg string, image url, or 'none'), default: 'zoovu:arrow', only rendered if renderAsButton is true */, 'clickCallback': undefined /* a callback to call after the CTA button is clicked, receives the event object and result JSON as parameter */, includeContentGroups: undefined /* json array of content group names for which the CTA should be shown */, excludeContentGroups: undefined /* json array of content group names for which the CTA should not be shown */, position: 'left' /* the alignment of the cta button, 'left', 'center' or 'right' - works only for ctaDirection: 'column', for ctaDirection: 'row' all the ctas will be aligned based on the first item in the list */}
ctaDirection: 'column', // how to place multiple CTAs in one search result - 'row' x 'column' (default)
resultTemplate: { // the result template
template: undefined,
preRenderCallback: undefined,
templateBuiltCallback: undefined,
postRenderCallback: undefined,
variableReplacementPattern: undefined,
dataPointDefaults: {},
highlightContext: undefined // css selector where query parts should be highlighted
},
hideLayerOnBodyClick: true, // whether to hide the search result layover layer when user clicks outside of the layer (otherwise only the ESC key and close button click will hide the layover layer)
showContentGroupHeadings: true, // whether to show the content group headings
semanticMode: false, // whether to generate semeantic urls like /s-t-shirts, only to be used with embed mode
semanticModeParamName: 's-', // the search query prefix in semantic mode
semanticModeSpaceCharacter: '-', // the character/string to use instead of space in a semantic mode
semanticModeKeepTrailingSlash: false,
placeholderImage: undefined, // by default a striped background will be instead of missing images, set to null to collapse missing images, or to a url to load a placeholder image
allowHistoryApi: true, // whether to use the history API to push and replace states
focusResultBlock: true, // whether to focus the result block once the results were loaded (to be able to tab directly to the first search result)
nameParsing: true, // whether to use filter names and full values in query params instead of filter ids (+ whether to add sorting options to the url)
sortingParamName: '_bprefix_sorting', // the name of the sorting query param
checkImageQuality: true, // whether to hide images with a bad aspect ration
fuzziness: undefined, // the search result fuzziness (between 0 and 5)
integrationType: undefined, // the integration type - 'layover' (default), 'fullscreen', or 'embed'
contentDataPoint: 'searchSnippet', // the data point that should be displayed as a search snippet
showVariants: true, // whether to display product variants
showAlternativeImages: true, // whether to display alternative images
showRelatedQueries: true, // whether to display related queries
relatedQueriesPosition: 'aboveResultLayer', // where to display related queries - 'aboveResultLayer', 'withinResultLayer', or 'belowResultLayer'
renderRelatedQueriesTitle: false, // whether to display a text label in front of the related queries
relatedQueriesTitle: 'Related Searches:', // the text label to display in front of the related queries
pageDescriptionLabel: 'Showing #COUNT# of #TOTAL# results', // label of the page information text
showCopyLinkToPositionButton: true, // whether to show a button enabling users to copy a link to the current page in the result set
copyLinkToPositionButtonLabel: 'Copy a link to this position in the list', // the label of the button enabling users to copy a link to the current page in the result set
copiedLinkToPositionButtonLabel: 'Link copied', // the label to display after a link to the current page was copied
showGuidedQuestions: false, // whether to display guiding questions
guidedQuestionsShowBack: true,
guidedQuestionsBackLabel: 'Back',
guidedQuestionImageSize: 'icon', // the image size of the guiding question answer - 'icon', 'thumbnail', 'banner', or 'none'
guidedQuestionPosition: 'top', // the position of the guiding questions - 'top', or 'left'
card: true, // whether to add a border to the result card
addToCartLink: '#RESULT_LINK#', // the link of the "add to cart" button
addToCartLabel: 'Add to cart', // the label of the "add to cart" button
maxBadges: 2, // the maximum number of badges to be shown in the result card
badgeDataPoints: [], // the data points to be shown as badges in the result card
stockDataPoint: undefined, // the name of the data point that holds the stock information
inStockLabel: 'In Stock', // the label of "In Stock" information
outOfStockLabel: 'Out of Stock', // the label of "Out of Stock" information
ratingDataPoint: undefined, // the name of the data point that holds the rating information
maxRating: 5, // the maximum value of the rating data point
ratingCountDataPoint: undefined, // the name of the data point that holds the rating count information
ratingCountLabel: '(#COUNT# reviews)', // the rating count label
documentTitle: undefined, // the document title to set after a SERP is embedded on the page (#QUERY#, #SITE_NAME#, and #COUNT# can be used as placeholder)
showPageSizeSelector: false, // whether to display a page size selector
pageSizeOptions: [12, 24, 48], // the page size selector options
pageSizeLabel: '#COUNT# per page', // the label of a page size selector option
scoreDataPoints: [], // the data points to be shown as scores in the result card, each data point definition is an object in the following format: { max: 5, dataPoint: 'my_score', label: 'My Score' }
variantGroupingAttribute: undefined, // if an attribute name is provided, all product variants with the same attribute value will be grouped under one variant tile (use-case: one colorway in multiple sizes)
variantSubgroupingAttribute: undefined, // if an attribute name is provided, all its values for the current variant will be rendered in a "subvariant" selector (use-case: sizes of the colorway)
variantsSubgroupSortingFunction: undefined // a sort function to order the subvariant values
},
queryTerm: {
scrollIntoViewBlock: 'start', // how to scroll the text into view on redirect and a single query term match, one of 'start', 'center', 'end' or 'none' (don't scroll into view at all)
highlightContext: undefined, // a CSS selector to limit a part of the page where redirect query terms will be highlighted
highlight: true, // whether to highlight parts of the query after redirect to a specific search result
scrollIntoViewBehavior: 'smooth', // the behavior of the scroll text into view, 'smooth' (the page smoothly scrolls to the text - over a few seconds) or 'auto' (the page instantly jumps to the text)
highlightColor: '#fffaa8', // the background color of highlighted text
highlightMatchedContent: false, // whether to highlight the matching content on the result page (after the user selects a result)
scrollOnMultiMatch: false, // whether to scroll to the first match if the matched content occurs multiple times on the page
tokenize: false, // whether to split longer queries into tokens and highlight each token (e.g. the query 'Zoovu Search' would also highglight single 'zoovu' and 'search' words)
faqContext: undefined, // FAQ selector --> if a FAQ query param is identified, the UI will scroll to this element
highlightOnRedirect: true, // whether to highlight search terms on redirect
scrollOnRedirect: true // whether to scroll to the highlighted search term on redirect
},
contentGroups: {
include: undefined, // json array of content group names to be included in the search result
exclude: undefined, // json array of content group names to be excluded from the search result
otherName: '', // the name of the results not in any other content group
ignoreOther: false, // whether or not to ignore the "other" content group
viewNames: {}, // mapping of content group names set up in the control panel to view names
classMap: {} // a map of string to a class name to be appended to the result group block
},
tracking: {
providers: [], // how to track, supported values: 'GA' (Google Analytics), 'GTM' (Google Tag Manager)
searchCallback: undefined, // callback before SERP is reported, SERP events aren't reported if this returns false, you'll get the query as the parameter for the callback
gaAlias: undefined, // the alias of the Google Analytics tracker
ignoreQueryParam: false, // whether to strip query params from external tracking
external: undefined, // definition of custom search/product components that should be tracked
zoovuCidAsSessionId: false, // whether to use the zoovu-cid cookie for search analytics
optOutDisablesTracking: false, // whether the Zoovu opt-out cookie completely disables client-side tracking
optOutDefault: false // if set to true, all sessions Zoovu opt-out cookie will be treated as opted out
},
smart404: {
identifier: 'Page not found', // the string in the title that identifies the page as a 404 page
cssIdentifier: undefined, // a css selector to an element identifying a 404 page, if the element is present on the page, the page will be considered a 404, this setting overrides the 'identifier' setting if set
resultSelector: '#zoovu-404', // a CSS selector that points to the area in which the alternative links should be shown
caption: 'Try going here instead:', // caption for 404 results
num: 12, // the maximum number of results, cannot be greater than 12
searchResultsLayerLabel: 'Recommended Links'
},
layout: {
mobile: { // below 992px
type: 'list', // can be "grid", "masonry" or "list", default: "list"
showImages: true, // whether to show images in search result, default: true
showSnippet: true, // whether to show text snippet in search result, default: true
showTitle: true, // whether to show title in search result, default: true
showDataPoints: true, // whether to show data points in search result, default: true
showUrl: false, // whether to show link in search result, default: false
gridColsMd: 2, // grid layout column count for devices between 768px and 991px, default: 2
gridColsSm: 1 // grid layout column count for devices below 768px, default: 1
},
desktop: { // 992 px and larger
type: 'list', // can be "grid", "masonry" or "list", default: "list"
showImages: true, // whether to show images in search result, default: true
showSnippet: true, // whether to show text snippet in search result, default: true
showTitle: true, // whether to show title in search result, default: true
showDataPoints: true, // whether to show data points in search result, default: true
showUrl: false, // whether to show link in search result, default: false
gridColsXl: 4, // grid layout column count for devices larger than 1200px, default: 4
gridColsLg: 3 // grid layout column count for devices between 992px and 1199px, default: 3
},
masonryCols: { // how many masonry grid columns to show, minimum width to column count mapping (default: 2 columns below 768px, 3 columns between 768px and 991px, 5 columns between 992px and 1199px and 6 columns above 1200px)
0: 2,
768: 3,
992: 5,
1200: 6
},
singleLineGridTitle: false, // whether to force a single line of the search result title with the 'grid' layout
navigation: {
position: 'top', // navigation "top", "left", or "none"
type: 'tabs', // the navigation layout 'scroll' or 'tabs', for more than 6 (position: 'top') or 10 (position: 'left') content groups the 'scroll' navigation will be used
tabTitle: '#NAME# (#COUNT#)', // e.g. 'Found 43 Recipes for "curry"'
showGroupResultCount: true, // whether to show the count of results in specific content group in the navigation
showAllResultsTab: true, // whether to show an 'All Results' tab,
allResultsTabName: 'All Results', // the name of the 'All Results' tab
allResultsTabTitle: 'All Results (#COUNT#)', // the title of the all results group, e.g. 'Found 43 results for "curry"'
keepOpenTab: true, // whether to reopen the last focused tab on new query
allResultsFirst: true, // whether the all results tab should be displayed as first or last tab
forceCaption: false, // whether to force the caption to be shown in the search result navigation
allResultsStrategy: 'mixed' // how to create the "All results" tab, either 'mixed' (results will be merged together within a single section) or 'grouped' (each result group will have an own section)
},
showListGridToggle: false // whether to display the list/grid layout switch
},
voiceSearch: {
enabled: false, // whether to enable voice search for supported browsers (an microphone icon will be added to your search field if Speech Recognition API is supported)
lang: 'en-US', // the input language (BCP 47 language tag)
repositionTrigger: undefined, // a css selector to an element which triggers a repositioning of the voice search icon once clicked
color: '#333333', // the color of the microphone icon
autoPosition: true // whether to inline the position attributes to the voice search icon
},
filters: {
enabled: false, // whether to generate and show filter options, default: false
position: 'left', // where to place the filter view, one of the following: "top", "left"; "top" - filters will be shown above the search results, "left" - filters will be shown to the left of search results + "show filter" button will be added for mobile devices; default: "left" for embeded or fullscreen search results, "top" otherwise
label: 'Filter', // the label of the filter column, will be also used as screen reader text
showCounts: true, // whether to show result counts for multiple choice filters
showQuickDelete: true, // whether to show a "Quick Delete" bar summarizing active filter options and providing a "delete all" option
deleteAllLabel: 'Reset All', // the label of the "delete all" option
settings: {}, // range filter settings, e.g. {Price: {unit: '$', step: 1, drawHistogram: false, showSlider: true, decimalPlaces: 2, prefix: 'from', postfix: 'to'}, feat: { disableSearch: true, label: 'Features'}}
forceSlideIn: false, // whether to hide the filters by default and only show those once toggled
toggleButtonLabel: 'Filter results', // the label of the filter toggle button
toggleAllButtonLabel: 'All filters', // the label of the button used to display all filters in a drawer
expandedGroupsCount: 6, // number of filter groups to expand by default, following groups will be collapsed (default: 6), set to -1 to expand all filter groups
multiSelectSearchLabel: 'Search #FILTER_NAME#', // the label of multiselect search input
multiSelectEmptyState: 'No matching filter options.', // the message to show if the filter option search yields empty results
multiSelectShowMoreLabel: 'See #COUNT# more',
multiSelectShowLessLabel: 'See fewer options',
multiSelectSearchThreshold: 12, // the minimum number of multiselect options required for the search input to be shown
multiSelectShowMoreThreshold: 12, // the minimum number of multiselect options required for the 'show more options' to be shown
clearGroupLabel: 'clear', // the label of the clear all filter group options button
sliderMinUnitLabel: 'Min #UNIT#', // label of the min val range slider input
sliderMaxUnitLabel: 'Max #UNIT#', // label of the min val range slider input
submitButtonLabel: 'Set', // the label of the submit filter button
dateFormatLocale: undefined, // the locale to use when formatting date string
showOnSingleResult: false, // whether to display filter options ever if those have no influcence on the result set
preSelect: [], // an array of filter options that will be always included in the filters request (unless a filter option from the same group has been selected by the user)
sliderResultCountLabel: '(#COUNT# results)', // the text of a label that shows how many results will be available after applying a range filter
allLabel: 'All', // a label to be displayed with top filters if no option was selected
maxTopCount: 4, // maximum number of filters to show on top, once this is exceeded, a "Show all" button will be added
highlightPopularFilters: true, // whether to also display the top filters above the search results (only applied if filters are displayed on the left)
duplicatePopularFilters: true, // whether the top filters above the search results should be also duplicated on the left
applyAndCloseLabel: 'Apply and Close', // the "Apply" filters label, displayed in the filter drawer
applyButtonLabel: 'Apply', // the "Apply" filter label, only used for filters on top
showBackToTop: false, // whether to show "back to top" button on SERP
backToTopLabel: 'Back to top', // "back to top" button label
fullCategoryTree: false, // whether to always render the full category tree (including branches with only one child)
waitForApply: false // whether to wait for "apply" button to be pressed before applying a filter on top/in a drawer
},
dataPoints: {
include: [], // data points that should be shown in the UI despite being set as hidden
exclude: [], // data points that should not be shown in the UI, array of data point names
single: [], // data points where only the first one should be shown (if multiple values are present), array of data point names
direction: 'row', // the direction of the data point key-value pairs - whether the data points should be shown as a row or as a column (table)
showNames: true, // whether to show data point names
collapseBy: ', ', // the (html) string to be used when merging rows of the structured data table having the same key, default: ', ', e.g. '<br/>', set to null to show data points with the same key in multiple rows
unique: false, // whether to display only unique values
displayType: false, // whether to append the data point type
showOnlyPassOns: false, // whether to show only pass on data points (ecom)
addTypeClass: false, // whether to append the data point type class name to the data point wrapper
namePostfix: ':' // the postfix to add to the data point name
},
subConfigs: { // a map of sub config ids to sub configs, the sub config overrides a zSearchConfig settings by providing the full setting path followign the dot notation, can be used for example for localization, e.g. 'de: {"searchBox.placeholder": "Suchen…"}', set the activeSubConfigId to turn on the desired subconfig (this can also be done after the plugin initializes by calling ZOOVU_SEARCH.changeConfig('activeSubConfigId', 'ID');), set the subconfig id to undefined to use the default zSearchConfig configuration

},
errorScreen: { // error screen configuration
offline: {
title: 'You are offline',
message: 'It seems there\'s a problem with your network. Please check your internet connection.',
tryAgain: 'Try again'
},
blocked: {
title: 'Search request blocked',
message: 'Please check your privacy extensions.',
tryAgain: 'Try again'
},
generic: {
title: 'Oops!',
message: 'Something went wrong. Sorry about that!',
tryAgain: 'Try again'
},
siteId: {
title: 'Site ID missing',
message: 'Please check your configuration code and make sure to provide a valid site ID.',
tryAgain: 'Learn more'
},
ipBlocked: {
title: 'Blocked',
message: 'You are not allowed to use this service.'
}
},
activeSubConfigId: undefined, // the sub config to activate
language: 'en', // the language of search interface, available options: 'en', 'de', 'fr', 'nl', 'pl', 'tr', 'pt', 'es', 'es_mx', 'it', 'da', 'fi', 'sv', 'no', 'lt', 'ro', 'lv', 'el', 'hu', 'cz', 'ru', 'is', 'ko', 'ja'
forceBranding: false, // whether to force the Zoovu Search logo to be shown in the search results/suggestions - use true to always show, 'columbo' to show the same branding as on a COLUMBO plan, or 'free' to show the same branding as on a FREE plan
ecom: false, // whether to use the ecommerce search api
similarContent: {
active: false, // whether to show "Related products" CTA
buttonLabel: 'More like this', // the label of the "Related products" CTA
title: 'More like this', // the label of the "Related products" section after opened
limit: 3, // max number of related products to displayed
displayType: 'inline', // how to display the related products - 'inline', 'layover', or 'drawer'
drawerPosition: 'right', // the drawer position 'left', or 'right'
includeTag: undefined, // the relation type to be included - an object with the following structure {tag: 'relation type name', limitPerRelation: 2}
includeRelation: undefined // the relation to be included - an object with the following structure { id: 42478 }
},
noResultsPage: { // no results page configuration
title: 'We\'re sorry, we couldn\'t find an exact match for "#QUERY#"', // no results page title
description: 'Please try another search term or adjust your query in case there\'s a typo we didn\'t catch.', // no results page copy
content: [ // no results page blocks
{
type: 'searchBox'
},
{
type: 'relatedQueries'
},
{
type: 'fuzzyResults',
title: 'Other results you might find useful',
fallback: {
type: 'popularResults',
title: 'Some of our popular content'
}
},
{
type: 'popularQueries',
title: 'People often search for'
}
]
},
zoe: { // zoe configuration
region: 'eu', // zoe region
showChat: false, // whether to show a "Chat about product" button
locale: 'en-US', // zoe locale
chatLabel: 'Chat', // the "Chat about product" button label
context: {}, // zoe context
title: 'Chat about #PRODUCT_NAME#' // zoe chat dialog title
},
comparison: { // comparison
enabled: false, // whether to show comparison checkbox
showCta: false, // whether to show a cta in the comparison table
tableStyle: 'default', // comparison table style - 'default', or 'zebra'
buttonLabel: 'Compare', // the label of the "Compare" button
tooManyProductsLabel: 'Only first 4 products will be compared.', // an error message to display if too many products are added to comparison
zoeApiKey: undefined, // api key of zoe to be used for comparison
zoeLocale: 'en-US', // locale of zoe to be used for comparison
zoeRegion: 'eu', // region of zoe to be used for comparison
context: {} // context of zoe to be used for comparison
},
miniPDP: {
enabled: false, // whether to show a mini PDP on product click
title: 'Product Details', // mini PDP title
maxImages: 6, // max images to be displayed within the mini PDP
attributesTitle: 'Specifications:', // a heading of the specifications section
goToProductLabel: 'Go to product page', // the label of a "go to product" CTA
showBackdrop: true, // whether to show backdrop
detailsTab: 'Details', // the label of the "details" tab
reviewsTab: 'Reviews', // the label of the "reviews" tab
fullscreen: false, // whether to show the mini pdp as fullscreen
floating: false, // whether to show mini pdp as a floating card
miniPDPForRelations: true, // whether relations opened from mini PDP should trigger another mini PDP
renderBreadcrumb: true, // whether to display a breadcrumb (e.g. if a relation was selected)
backToLabel: 'Back to', // the label of the back to button, product name is appended to the end
picturesTab: 'Pictures', // the label of the "pictures" tab, only used for floating mini pdp
bulletPoints: [], // array of data point names to be highlighted in the "pictures" tab (only floating mini pdp)
chatLabel: 'Ask AI', // the AI chat tab label (Zoe has to be configured)
stateless: false // if set to true, the selected product won't be persisted in query params
},
landingPageAnalyticsQuery: undefined // the query to be displayed in analytics, only relevant for landing pages
}

Callbacks

Callbacks let you hook into different steps of the search process. They give you access to the search API response so you can customize how results are displayed or handled.

Each callback uses the same structure for the search response. The response contains key information such as the original query, filter options, sorting state, and grouped results. This structure stays consistent across all supported callbacks.

{
query: 'q', // the query (content search only)
interpretedQuery: { // the query information (ecom search only)
original: 'q', // the original query
queryWasCorrected: false, // a boolean flag indicating whether a query correction was performed
corrected: undefined // the corrected query
},
suggests: { // the search results, mapping of result group names to array of results (note: for ecom search a single result entry can be an array containing all resolved product variants)
Blog: [
{
link: 'https://myresult.com',
name: 'My Result',
image: 'https://myresult.com/image.jpg',
content: 'I am the search snippet',
type: 'HTML', // either HTML, CUSTOM, or YOUTUBE_VIDEO
html: undefined, // only for CUSTOM results
dataPoints: [
{ key: 'Price', value: '$15', show: true }
],
identifier: undefined // the article number (ecom search only)
}
]
},
totalResultsPerContentGroup: { // a mapping of result group names to number of available results
Blog: 4
},
activeFilterOptions: [
{
key: 'fid#2',
name: 'Author',
values: [ // only for multiselect filters
{
name: 'Exotic',
value: 'exotic'
}
],
min: undefined, // the set min value, only for range filters
max: undefined // the set max value, only for range filters
}
],
filterOptions: [
{
filterType: 'COLLECTION', // the filter type, COLLECTION, DATE, TREE, COLOR, BOOLEAN, RANGE (content search only)
type: 'COLLECTION', // the filter type, COLLECTION, DATE, TREE, COLOR, BOOLEAN, RANGE (ecom search only)
key: 'fid#2',
name: 'Author',
min: undefined, // for range filters
max: undefined, // for range filters
categories: [ // for tree filters (ecom search only)
{
conceptId: 'CONCEPT_ID',
count: 4, // ecom search only
key: '54979',
name: 'Concept Name',
value: 'ROOT/CONCEPT',
viewName: 'Concept Name',
children: []
}
],
values: [
{
key: 'Key',
name: 'Name',
value: 'Value', // ecom search only
count: 1 // ecom search only
}
], // for multiselect filters
counts: { // a mapping of filter value to number of available results (available only for range filters with ecom search, and for all filters with content search)
exotic: 4
}
}
],
sortingOptions: [ // available sorting options (string array for content search, object array for ecom search)
'Date (descending)',
'Date (ascending)',
{
name: 'Preis',
key: '4978',
sort: 'DESC' // ASC or DESC
}
],
sorting: 'Date (descending)', // the active sorting option (content search only)
sortingOrder: 'DESC', // the sorting order, ASC or DESC (content search only)
activeSortingOption: { // the active sorting option (ecom search only)
name: 'Preis',
key: '4978',
sort: 'DESC' // ASC or DESC
},
filterMapping: { // a mapping of result group names to array of filters that should be displayed for the given group (ecom search only)
Products: ['54979']
},
redirect: undefined, // set for redirect mappings, the redirect url
totalResults: 4 // number of all available results
}

Suggest change callback

Use this callback to respond when the list of search suggestions updates. This might happen when the user types in the search box.

The callback gives you a list of updated data sets, which include result groups, search history, or other sets of suggestions.

zSearchConfig.callbacks.suggestChange = (suggestionsVisible : boolean, dataSets : Array<SuggestionDataSet>) => {};

The dataSets argument is an array of retrieved data sets with the following structure:

{
type: 'resultGroup', // the data set type (resultGroup, searchHistory, or dataSet)
data: [
{
title: 'My Result',
link: 'https://myresult.com'
image: 'https://placekitten.com/300/200',
contentGroup: 'Blog',
dataPoints: [
{ key: 'Price', value: '$15', show: true }
]
}
]
}

Pre-render callback

Use this callback to make changes to the search results before they are displayed. You get access to the full search response and the list of results. Any changes you make will show up in the final output.

It's useful if you want to filter or adjust the results before they appear on the page.

zSearchConfig.callbacks.preRender = (suggests : Suggests, data : SearchApiResponse) => {};

Result line callback

Use this callback if you want to modify individual search results after they are turned into DOM elements. It runs once per result and gives you both the result object and its rendered HTML node.

You can use this to add custom classes, badges, or other visual tweaks to specific results.

zSearchConfig.callbacks.suggestLine = (suggest : Suggest, node : HTMLElement) => {};
/*
Sample Suggest Data:
{
link: 'https://myresult.com',
name: 'My Result',
image: 'https://myresult.com/image.jpg',
content: 'I am the search snippet',
type: 'HTML', // either HTML, CUSTOM, or YOUTUBE_VIDEO
html: undefined, // only for CUSTOM results
dataPoints: [
{ key: 'Price', value: '$15', show: true }
],
identifier: undefined // the article number (ecom search only)
}
*/

Results preloaded

This callback runs after a new page of search results is added to the DOM. For example, it runs after someone clicks the “more results” button.

You can use it to track user interactions or to trigger animations or lazy loading scripts.

zSearchConfig.callbacks.resultsPreloaded = (data : SearchApiResponse) => {};

Post search callback

This callback runs after a search is executed. It also runs when users apply filters or sorting options. It does not run on pagination.

Use it if you want to log searches, trigger analytics, or update page content based on the search state.

zSearchConfig.callbacks.postSearch = (data : SearchApiResponse) => {};

Search Result Callback

Use this callback if you want to fully control how search results are rendered. When this is set, the default rendering is skipped. You’ll receive the entire search API response and can use it to build your own layout.

It's useful for advanced customizations or full design overrides.

zSearchConfig.callbacks.searchResult = (data : SearchApiResponse) => {};