Marqo Storefront Search Widget -- User Guide
Configuration guide for merchants and agencies customizing the Marqo search widget on Shopify storefronts.
1. Getting Started
The Marqo storefront search widget replaces Shopify's default search and collection pages with AI-powered product results. It renders a product grid with filters, sorting, pagination, and customizable product cards directly on your storefront.
What the widget provides
- Product grid with configurable columns (mobile/tablet/desktop), card styling, and image carousels
- Filter sidebar with categorical, range, hierarchical, and stock availability filters
- Sort dropdown with configurable sort options (price, relevance, etc.)
- Pagination with page numbers or continuous scroll
- Product cards with images, titles, vendors, prices, sale badges, star reviews, and CTA buttons
- Grid injections for promo banners, video tiles, and editorial content between products
- DOM events for integrating quickshop modals, analytics, and third-party widgets
How it appears on storefronts
The widget embeds into a container element on search and collection pages. On search pages, it takes over the results area. On collection pages, it replaces the collection product grid. The widget automatically detects the correct container via the Embed Element selector configured in the admin UI.
First-time setup
- Install the Marqo app on your Shopify store
- Open the Marqo admin dashboard
- Navigate to Settings to configure the widget appearance
- Use Live Preview to see changes in real time before publishing
- Save and publish to push settings to your storefront
Customization tiers
The widget supports four tiers of customization, from easiest to most advanced:
| Tier | Mechanism | Who | Where |
|---|---|---|---|
| 1. Dashboard knobs | ~121 UI settings (colors, fonts, layout, toggles) | Non-technical merchant | Admin UI |
| 2. Custom CSS | CSS editor with full selector access | Merchant with CSS knowledge | Admin UI |
| 3. Advanced Template Editor | Per-component HTML/CSS editing | Developer | Admin UI |
| 4. Theme Template Overrides | Vue templates in Shopify theme code | Developer/agency | Shopify theme editor |
Tiers 1 and 2 always apply regardless of which tier provides the HTML template. CSS variables and custom CSS are injected independently.
2. Dashboard Settings (Tier 1)
All settings are accessible from the admin UI. Changes are reflected in the live preview immediately.
Layout
Controls the overall page structure and search bar.
| Setting | Type | What it controls |
|---|---|---|
| Show Search Bar | Toggle | Whether to display the search input above results |
| Search Bar Placeholder | Text | Placeholder text in the search input (e.g., "Search for products") |
| Page Title Template | Text | Template for the results heading. Use {count} for total results and {term} for the search query. Example: {count} results found for "{term}" |
| Results Count Font | Font picker | Font family for the results count text |
| Results Count Size | Number (px) | Font size for the results count text |
| Results Count Weight | Dropdown | Font weight for the results count text |
| Results Count Color | Color picker | Color for the results count text |
Filters
Controls the filter sidebar appearance and behavior.
Filter Items:
| Setting | Type | What it controls |
|---|---|---|
| Field | Dropdown | Which product field to filter on (e.g., productVendor, productType, priceMin, productTags) |
| Label | Text | Display name shown to shoppers (e.g., "Brand", "Price") |
| Type | Dropdown | categorical (checkboxes) or range (min/max inputs with presets) |
| Enabled | Toggle | Show or hide this filter |
| Array Field | Toggle | Enable for fields that contain arrays (like productTags) |
| Max Results | Number | Maximum facet values to show for categorical filters (default: 100) |
| Range Presets | List | Quick-select buttons for range filters (e.g., "Under $50", "$50-$100") |
Filter Behavior:
| Setting | Type | What it controls |
|---|---|---|
| Icon Style | Dropdown | Accordion toggle icon: chevron or plus_minus |
| Show Product Count | Toggle | Show the count of matching products next to each filter option |
| Show Selected Filters | Toggle | Display active filter pills above the results |
| Keep Collapsed | Toggle | Start all filter sections collapsed |
| Show First Expanded | Toggle | Expand the first filter section even when others are collapsed |
| Filter Mode | Dropdown | sidebar (always visible on desktop) or drawer (slide-in panel) |
| Stock Availability Labels | Text fields | Labels for "In Stock" and "Out of Stock" filter options |
Filter Styling:
| Setting | CSS Variable | What it controls |
|---|---|---|
| Background Color | --marqo-filter-bg | Filter sidebar background |
| Border Radius | --marqo-filter-radius | Corner rounding of the filter container |
| Text Color | --marqo-filter-text-color | Color for filter labels and option text |
| Text Font | --marqo-filter-text-font | Font family for filter text |
| Text Size | --marqo-filter-text-size | Font size for filter text |
| Text Weight | --marqo-filter-text-weight | Font weight for filter text |
| Separator Color | --marqo-filter-separator | Color of the divider lines between filter sections. Set to "none" for transparent. |
| Active Indicator Color | --marqo-filter-active-indicator | Highlight color for checked filter options |
| Button Background | --marqo-filter-btn-bg | Background of filter action buttons |
| Button Text Color | --marqo-filter-btn-text | Text color of filter action buttons |
| Button Border Color | --marqo-filter-btn-border | Border color of filter action buttons |
Active Filter Pill Styling:
| Setting | CSS Variable | What it controls |
|---|---|---|
| Background | --marqo-active-filter-bg | Background of active filter pills |
| Text Color | --marqo-active-filter-text | Text color of active filter pills |
| Font Size | --marqo-active-filter-size | Font size of active filter pills |
| Border Radius | --marqo-active-filter-radius | Corner rounding of active filter pills |
Sorting
Controls the sort dropdown above the results grid.
| Setting | Type | What it controls |
|---|---|---|
| Enabled | Toggle | Show or hide the sort dropdown |
| Default Sort | Dropdown | Which sort option is selected by default |
| Sort Options | List | Available sort options with label and enabled toggle. Each has an id (e.g., priceMin-asc), label (e.g., "Price: Low to High"), and enabled toggle. |
Sort Dropdown Styling:
| Setting | CSS Variable | What it controls |
|---|---|---|
| Background | --marqo-sort-dropdown-bg | Dropdown background color |
| Border Color | --marqo-sort-dropdown-border | Dropdown border color |
| Text Color | --marqo-sort-dropdown-text | Dropdown text color |
Product Display
Controls the product grid layout and card appearance.
Layout:
| Setting | CSS Variable | What it controls |
|---|---|---|
| Image Ratio | --marqo-card-image-ratio | Product image aspect ratio: 1:1, 3:4, or 9:16 |
| Column Spacing | --marqo-column-spacing | Gap between product cards in the grid (px) |
| Card Content Spacing | --marqo-card-content-gap | Spacing between elements inside a product card (px) |
| Columns (Mobile) | -- | Number of grid columns on mobile (<480px). Default: 2 |
| Columns (Tablet) | -- | Number of grid columns on tablet (<1024px). Default: 3 |
| Columns (Desktop) | -- | Number of grid columns on desktop. Default: 4 |
| Text Alignment | -- | Alignment of text within product cards: left, center, or right |
Card Styling:
| Setting | CSS Variable | What it controls |
|---|---|---|
| Card Border Radius | --marqo-card-border-radius | Corner rounding of product cards (px) |
| Card Border Width | --marqo-card-border-width | Border thickness of product cards (px) |
| Card Border Color | --marqo-card-border-color | Border color of product cards |
| Card Background | --marqo-card-bg | Background color of product cards |
| Card Shadow | --marqo-card-shadow | Box shadow preset: none, subtle, medium, or strong |
| Card Hover Effect | --marqo-card-hover-shadow, --marqo-card-hover-transform, --marqo-card-image-hover-transform | Hover behavior: none, shadow (shadow on hover), lift (card lifts up), or zoom (image zooms) |
Text Styles:
Five text groups with four properties each:
| Group | CSS Variable Prefix | What it styles |
|---|---|---|
| Vendor | --marqo-vendor-* | Product vendor/brand name |
| Title | --marqo-title-* | Product title |
| Price | --marqo-price-* | Product price |
| Variant | --marqo-variant-* | Variant title (e.g., "Large / Blue") |
| Collections | --marqo-collections-* | Collection names |
Each group has these properties:
| Property | CSS Variable Suffix | What it controls |
|---|---|---|
| Font Family | -font | Font family (e.g., inherit, "Helvetica Neue") |
| Font Size | -size | Font size in px |
| Font Weight | -weight | Font weight (e.g., 400, 500, 600, 700) |
| Color | -color | Text color |
Sale Badge:
| Setting | CSS Variable | What it controls |
|---|---|---|
| Enabled | -- | Show or hide the sale badge on discounted products |
| Position | -- | Badge position: top-left or top-right |
| Text Format | -- | Badge text template. Use {percent} for the discount percentage. Example: SAVE {percent}% |
| Background Color | --marqo-sale-badge-bg | Badge background color |
| Text Color | --marqo-sale-badge-text | Badge text color |
| Border Radius | --marqo-sale-badge-radius | Corner rounding of the badge (px) |
Price Display:
| Setting | CSS Variable | What it controls |
|---|---|---|
| Sale Price Color | --marqo-price-sale-color | Color of the sale price (shown when item is on sale) |
| Compare-At Price Color | --marqo-price-compare-color | Color of the original price with strikethrough |
| Show Compare-At Price | -- | Whether to show the original price when an item is on sale |
Image Carousel:
| Setting | Type | What it controls |
|---|---|---|
| Enabled | Toggle | Enable prev/next arrow buttons for cycling through product images |
| Show Arrows | Dropdown | hover (arrows appear on mouse hover) or always (arrows always visible) |
| Arrow Style | Dropdown | Visual style of the carousel arrows: chevron or arrow |
CTA (Call to Action)
Controls the button on each product card.
| Setting | CSS Variable | What it controls |
|---|---|---|
| Enabled | -- | Show or hide the CTA button |
| Behavior | -- | navigate (link to product page) or add_to_cart (add to Shopify cart) |
| Button Text | -- | Default button text (e.g., "Shop Now", "Add to Cart") |
| Adding Text | -- | Text shown while adding to cart (e.g., "Adding...") |
| Added Text | -- | Text shown after successful add (e.g., "Added!") |
| Sold Out Text | -- | Text shown when product is sold out |
| Background Color | --marqo-cta-bg | Button background color |
| Text Color | --marqo-cta-text | Button text color |
| Border Color | --marqo-cta-border-color | Button border color |
| Border Width | --marqo-cta-border-width | Button border thickness (px) |
| Border Radius | --marqo-cta-border-radius | Button corner rounding (px) |
| Full Width | --marqo-cta-width | Whether the button spans the full card width |
Pagination
Controls how shoppers navigate between pages of results.
| Setting | Type | What it controls |
|---|---|---|
| Mode | Dropdown | paging (numbered page buttons) or continuous_scroll (infinite scroll) |
| Items Per Page | Number | Number of products per page |
| Show Per-Page Dropdown | Toggle | Let shoppers choose how many products per page |
| Per-Page Options | List | Available values for the per-page dropdown (e.g., 12, 24, 36, 48, 60) |
Pagination Dropdown Styling:
| Setting | CSS Variable | What it controls |
|---|---|---|
| Background | --marqo-page-dropdown-bg | Items-per-page dropdown background |
| Border Color | --marqo-page-dropdown-border | Items-per-page dropdown border color |
| Text Color | --marqo-page-dropdown-text | Items-per-page dropdown text color |
Reviews
Controls the product review stars and integration with third-party review providers.
| Setting | CSS Variable | What it controls |
|---|---|---|
| Enabled | -- | Show or hide review stars on product cards |
| Provider | -- | Review data source: okendo, trustpilot, or custom |
| Metafield Namespace | -- | Shopify metafield namespace for custom review data |
| Review Count Key | -- | Metafield key for the review count |
| Review Average Key | -- | Metafield key for the average rating |
| Star Color | --marqo-star-color | Color of filled review stars |
| Star Size | --marqo-star-size | Size of review stars (px) |
| Review Text Color | --marqo-review-text-color | Color of the review count text |
| Show Count | -- | Whether to display the review count number (e.g., "(42)") next to stars |
When using Okendo or Trustpilot, the widget renders fallback star ratings from your indexed review data. After the page loads, the provider's own widget hydrates and replaces the fallback with their interactive component.
States
Controls the loading, error, and no-results states.
| State | Settings | What it controls |
|---|---|---|
| Loading | Enabled, Message | What to show while search results are loading |
| Error | Enabled, Message | What to show when the search request fails |
| No Results | Enabled, Message | What to show when a search returns zero results |
Selectors
Controls where the widget renders and which pages it runs on.
| Setting | Type | What it controls |
|---|---|---|
| Embed Element | CSS selector | Where to inject the widget on the page. Example: #MainContent, #main-content, #main |
| Excluded Paths | List of URL paths | Pages where the widget should NOT run. Example: /cart, /account, /pages/about |
3. Custom CSS (Tier 2)
The Custom CSS editor in the admin UI lets you write CSS that applies on top of the dashboard settings. Your CSS is injected after the widget's default styles, so it takes precedence.
How to use the CSS editor
- Navigate to Settings > Custom CSS in the admin UI
- Write CSS rules targeting
.marqo-*classes - Preview changes in real time
- Save to publish
Complete CSS variable reference
All CSS variables are scoped to .marqo-search-layout. You can override any variable in your custom CSS:
.marqo-search-layout {
--marqo-card-border-radius: 12px;
--marqo-title-color: #333333;
--marqo-cta-bg: #ff6600;
}
Sale Badge:
| Variable | Default | Controls |
|---|---|---|
--marqo-sale-badge-bg | #dc2626 | Badge background |
--marqo-sale-badge-text | #ffffff | Badge text color |
--marqo-sale-badge-radius | 4px | Badge border radius |
CTA Button:
| Variable | Default | Controls |
|---|---|---|
--marqo-cta-bg | #000000 | Button background |
--marqo-cta-text | #ffffff | Button text color |
--marqo-cta-border-color | #000000 | Button border color |
--marqo-cta-border-width | 0px | Button border width |
--marqo-cta-border-radius | 0px | Button border radius |
--marqo-cta-width | 100% | Button width (100% or auto) |
Product Card:
| Variable | Default | Controls |
|---|---|---|
--marqo-card-border-radius | 8px | Card corner rounding |
--marqo-card-border-width | 1px | Card border thickness |
--marqo-card-border-color | #e2e8f0 | Card border color |
--marqo-card-bg | #ffffff | Card background |
--marqo-card-shadow | none | Card box shadow |
--marqo-card-hover-shadow | 0 4px 12px rgba(0,0,0,0.08) | Shadow on hover |
--marqo-card-hover-transform | none | Transform on hover (e.g., translateY(-2px) for lift) |
--marqo-card-image-hover-transform | none | Image transform on hover (e.g., scale(1.05) for zoom) |
--marqo-column-spacing | 16px | Gap between cards |
--marqo-card-content-gap | 6px | Spacing inside card content |
--marqo-card-image-ratio | 1/1 | Image aspect ratio |
Price:
| Variable | Default | Controls |
|---|---|---|
--marqo-price-sale-color | #dc2626 | Sale price color |
--marqo-price-compare-color | #9ca3af | Compare-at (original) price color |
--marqo-price-font | inherit | Price font family |
--marqo-price-size | 16px | Price font size |
--marqo-price-weight | 700 | Price font weight |
--marqo-price-regular-color | #059669 | Regular (non-sale) price color |
Text Styles:
| Variable | Default | Controls |
|---|---|---|
--marqo-vendor-font | inherit | Vendor font family |
--marqo-vendor-size | 12px | Vendor font size |
--marqo-vendor-weight | 500 | Vendor font weight |
--marqo-vendor-color | #64748b | Vendor text color |
--marqo-title-font | inherit | Title font family |
--marqo-title-size | 14px | Title font size |
--marqo-title-weight | 600 | Title font weight |
--marqo-title-color | #1e293b | Title text color |
--marqo-variant-font | inherit | Variant title font family |
--marqo-variant-size | 12px | Variant title font size |
--marqo-variant-weight | 400 | Variant title font weight |
--marqo-variant-color | #9ca3af | Variant title text color |
--marqo-collections-font | inherit | Collections font family |
--marqo-collections-size | 11px | Collections font size |
--marqo-collections-weight | 400 | Collections font weight |
--marqo-collections-color | #b0b8c4 | Collections text color |
Filters:
| Variable | Default | Controls |
|---|---|---|
--marqo-filter-bg | #ffffff | Filter sidebar background |
--marqo-filter-radius | 8px | Filter container border radius |
--marqo-filter-text-color | #1e293b | Filter text color |
--marqo-filter-text-size | 14px | Filter text font size |
--marqo-filter-text-font | inherit | Filter text font family |
--marqo-filter-text-weight | 500 | Filter text font weight |
--marqo-filter-separator | #e2e8f0 | Filter section separator color |
--marqo-filter-active-indicator | #3b82f6 | Active filter indicator color |
--marqo-filter-btn-bg | #ffffff | Filter button background |
--marqo-filter-btn-text | #1e293b | Filter button text color |
--marqo-filter-btn-border | #e2e8f0 | Filter button border color |
Active Filter Pills:
| Variable | Default | Controls |
|---|---|---|
--marqo-active-filter-bg | #f3f4f6 | Pill background |
--marqo-active-filter-text | #374151 | Pill text color |
--marqo-active-filter-size | 12px | Pill font size |
--marqo-active-filter-radius | 4px | Pill border radius |
Sort Dropdown:
| Variable | Default | Controls |
|---|---|---|
--marqo-sort-dropdown-bg | #ffffff | Sort dropdown background |
--marqo-sort-dropdown-border | #e2e8f0 | Sort dropdown border |
--marqo-sort-dropdown-text | #1e293b | Sort dropdown text |
Pagination Dropdown:
| Variable | Default | Controls |
|---|---|---|
--marqo-page-dropdown-bg | #ffffff | Per-page dropdown background |
--marqo-page-dropdown-border | #e2e8f0 | Per-page dropdown border |
--marqo-page-dropdown-text | #1e293b | Per-page dropdown text |
Results Count:
| Variable | Default | Controls |
|---|---|---|
--marqo-results-font | inherit | Results count font family |
--marqo-results-size | 14px | Results count font size |
--marqo-results-weight | 400 | Results count font weight |
--marqo-results-color | #6b7280 | Results count text color |
Reviews:
| Variable | Default | Controls |
|---|---|---|
--marqo-star-color | #f59e0b | Star rating fill color |
--marqo-star-size | 16px | Star rating size |
--marqo-review-text-color | #6b7280 | Review count text color |
Responsive Grid (dynamic):
| Variable | Context | Controls |
|---|---|---|
--marqo-grid-columns | Set per breakpoint | Current column count. Used internally to clamp grid injection span. |
Common CSS examples
Rounded product cards with soft shadow:
.marqo-search-layout {
--marqo-card-border-radius: 16px;
--marqo-card-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
--marqo-card-border-width: 0px;
}
Custom CTA button styling:
.marqo-search-layout {
--marqo-cta-bg: #2563eb;
--marqo-cta-text: #ffffff;
--marqo-cta-border-radius: 24px;
--marqo-cta-border-width: 0px;
}
Hide vendor name and collection text:
.marqo-vendor { display: none; }
.marqo-collections { display: none; }
Custom filter sidebar width:
.marqo-filter-sidebar {
width: 280px;
min-width: 280px;
}
Style the sale badge:
.marqo-sale-badge {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 4px 10px;
}
Custom pagination button style:
.marqo-pagination-button.marqo-active {
background: #2563eb;
color: #ffffff;
border-radius: 50%;
}
4. Advanced Template Editor (Tier 3)
The Advanced Template Editor lets you customize the HTML and CSS of individual components. Each component has its own HTML template and CSS stylesheet stored in the settings record.
Per-component editing
Each component can be edited independently. Changing the product card template does not affect filters, pagination, or other components.
Available template variables
Product card templates have access to these variables:
| Variable | Type | Description |
|---|---|---|
{{ title }} | string | Decoded product title |
{{ vendor }} | string | Product vendor/brand name |
{{ price }} | string | Formatted current price (e.g., "$29.99") |
{{ compareAtPrice }} | string | Formatted original price when on sale (empty if not on sale) |
{{ productUrl }} | string | Product page URL (e.g., /products/baked-starter-kit) |
{{ imageUrl }} | string | Current display image URL (carousel-aware) |
{{ salePercent }} | number | Discount percentage (0 if not on sale) |
{{ saleBadgeLabel }} | string | Formatted sale badge text (e.g., "SAVE 30%") |
{{ saleBadgePosition }} | string | Badge position: top-left or top-right |
{{ reviewAverage }} | number | Average star rating (0 if no reviews) |
{{ reviewCount }} | number | Number of reviews |
{{ reviewPercentage }} | number | Rating as percentage of 5 stars (for star fill width) |
{{ ctaEnabled }} | boolean | Whether the CTA button is enabled |
{{ ctaButtonText }} | string | CTA button text |
{{ shopifyProductId }} | string | Shopify product ID |
{{ productId }} | string | Marqo product/variant ID |
All raw product fields from the search index are also available (e.g., {{ product.productTags }}, {{ product.variantTitle }}).
Available methods
Templates can use these methods with Vue event bindings:
| Method | Syntax | Description |
|---|---|---|
addToCart(variantId, qty) | @click="addToCart(product._id, 1)" | Add to Shopify cart via /cart/add.js |
formatPrice(cents) | {{ formatPrice(priceValue) }} | Format a number as currency |
carouselPrev() | @click.prevent="carouselPrev" | Navigate to previous image |
carouselNext() | @click.prevent="carouselNext" | Navigate to next image |
Vue directive reference
The template engine is Vue.js. These directives are available:
| Directive | Purpose | Example |
|---|---|---|
v-if | Conditionally render an element | <span v-if="salePercent">On Sale!</span> |
v-else | Else branch for v-if | <span v-else>Regular Price</span> |
v-for | Loop over a list | <li v-for="tag in product.productTags">{{ tag }}</li> |
v-show | Toggle visibility (CSS display) | <div v-show="hasMultipleImages"> |
:attr (v-bind) | Dynamic attribute binding | <img :src="imageUrl" :alt="title" /> |
@event (v-on) | Event listener binding | <button @click="carouselNext">Next</button> |
v-html | Render raw HTML (use with caution) | <div v-html="customContent"></div> |
{{ expr }} | Text interpolation | <span>{{ price }}</span> |
Validation warnings
The editor validates your template and shows warnings for:
- Missing required CSS classes (e.g.,
.marqo-product-card) - Invalid Vue syntax
- References to undefined variables
Reset to default
Each component has a "Reset to Default" button that restores the original template. This does not affect other components.
5. Grid Injections
Grid injections let you insert non-product content (promo banners, video tiles, editorial blocks) at configurable positions within the product results grid.
How it works
Injections are siblings of product cards in the CSS grid. They appear between product cards at positions you specify.
+----------+ +----------+ +----------+ +----------+
|Product 1 | |Product 2 | |Product 3 | |Product 4 |
+----------+ +----------+ +----------+ +----------+
+-----------------------------------------------------+
| Grid Injection (position: 4, span: 4) |
| "Complete Your Routine -- Shop Routines" |
+-----------------------------------------------------+
+----------+ +----------+ +----------+ +----------+
|Product 5 | |Product 6 | |Product 7 | |Product 8 |
+----------+ +----------+ +----------+ +----------+
Configuration
- Go to Settings > Grid Injections in the admin UI
- Click Add Content Block
- Configure each injection:
| Setting | Type | What it controls |
|---|---|---|
| Position | Number | Where to insert. 0 = before first product, 4 = after the 4th product. Relative to the current page. |
| Column Span | Number (1-4) | How many grid columns to span. 4 = full-width banner (for a 4-column grid). Clamped to the current column count at each breakpoint. |
| HTML | Code editor | The content HTML |
| CSS | Code editor | Styling for the content |
| Enabled | Toggle | Show or hide this injection |
Position behavior
- Position 0: Before the first product card
- Position N: After the Nth product card
- Position > result count: Placed at the end of the grid
- Negative positions: Clamped to 0
- Positions are relative to the current page's result set, not the global result set
Column span
The span value controls how many grid columns the injection stretches across. It is automatically clamped at each breakpoint so it never exceeds the current column count. For example, span: 4 on a 2-column mobile layout becomes span: 2.
CSS scoping
Injection CSS is injected into a dedicated <style id="marqo-grid-injection-styles"> element. Use specific selectors scoped to your injection content:
/* Good -- scoped to injection content */
.marqo-grid-injection .my-banner { padding: 24px; }
/* Avoid -- affects the entire page */
.banner { padding: 24px; }
Examples
Static promo banner (full width):
Position: 4, Span: 4
HTML:
<div class="routine-banner">
<img src="https://cdn.shopify.com/s/files/banner.jpg"
alt="Complete Your Routine" />
<a href="/collections/routines">Shop Routines →</a>
</div>
CSS:
.routine-banner {
text-align: center;
padding: 32px 24px;
background: #faf5f0;
border-radius: 8px;
}
.routine-banner img {
max-width: 100%;
border-radius: 4px;
margin-bottom: 12px;
}
.routine-banner a {
color: #000;
font-weight: 600;
text-decoration: underline;
}
Video tile (Videowise integration):
Position: 8, Span: 2
HTML:
<div class="videowise-container" data-videowise-id="abc123"></div>
Then in theme.liquid, add a script to hydrate the video after the grid renders:
<script>
document.addEventListener('marqo:grid.injected', function() {
if (window.Videowise) window.Videowise.init();
});
</script>
Editorial content block:
Position: 12, Span: 2
HTML:
<div class="editorial">
<h3>Expert Tips</h3>
<p>Our makeup artists recommend starting with primer for all-day wear.</p>
<a href="/blogs/tips">Read More</a>
</div>
Limitations
- No template variables: Unlike product card templates, injection HTML is static. It does not have access to
{{ product }}data or search context. For dynamic content, use DOM events to populate containers after render. - No
<script>execution: HTML inserted viainnerHTMLdoes not execute<script>tags. Use theme-level scripts with DOM event listeners instead. - CSS is global: Injection CSS is not automatically scoped. Use specific class selectors.
- Sanitization: Inline event handlers (
onclick,onerror, etc.),javascript:URIs, and dangerous tags (<script>,<iframe>,<object>,<embed>,<form>) are stripped for security.
6. DOM Events
The widget emits CustomEvents on document at key lifecycle points. Listen for these events from a <script> tag in your Shopify theme to add custom behavior.
Event reference
| Event | When it fires | Cancelable | Key Payload Fields |
|---|---|---|---|
marqo:ready | Widget initialized | No | {} |
marqo:destroy | Widget being torn down | No | {} |
marqo:search.start | Search request about to fire | No | { query, collection? } |
marqo:search.results | Results received and rendered | No | { products, query, total, facets, container } |
marqo:search.empty | Zero results returned | No | { query } |
marqo:search.error | Search request failed | No | { error } |
marqo:card.render | Each product card rendered (fires per card) | No | { product, element, index } |
marqo:cta.click | CTA button clicked | Yes | { product, handle, productUrl, shopifyProductId, element } |
marqo:filter.change | Filter value changed | No | { field, type, value } |
marqo:filter.clear | All filters cleared | No | {} |
marqo:sort.change | Sort option changed | No | { sort } |
marqo:page.change | Page navigation | No | { page } |
marqo:grid.injected | Grid injections rendered | No | { injections } |
All events have bubbles: true. Only marqo:cta.click is cancelable.
Listening for events
Add a <script> tag to your theme.liquid before </body>:
<script>
document.addEventListener('marqo:search.results', function(e) {
console.log('Search complete:', e.detail.total, 'results for', e.detail.query);
});
</script>
Event payload details
marqo:search.start
{
query: string; // User's search query (empty string for collection pages)
collection?: string; // Collection name (only on collection pages)
}
marqo:search.results
{
products: MarqoSearchHit[]; // Array of product result objects
query: string; // Search query
total: number; // Total result count
facets: object; // Facet/filter data
container: HTMLElement; // The results grid DOM element
}
marqo:card.render
{
product: MarqoSearchHit; // Product data for this card
element: HTMLElement; // The card's DOM element
index: number; // Position in the results (0-indexed)
}
marqo:cta.click
{
product: MarqoSearchHit; // Full product data
handle: string; // Product handle (e.g., "baked-starter-kit")
productUrl: string; // "/products/baked-starter-kit"
shopifyProductId: string; // Shopify product ID
element: HTMLElement; // The CTA button DOM element
}
marqo:filter.change
{
field: string; // Filter field name (e.g., "productVendor")
type: string; // Filter type ("categorical" or "range")
value: any; // Selected value(s)
}
Cancelable events
marqo:cta.click is the only cancelable event. Calling e.preventDefault() stops the default navigation to the product detail page:
<script>
document.addEventListener('marqo:cta.click', function(e) {
e.preventDefault(); // Stop navigation to PDP
console.log('CTA clicked for:', e.detail.handle);
});
</script>
Use case: Quickshop modal
Bridge the CTA click to your theme's quickshop/quick-add component:
<script>
document.addEventListener('marqo:cta.click', function(e) {
e.preventDefault();
// Create or find the quickshop modal (theme-specific web component)
var modal = document.querySelector('#MarqoQuickAdd');
if (!modal) {
document.body.insertAdjacentHTML('beforeend',
'<quick-add-modal id="MarqoQuickAdd" class="quick-add-modal">' +
'<div role="dialog" class="quick-add-modal__content global-settings-popup" tabindex="-1">' +
'<button type="button" class="quick-add-modal__toggle" aria-label="Close">×</button>' +
'<div class="quick-add-modal__content-info"></div>' +
'</div>' +
'</quick-add-modal>'
);
modal = document.querySelector('#MarqoQuickAdd');
}
e.detail.element.setAttribute('data-product-url', e.detail.productUrl);
modal.show(e.detail.element);
});
</script>
Use case: Analytics integration
Track search and click events with your analytics provider:
<script>
document.addEventListener('marqo:search.results', function(e) {
analytics.track('search', {
query: e.detail.query,
resultCount: e.detail.total,
});
});
document.addEventListener('marqo:cta.click', function(e) {
analytics.track('product_click', {
handle: e.detail.handle,
position: e.detail.index,
});
});
</script>
Use case: Third-party widget hydration
Re-initialize Okendo, Trustpilot, Videowise, or other third-party widgets after Marqo renders new content:
<script>
document.addEventListener('marqo:search.results', function() {
// Re-initialize Okendo widgets on new cards
if (window.oke) window.oke.initWidget();
});
document.addEventListener('marqo:grid.injected', function() {
// Hydrate Videowise containers injected into the grid
if (window.Videowise) window.Videowise.init();
});
</script>
Use case: Custom card modifications
Modify product cards after they render (e.g., add custom badges):
<script>
document.addEventListener('marqo:card.render', function(e) {
var product = e.detail.product;
var element = e.detail.element;
// Add "New" badge to products tagged as new
if (product.productTags && product.productTags.includes('new')) {
var badge = document.createElement('span');
badge.className = 'custom-new-badge';
badge.textContent = 'NEW';
element.querySelector('.marqo-product-card-image').appendChild(badge);
}
});
</script>
7. Theme Template Overrides (Tier 4)
Theme template overrides let developers place custom Vue templates directly in the Shopify theme code. The widget detects these templates and uses them instead of the admin UI templates.
How it works
Place a <script> tag with a specific ID and type="text/template" in your theme.liquid file (before </body>). The widget checks for these override tags before rendering each component.
{% raw %} wrapping requirement
Vue's {{ variable }} syntax conflicts with Shopify Liquid's {{ variable }} syntax. You must wrap Vue templates in {% raw %}{% endraw %} to prevent Liquid from evaluating Vue expressions:
<script id="marqo-product-card-template" type="text/template">
{% raw %}
<div class="marqo-product-card">
<h3>{{ title }}</h3>
<span>{{ price }}</span>
</div>
{% endraw %}
</script>
Without {% raw %}, Liquid would try to evaluate {{ title }} as a Liquid variable (producing empty string) before Vue ever sees it.
Supported override IDs
| Script ID | Overrides |
|---|---|
marqo-product-card-template | Product card HTML |
marqo-filters-template | Filter sidebar |
marqo-pagination-template | Pagination controls |
marqo-sort-template | Sort dropdown |
marqo-loading-template | Loading state |
marqo-error-template | Error state |
marqo-no-results-template | No results state |
Per-component independence
Each component checks for its own theme override independently. You can override product cards while letting filters, pagination, and sort use the admin UI templates:
<!-- Only override the product card -- everything else uses admin UI settings -->
<script id="marqo-product-card-template" type="text/template">
{% raw %}
<div class="marqo-product-card">
<span v-if="salePercent" class="marqo-sale-badge">SAVE {{ salePercent }}%</span>
<a :href="productUrl">
<img :src="imageUrl" :alt="title" />
<h3>{{ title }}</h3>
<span>{{ price }}</span>
</a>
<button v-if="ctaEnabled" @click="addToCart(product._id)">
{{ ctaButtonText }}
</button>
</div>
{% endraw %}
</script>
Fallback chain
The widget resolves templates with this priority:
Tier 4: Theme override (Shopify theme snippet) <-- checked first, wins if present
|
v (if not found)
Tier 3: Advanced Template Editor (admin UI) <-- editable from admin UI
|
v (if not found)
Tier 2: Custom CSS (additive -- always applied)
|
v (applied on top of)
Tier 1: Dashboard knobs (CSS variables -- always applied)
Tiers 1 and 2 always apply regardless of which tier provides the HTML template. CSS variables and custom CSS are injected independently.
Support boundary
Following industry standard practice:
| Tier | Support level |
|---|---|
| 1. Dashboard knobs | Fully supported |
| 2. Custom CSS | Supported (basic guidance) |
| 3. Advanced Template Editor | Supported for default templates. Custom edits are your responsibility. |
| 4. Theme template overrides | Unsupported -- "at your own discretion" |
Custom template work falls outside the scope of the Marqo support team. Using custom templates is at your own discretion.
8. Troubleshooting
Blank results after filter click
Symptom: Clicking a filter clears the product grid and shows nothing.
Possible causes:
- The filter field name does not match the actual Marqo index field. Check the Field value in filter settings matches the indexed field name exactly (case-sensitive).
- The filter is configured as
categoricalbut the field contains numeric data (or vice versa). Check the filter Type setting. - The filter field is an array type (like
productTags) but Array Field is not enabled.
Fix: Verify filter field names match your index schema. Check the browser console for error messages from [Marqo Search].
Loading stuck / spinner never clears
Symptom: The loading spinner appears and never goes away.
Possible causes:
- The Marqo API is unreachable. Check the browser Network tab for failed requests.
- The embed element selector does not match any element on the page. Verify the Embed Element CSS selector is correct.
- The page URL matches an excluded path. Check the Excluded Paths setting.
Fix: Open the browser developer console and look for [Marqo Search] error messages. Check the Network tab for API request failures.
CSP (Content Security Policy) errors
Symptom: Browser console shows Content Security Policy errors. Widget may not render.
Possible causes:
- The storefront's CSP policy blocks inline styles or scripts.
- The widget injects CSS via
<style>elements, which requiresstyle-src 'unsafe-inline'or a nonce.
Fix: Review your Shopify theme's CSP headers. The widget uses runtime-only Vue (no eval() or template compilation at runtime), which is CSP-safe for scripts. However, inline styles may need CSP adjustment.
Grid injections not appearing
Symptom: Grid injections configured in the admin UI do not appear on the storefront.
Possible causes:
- The injection's Enabled toggle is off.
- The injection Position is larger than the number of results on the page.
- The injection HTML is empty.
Fix: Verify the injection is enabled and has HTML content. Listen for the marqo:grid.injected event in the console to confirm the injection is being processed.
Reviews not showing (Okendo / Trustpilot)
Symptom: Star ratings show but the third-party review widget does not hydrate.
Possible causes:
- The review provider's script has not loaded yet when the widget renders.
- The
data-oke-reviews-product-idattribute format does not match what Okendo expects.
Fix: The widget waits 1 second before attempting to hydrate review widgets. If the provider loads later, listen for marqo:search.results and call the provider's init function:
<script>
document.addEventListener('marqo:search.results', function() {
if (window.oke) window.oke.initWidget();
if (window.Trustpilot) {
document.querySelectorAll('[data-trustpilot-widget]').forEach(function(el) {
window.Trustpilot.loadFromElement(el);
});
}
});
</script>
Debug mode: Monitor all Marqo events
Paste this snippet in the browser console to log every Marqo event as it fires:
[
'marqo:ready', 'marqo:destroy',
'marqo:search.start', 'marqo:search.results', 'marqo:search.empty', 'marqo:search.error',
'marqo:card.render', 'marqo:cta.click',
'marqo:filter.change', 'marqo:filter.clear', 'marqo:sort.change', 'marqo:page.change',
'marqo:grid.injected'
].forEach(name => {
document.addEventListener(name, (e) => {
console.log(`%c${name}`, 'color: #3b82f6; font-weight: bold', e.detail);
});
});
Then interact with the search page. Events appear in blue in the console with their payloads.
Note: marqo:ready fires once on widget init. If you paste the listener after the page has loaded, you will not see it. Refresh the page after pasting to catch it.