Introduction
Your store’s header navigation is more than just links—it’s the roadmap guiding visitors through your brand, products, and content. By leveraging Liquid and a JSON-driven menu configuration, you can build a fully dynamic, easily maintainable header that adapts as your catalog grows. In this guide, you’ll learn how to: store your menu structure in a JSON asset, parse it with Liquid, render nested menus, and add responsive behavior with minimal JavaScript. Along the way, we’ll share best practices, real-world analogies, and expert tips so you can empower merchants to update navigation without touching code. Let’s transform your header from static links into a robust, data-driven navigation system.

Understanding the Architecture
Before writing code, let’s outline the pieces:
- JSON Menu File: A structured asset (e.g.,
assets/header-menu.json
) defining labels, URLs, and submenus. - Liquid Parsing: Use
asset_url
andjson
filters to load and traverse menu items. - Section Markup: A
sections/header.liquid
file that loops through parsed JSON and outputs<nav>
markup. - Styling & Responsiveness: CSS Grid or Flexbox for layout; a simple toggle script for mobile menus.
- Merchant-Friendly: Updating the JSON file (via Theme Editor’s file uploader or Git) instantly reflects in the navigation.
Analogy: Think of your JSON file as a train’s timetable—once updated, all stations (pages) automatically follow the new schedule without rewiring tracks.
Prerequisites and Setup
- Shopify Theme (2.0+): Ensure your theme supports JSON templates and custom sections.
- Shopify CLI (optional): For local editing and live preview (
shopify theme serve
). - Code Editor: VS Code or your preference.
- Basic JS/CSS: Familiarity with vanilla JavaScript and CSS Grid/Flexbox.

Step 1: Defining Your JSON Menu
Create a new asset at assets/header-menu.json
:
jsonCopyEdit{
"items": [
{
"label": "Home",
"url": "/"
},
{
"label": "Shop",
"url": "/collections/all",
"children": [
{ "label": "New Arrivals", "url": "/collections/new" },
{ "label": "Best Sellers", "url": "/collections/best-sellers" }
]
},
{
"label": "About Us",
"url": "/pages/about"
},
{
"label": "Blog",
"url": "/blogs/news"
},
{
"label": "Contact",
"url": "/pages/contact"
}
]
}
items
array: Top-level menu entries.- Optional
children
: Defines nested submenus for dropdowns.
Expert Tip: Keep labels and URLs in sync with your store’s structure. You can export existing menus manually or via API if needed.
Step 2: Loading JSON in Liquid
In sections/header.liquid
, at the top:
liquidCopyEdit{% assign menu_json = 'header-menu.json' | asset_url | fetch %}
{% assign menu = menu_json | parse_json %}
asset_url
: Generates the URL to your JSON file.fetch
(Shopify 2.0+): Retrieves the file’s contents.parse_json
: Converts the string into a Liquid object.
Note: If your theme doesn’t support
fetch
, use thejson
filter on a static string or load viainclude
.
Step 3: Rendering the Navigation Markup
Below your assignments, add:
liquidCopyEdit<nav class="site-header__nav">
<ul class="nav-menu">
{% for item in menu.items %}
<li class="nav-item{% if item.children %} has-children{% endif %}">
<a href="{{ item.url }}" class="nav-link">{{ item.label }}</a>
{% if item.children %}
<ul class="sub-menu">
{% for child in item.children %}
<li class="sub-item">
<a href="{{ child.url }}" class="sub-link">{{ child.label }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
<button class="nav-toggle" aria-expanded="false" aria-label="Toggle navigation">
☰
</button>
</nav>
.nav-menu
contains top-level items..has-children
marks items with dropdowns..nav-toggle
button for mobile view.
Accessibility Note: Always include
aria
attributes on toggles and dropdowns for screen-reader compatibility.
Step 4: Styling the Menu
In assets/header.css
(or your main CSS file):

cssCopyEdit.site-header__nav {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
}
.nav-menu {
display: flex;
gap: 2rem;
list-style: none;
margin: 0;
padding: 0;
}
.nav-item { position: relative; }
.nav-link {
text-decoration: none;
font-weight: 500;
}
.sub-menu {
display: none;
position: absolute;
top: 100%;
left: 0;
list-style: none;
background: white;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
margin: 0;
padding: 1rem 0;
}
.has-children:hover .sub-menu {
display: block;
}
.sub-item { padding: 0.5rem 1rem; }
.sub-link { text-decoration: none; color: #333; }
/* Mobile Styles */
@media (max-width: 768px) {
.nav-menu {
flex-direction: column;
display: none;
background: white;
width: 100%;
position: absolute;
top: 100%;
left: 0;
}
.nav-item { margin: 0; }
.nav-toggle { display: block; }
.nav-menu.active { display: flex; }
}
- Desktop: Flex row, hover-triggered dropdowns.
- Mobile: Hidden by default, toggled via an
active
class.
Best Practice: Use CSS custom properties (e.g.,
--nav-gap: 2rem;
) to let merchants adjust spacing via theme settings.
Step 5: Adding Toggle Behavior with JavaScript
In assets/header.js
:
jsCopyEditdocument.addEventListener('DOMContentLoaded', () => {
const toggle = document.querySelector('.nav-toggle');
const menu = document.querySelector('.nav-menu');
toggle.addEventListener('click', () => {
const expanded = toggle.getAttribute('aria-expanded') === 'true';
toggle.setAttribute('aria-expanded', !expanded);
menu.classList.toggle('active');
});
});
Then include this script in your layout:
liquidCopyEdit{{ 'header.js' | asset_url | script_tag }}
- ARIA updates: Keep
aria-expanded
in sync. active
class: Triggers mobile menu visibility.
Expert Insight: Debounce resize events if you add more complex behaviors to avoid performance hits.
Step 6: Making It Merchant-Friendly
- Expose JSON Filename in Settings: Let merchants switch to a different menu without code edits: jsonCopyEdit
// config/settings_schema.json { "name": "Header Menu", "settings": [ { "type": "text", "id": "menu_json", "label": "Menu JSON Filename", "default": "header-menu.json" } ] }
Then inheader.liquid
: liquidCopyEdit{% assign filename = settings.menu_json | default: 'header-menu.json' %} {% assign menu_json = filename | asset_url | fetch %}
- Use Presets: Provide sample JSON files (e.g.,
header-menu-alt.json
) with different menu structures.

Pro Tip: Validate JSON syntax with a linter before uploading to prevent Liquid parsing errors.
Conclusion
By centralizing your navigation in a JSON asset and harnessing Liquid’s parsing capabilities, you gain a flexible, maintainable header that scales with your store. Merchants can update links, labels, and dropdowns simply by editing or swapping JSON files—no developer intervention required. Pair this with responsive CSS and a lightweight toggle script, and you have a modern, accessible header navigation that delights users on any device. Start small with a basic menu, then layer on advanced features like mega menus or icon-based links to elevate the shopping experience.