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 type | Storage | UX | Managed by |
|---|---|---|---|
| Per-instance (collection handle, title, layout, product count, CTA) | Theme JSON (Liquid block schema) | Shopify theme editor | Merchants or inject script |
| Shared rendering (card templates, CSS, badges, filters) | DDB → metafields | Marqo admin UI | Merchants 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:
- Find all
dynamic-featured-collectionsections in each template - For each one, read its settings (collection, title, layout, product_count, cta_label)
- Create a corresponding
marqo-featured-collectionapp block with those settings mapped into the block's schema - Replace the native section with the Marqo app section in the template order
- Disable or remove the native section
Settings mapping
Native dynamic-featured-collection settings map 1:1 to the Marqo block schema:
| Native setting | Marqo block setting | Notes |
|---|---|---|
collection | collection | Collection handle string |
title | title | Empty string = use collection title |
layout | layout | slideshow or grid |
product_count | product_count | Integer, range 1–50 |
cta_label | cta_label | Empty string = hide button |
cta_button_style | — | Always primary, not needed |
Targeting rules
Add a --blocks featured option (and include in both → becomes all):
- Target: templates with
dynamic-featured-collectionsections (non-main) - Only replace visible (non-disabled) featured collection sections
- Skip the
mainsection (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
| Metric | CA | US |
|---|---|---|
| Templates with featured sections | 71 | 68 |
| Total featured sections | — | 278 |
| Max sections per template | 11 | 11 |
| Layouts used | slideshow, grid | slideshow (188), grid (90) |
| Product count range | 4–17 | 2–50 |
| With custom title | — | 36 / 278 |
| With CTA label | — | 26 / 278 |
| With custom CSS | — | 8 / 278 |
| CTA button style | always primary | always 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(alwaysprimary)
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
| Component | Path |
|---|---|
| Inject script | scripts/ecom/inject_marqo_blocks.py |
| Existing blocks | components/shopify/extensions/marqo-search-theme/blocks/ |
| Loader | components/shopify/extensions/marqo-search-theme/assets/marqo-loader.js |
| Storefront bundle | components/shopify/storefront_search/src/ |
| Settings service | components/shopify/admin_server/admin_server/services/settings_service.py |
| Storefront routes | components/shopify/admin_server/admin_server/routes/storefront_routes.py |
| Collection overrides | components/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 |