Skip to main content

Featured Collections App Block

Context

Muji (and similar merchants) uses Shopify's native dynamic-featured-collection sections to display curated product carousels on collection pages. A single collection template can have 5–11 of these sections, each pointing to a different collection with its own title, layout, and product count.

We already have marqo-results (grid) and marqo-subcollections app blocks that replace native sections. The featured collections block is the third and final block type needed to fully replace native collection page rendering with Marqo-powered components.

The challenge is per-instance configuration — unlike the grid and subcollections blocks (which have no settings), each featured collection block needs its own collection handle, title, layout, and product count. This creates a data ownership question: where does per-block config live, and how do we manage it at scale?

Architecture Decision

Two-tier config model

Config typeStorageUXManaged by
Per-instance (collection handle, title, layout, product count, CTA)Theme JSON (Liquid block schema)Shopify theme editorMerchants or inject script
Shared rendering (card templates, CSS, badges, filters)DDB → metafieldsMarqo admin UIMerchants via admin dashboard

Rationale: Per-instance config is theme layout data — it defines what to show on each page. This is the same category as Shopify's native dynamic-featured-collection settings, which live in theme JSON and are managed in the theme editor. Duplicating this to DDB would create two sources of truth with no reliable sync mechanism (Shopify has no themes/update webhook for template changes, merchants edit blocks in real-time, and multiple themes can exist per store).

Shared rendering config (how cards look, what badges to show, CSS) is already in DDB/metafields and applies globally. This stays as-is.

Theme audit endpoint (read-only)

A new read-only endpoint that reads the Shopify Asset API on-demand to show what featured collection blocks are configured across all templates. No persistent sync — just a point-in-time view.

This provides:

  • Visibility into what's configured without opening the theme editor
  • Data for diagnostics and support
  • Input for programmatic migrations

Liquid Block Schema

{% schema %}
{
"name": "Marqo Featured Collection",
"target": "section",
"settings": [
{
"type": "collection",
"id": "collection",
"label": "Collection"
},
{
"type": "text",
"id": "title",
"label": "Title override",
"info": "Leave blank to use the collection title"
},
{
"type": "select",
"id": "layout",
"label": "Layout",
"options": [
{ "value": "slideshow", "label": "Slideshow" },
{ "value": "grid", "label": "Grid" }
],
"default": "slideshow"
},
{
"type": "range",
"id": "product_count",
"label": "Products to show",
"min": 1,
"max": 50,
"step": 1,
"default": 10
},
{
"type": "text",
"id": "cta_label",
"label": "CTA button text",
"info": "Leave blank to hide the button"
}
]
}
{% endschema %}

The Liquid template reads these settings and passes them as data-* attributes on a placeholder div, similar to how marqo-subcollections.liquid works. The storefront bundle picks them up and renders the component.

Inject Script Extension

The existing scripts/ecom/inject_marqo_blocks.py needs a third block type: featured. The script should:

  1. Find all dynamic-featured-collection sections in each template
  2. For each one, read its settings (collection, title, layout, product_count, cta_label)
  3. Create a corresponding marqo-featured-collection app block with those settings mapped into the block's schema
  4. Replace the native section with the Marqo app section in the template order
  5. Disable or remove the native section

Settings mapping

Native dynamic-featured-collection settings map 1:1 to the Marqo block schema:

Native settingMarqo block settingNotes
collectioncollectionCollection handle string
titletitleEmpty string = use collection title
layoutlayoutslideshow or grid
product_countproduct_countInteger, range 1–50
cta_labelcta_labelEmpty string = hide button
cta_button_styleAlways primary, not needed

Targeting rules

Add a --blocks featured option (and include in both → becomes all):

  • Target: templates with dynamic-featured-collection sections (non-main)
  • Only replace visible (non-disabled) featured collection sections
  • Skip the main section (that's handled by grid/subcollections)
  • Preserve section order position
  • Handle templates with mixed content (featured collections alongside slideshows, rich text, etc.)

Edge cases

  • 8 sections with custom_css (US store) — log these in the report for manual review
  • Disabled sections — skip (merchant intentionally hid them, likely seasonal)
  • Multiple featured collections per template — each gets its own app block; all written in one PUT

Theme Audit Endpoint

Purpose

Read-only endpoint that scans a theme's templates and returns a summary of all Marqo app blocks and their configuration. Useful for support, diagnostics, and pre-migration analysis.

API

GET /api/v1/storefront/shops/{shopify_domain}/theme-audit?theme_id={theme_id}

Response:

{
"theme_id": "154696974526",
"templates_scanned": 190,
"blocks": {
"marqo-results": {
"count": 73,
"templates": ["collection.json", "collection.backpacks-bags.json", "..."]
},
"marqo-subcollections": {
"count": 32,
"templates": ["collection.accessories.json", "..."]
},
"marqo-featured-collection": {
"count": 0,
"templates": []
}
},
"native_featured_collections": {
"count": 278,
"visible": 220,
"disabled": 58,
"with_custom_css": 8,
"templates": [
{
"template": "collection.aroma.json",
"sections": [
{"collection": "essential-oils", "title": "", "layout": "slideshow", "product_count": 10, "disabled": false},
{"collection": "reed-diffusers", "title": "", "layout": "grid", "product_count": 8, "disabled": true}
]
}
]
}
}

Implementation

  • Uses the Shopify Admin REST API (GET /themes/{theme_id}/assets.json) with the shop's stored access token
  • No DDB writes — purely reads theme state
  • Rate-limited to stay under Shopify's 2 req/s Asset API cap
  • Auth: Marqo API key (same as other storefront admin endpoints)

Data from Muji Analysis

Scope across both stores

MetricCAUS
Templates with featured sections7168
Total featured sections278
Max sections per template1111
Layouts usedslideshow, gridslideshow (188), grid (90)
Product count range4–172–50
With custom title36 / 278
With CTA label26 / 278
With custom CSS8 / 278
CTA button stylealways primaryalways primary

Settings keys (consistent across both stores)

Every dynamic-featured-collection section uses exactly 6 settings:

  • collection (handle)
  • title (string, often empty)
  • layout (slideshow | grid)
  • product_count (integer)
  • cta_label (string, often empty)
  • cta_button_style (always primary)

Extension UUID

The Marqo app extension UUID differs per store — the inject script auto-detects it from config/settings_data.json:

  • CA: 019e1b06-6c25-745a-81bb-abeaa6de7f31
  • US: 019e1b06-8a4f-7852-9e7b-8a78fcd3619c

Key File Paths

ComponentPath
Inject scriptscripts/ecom/inject_marqo_blocks.py
Existing blockscomponents/shopify/extensions/marqo-search-theme/blocks/
Loadercomponents/shopify/extensions/marqo-search-theme/assets/marqo-loader.js
Storefront bundlecomponents/shopify/storefront_search/src/
Settings servicecomponents/shopify/admin_server/admin_server/services/settings_service.py
Storefront routescomponents/shopify/admin_server/admin_server/routes/storefront_routes.py
Collection overridescomponents/shopify/storefront_search/src/vue/collection-overrides.ts
Subcollections block (reference)components/shopify/extensions/marqo-search-theme/blocks/marqo-subcollections.liquid
Subcollections Vue component (reference)components/shopify/storefront_search/src/vue/SubcollectionsApp.ts