This is the code reference for adding structured data to your Shopify store. Every snippet below uses Shopify Liquid variables for dynamic data, follows Google's current requirements, and has been validated against the Schema.org specification.
Copy the snippet you need. Paste it in the right template file. Validate. Move on.
For the strategic overview of which schema types to add, see Structured Data for Shopify. For the complete schema markup guide, see the Shopify Schema Markup Guide.
Why JSON-LD Is the Right Format for Shopify
Three structured data formats exist: JSON-LD, Microdata, and RDFa. Google supports all three but explicitly recommends JSON-LD.
JSON-LD lives in a <script> tag, separate from your HTML markup. You add it to your template without touching your page structure. If you update your theme's HTML, the JSON-LD stays intact. If the JSON-LD has errors, your page rendering isn't affected.
Microdata is embedded directly in your HTML elements with itemscope, itemtype, and itemprop attributes. Changing your page layout can break your structured data. It's harder to maintain and more error-prone.
RDFa is similar to Microdata but uses a different attribute system. It supports more complex relationships but adds unnecessary complexity for ecommerce use cases.
For Shopify stores, JSON-LD is the clear choice. It's easier to implement in Liquid templates, easier to maintain across theme updates, and easier to validate. Google processes all three formats identically, so there's no ranking advantage to any format — just a maintenance advantage to JSON-LD.
Where to Add JSON-LD in Shopify
Every JSON-LD snippet goes in a specific template file depending on which pages it applies to.
theme.liquid — Global Schema
What goes here: Organization and WebSite schema. These apply to your entire site, so they belong in the layout file that wraps every page.
Where to paste: Inside the <head> tag, before </head>.
File location: Online Store > Themes > Edit Code > Layout > theme.liquid
Product Templates — Product Schema
What goes here: Product schema with offer, brand, and review data.
Where to paste: At the end of your main product section, before the closing tag.
File location: sections/main-product.liquid (Dawn and modern themes) or templates/product.liquid (older themes)
Collection Templates — ItemList Schema
What goes here: BreadcrumbList for collection hierarchy. Optionally, ItemList for product listings.
File location: sections/main-collection.liquid or templates/collection.liquid
Page Templates — FAQ and Custom Schema
What goes here: FAQPage schema for FAQ pages. LocalBusiness schema for about/contact pages.
File location: templates/page.faq.liquid (custom template) or sections/ for section-based implementations
Article Templates — BlogPosting Schema
What goes here: Article or BlogPosting schema for blog content.
File location: sections/main-article.liquid or templates/article.liquid
Snippets — Reusable Schema Components
For cleaner code organization, create schema as Shopify snippets and include them in templates:
Create: snippets/schema-product.liquid
Include: {% render 'schema-product' %} in your product template
This keeps schema code separate from presentation code and makes updates easier.
Copy-Paste JSON-LD Snippets
Product Schema
The most critical schema type for any Shopify store. This template handles single-variant and multi-variant products, includes brand, images, and conditional fields.
{% comment %}
File: snippets/schema-product.liquid
Include in: sections/main-product.liquid
{% endcomment %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": {{ product.title | json }},
"description": {{ product.description | strip_html | truncate: 5000 | json }},
"url": "{{ shop.url }}{{ product.url }}",
"image": [
{% for image in product.images limit: 5 %}
{{ image | image_url: width: 1200 | prepend: 'https:' | json }}{% unless forloop.last %},{% endunless %}
{% endfor %}
],
"brand": {
"@type": "Brand",
"name": {{ product.vendor | json }}
},
"sku": {{ product.selected_or_first_available_variant.sku | json }},
{% if product.metafields.custom.gtin %}
"gtin13": {{ product.metafields.custom.gtin | json }},
{% endif %}
{% if product.metafields.custom.mpn %}
"mpn": {{ product.metafields.custom.mpn | json }},
{% endif %}
{% if product.metafields.reviews.rating %}
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": {{ product.metafields.reviews.rating.value | json }},
"reviewCount": {{ product.metafields.reviews.rating_count | json }},
"bestRating": "5",
"worstRating": "1"
},
{% endif %}
"offers": {% if product.variants.size > 1 %}{
"@type": "AggregateOffer",
"lowPrice": {{ product.price_min | money_without_currency | json }},
"highPrice": {{ product.price_max | money_without_currency | json }},
"priceCurrency": {{ shop.currency | json }},
"offerCount": {{ product.variants.size }},
"offers": [
{% for variant in product.variants %}
{
"@type": "Offer",
"name": {{ variant.title | json }},
"url": "{{ shop.url }}{{ variant.url }}",
"sku": {{ variant.sku | json }},
"price": {{ variant.price | money_without_currency | json }},
"priceCurrency": {{ shop.currency | json }},
"availability": "https://schema.org/{% if variant.available %}InStock{% else %}OutOfStock{% endif %}",
"itemCondition": "https://schema.org/NewCondition"
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}{% else %}{
"@type": "Offer",
"url": "{{ shop.url }}{{ product.url }}",
"price": {{ product.price | money_without_currency | json }},
"priceCurrency": {{ shop.currency | json }},
"availability": "https://schema.org/{% if product.available %}InStock{% else %}OutOfStock{% endif %}",
"itemCondition": "https://schema.org/NewCondition"
}{% endif %}
}
</script>
For a detailed explanation of every field, see Product Schema for Shopify.
💡 Pro Tip: Analytics Agent automatically tracks all these metrics for you. Install Analytics Agent and get instant insights without the manual work.
Organization Schema
Add this once to theme.liquid. Replace placeholder values with your actual business information.
{% comment %}
File: snippets/schema-organization.liquid
Include in: layout/theme.liquid (inside <head>)
{% endcomment %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": {{ shop.name | json }},
"url": "{{ shop.url }}",
"logo": {
"@type": "ImageObject",
"url": "{{ 'logo.png' | asset_url | prepend: 'https:' }}"
},
"sameAs": [
{% if shop.metafields.custom.instagram_url %}"{{ shop.metafields.custom.instagram_url }}",{% endif %}
{% if shop.metafields.custom.twitter_url %}"{{ shop.metafields.custom.twitter_url }}",{% endif %}
{% if shop.metafields.custom.facebook_url %}"{{ shop.metafields.custom.facebook_url }}"{% endif %}
],
"contactPoint": {
"@type": "ContactPoint",
"contactType": "customer service",
"email": {{ shop.email | json }}
}
}
</script>
Notes:
shop.namepulls from your Shopify store name setting- Replace
'logo.png'with your actual logo filename in assets - Social URLs can be stored in shop metafields or hardcoded if they don't change
BreadcrumbList Schema
Dynamic breadcrumbs based on the current page context. Works on product and collection pages.
{% comment %}
File: snippets/schema-breadcrumb.liquid
Include in: sections/main-product.liquid, sections/main-collection.liquid
{% endcomment %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{ shop.url }}"
}
{% if collection %}
,{
"@type": "ListItem",
"position": 2,
"name": {{ collection.title | json }},
"item": "{{ shop.url }}{{ collection.url }}"
}
{% endif %}
{% if product %}
,{
"@type": "ListItem",
"position": {% if collection %}3{% else %}2{% endif %},
"name": {{ product.title | json }}
}
{% endif %}
]
}
</script>
Notes:
- The last item in the breadcrumb should not have an
itemURL (it's the current page) collectioncontext is available on product pages when accessed via a collection URL- Dawn v15+ themes include breadcrumb schema by default — check before adding
FAQPage Schema
For FAQ pages, product pages with Q&A sections, or any page with question-and-answer content.
{% comment %}
File: snippets/schema-faq.liquid
Include in: templates/page.faq.liquid or product template with FAQ section
{% endcomment %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is your shipping policy?",
"acceptedAnswer": {
"@type": "Answer",
"text": "We offer free standard shipping on orders over $50. Standard delivery takes 3-7 business days. Express shipping is available for $12.99."
}
},
{
"@type": "Question",
"name": "What is your return policy?",
"acceptedAnswer": {
"@type": "Answer",
"text": "We accept returns within 30 days of purchase. Items must be unworn with original tags. Return shipping is free for US orders."
}
},
{
"@type": "Question",
"name": "Do you ship internationally?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes, we ship to over 40 countries. International rates are calculated at checkout. Delivery typically takes 7-14 business days."
}
}
]
}
</script>
For dynamic FAQ schema using metafields:
{% if product.metafields.custom.faq_questions != blank %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{% assign faqs = product.metafields.custom.faq_questions.value %}
{% for faq in faqs %}
{
"@type": "Question",
"name": {{ faq.question | json }},
"acceptedAnswer": {
"@type": "Answer",
"text": {{ faq.answer | strip_html | json }}
}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}
</script>
{% endif %}
For the full FAQ implementation guide, see FAQ Schema for Shopify.
See Analytics Agent in Action
Discover how AI-powered insights can transform your Shopify store.
Article/BlogPosting Schema
For Shopify blog posts. Add to your article template.
{% comment %}
File: snippets/schema-article.liquid
Include in: sections/main-article.liquid or templates/article.liquid
{% endcomment %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": {{ article.title | json }},
"description": {{ article.excerpt_or_content | strip_html | truncate: 300 | json }},
"image": {% if article.image %}{{ article.image | image_url: width: 1200 | prepend: 'https:' | json }}{% else %}""{% endif %},
"url": "{{ shop.url }}{{ article.url }}",
"datePublished": "{{ article.published_at | date: '%Y-%m-%dT%H:%M:%S%z' }}",
"dateModified": "{{ article.updated_at | date: '%Y-%m-%dT%H:%M:%S%z' }}",
"author": {
"@type": "Person",
"name": {{ article.author | json }}
},
"publisher": {
"@type": "Organization",
"name": {{ shop.name | json }},
"logo": {
"@type": "ImageObject",
"url": "{{ 'logo.png' | asset_url | prepend: 'https:' }}"
}
},
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "{{ shop.url }}{{ article.url }}"
}
}
</script>
Notes:
article.published_atandarticle.updated_atmust be formatted as ISO 8601 datesarticle.imagemay be nil if no featured image is set — the conditional handles thispublishershould match your Organization schema
Shopify Liquid Tips for JSON-LD
JSON-LD in Shopify templates is where Liquid templating meets JSON syntax. This intersection creates specific issues that don't occur in standard Liquid or standard JSON.
The | json Filter
This is your most important tool. The | json filter wraps a value in quotes and escapes special characters (quotes, backslashes, newlines). Always use it for string values.
"name": {{ product.title | json }}
Outputs: "name": "Men's Blue \"Oxford\" Shirt" — with the apostrophe and quotes properly escaped.
Without | json:
"name": "{{ product.title }}"
If the product title contains a quote character, the JSON breaks. Use the filter.
Handling Nil/Empty Values
Shopify Liquid returns empty strings or nil for missing data. This can produce invalid JSON:
"gtin13": "",
An empty GTIN is worse than no GTIN. Use conditional checks:
{% if product.metafields.custom.gtin != blank %}
"gtin13": {{ product.metafields.custom.gtin | json }},
{% endif %}
Money Formatting
Shopify's money filter adds currency symbols and formatting. For JSON-LD, you need the raw number:
"price": {{ product.price | money_without_currency | json }}
This outputs "49.99" instead of "$49.99". The currency goes in the separate priceCurrency field.
Note: money_without_currency uses the store's locale settings for decimal separators. If your store is configured for a locale that uses commas as decimal separators (e.g., 49,99), you may need to handle this differently. Google expects a period as the decimal separator.
Image URLs
Shopify's image URLs are protocol-relative by default (//cdn.shopify.com/...). JSON-LD requires absolute URLs:
{{ image | image_url: width: 1200 | prepend: 'https:' | json }}
Always prepend https: to image URLs in schema.
URL Construction
Build full absolute URLs for all url and item properties:
"url": "{{ shop.url }}{{ product.url }}"
Never use relative URLs like /products/blue-shirt in JSON-LD. Google requires the full https://yourstore.com/products/blue-shirt.
Trailing Commas in Loops
The most common Liquid-JSON error. JSON doesn't allow trailing commas, but Liquid loops naturally produce them:
Wrong:
{% for variant in product.variants %}
{ "@type": "Offer", "price": {{ variant.price | money_without_currency | json }} },
{% endfor %}
Correct:
{% for variant in product.variants %}
{ "@type": "Offer", "price": {{ variant.price | money_without_currency | json }} }{% unless forloop.last %},{% endunless %}
{% endfor %}
Validating Your JSON-LD
Every snippet should be validated after implementation. Here's the workflow:
Step 1: Preview in View Source. Before running any tools, view the page source and find the application/ld+json script. Check that the JSON is well-formed — no trailing commas, no unescaped quotes, no empty values where data should be.
Step 2: Google Rich Results Test. Paste the live URL. This validates against Google's specific requirements and tells you which rich results the page qualifies for.
Step 3: Schema.org Validator. For a deeper check against the full Schema.org specification. Catches issues like deprecated properties or incorrect data types that Google's tool might not flag.
Step 4: Check edge cases. Test product pages with:
- No images
- No reviews
- Only one variant
- Out of stock
- No vendor set
Any of these can produce invalid JSON if your conditional logic doesn't handle the edge case.
Step 5: Bulk validation. After confirming individual pages work, validate across your full site. Run a JSON-LD Audit to crawl every page, check every schema block, and flag errors you'd miss testing one page at a time.
💡 Pro Tip: Analytics Agent automatically tracks all these metrics for you. Install Analytics Agent and get instant insights without the manual work.
Common JSON-LD Errors on Shopify
Syntax errors from Liquid output. Liquid produces output that looks fine visually but breaks JSON parsing. The | json filter solves most of these, but test every template.
Missing commas between properties. JSON requires commas between properties. When you use Liquid conditionals to include optional fields, it's easy to end up with missing or extra commas:
{% if product.metafields.custom.gtin %}
"gtin13": {{ product.metafields.custom.gtin | json }},
{% endif %}
"offers": { ... }
If the GTIN metafield is empty, the comma before "offers" is correct. If it's present, the comma after gtin13 is correct. This specific pattern works because the trailing comma after gtin13 leads into "offers". But complex conditional logic can easily produce double commas or missing commas.
Duplicate schema from theme + manual code. Check what your theme already outputs. If Dawn includes Product schema and you add your own Product schema, Google sees two conflicting Product entities. Either extend the existing schema or disable the theme's output and replace it entirely.
Wrong data types. JSON-LD expects specific types. price should be a string representation of a number ("49.99"), not a number (49.99). availability must be a full URL ("https://schema.org/InStock"), not a string ("In Stock").
Relative URLs. Every url, item, and image property must be an absolute URL starting with https://. Shopify Liquid variables like product.url return relative paths — always prepend {{ shop.url }}.
When to Skip Manual JSON-LD Entirely
Writing and maintaining JSON-LD templates works well for stores with a standard setup and a developer available to implement and troubleshoot. But it's not the only option.
Analytics Agent's JSON-LD Audit crawls your site, identifies what schema exists and what's missing, validates against Google's requirements, and can auto-generate corrected schema. For stores where:
- Multiple apps are already adding schema (and possibly conflicting)
- The team doesn't have Liquid development experience
- Products change frequently and schema needs to stay in sync
- You want validated schema without maintaining template code
Run a JSON-LD Audit to see what your current schema looks like, then decide whether to implement manually using the snippets above or use the automated approach.
Quick Reference: Snippet Placement
| Schema Type | Snippet File | Include In | Applies To |
|---|---|---|---|
| Product | snippets/schema-product.liquid |
sections/main-product.liquid |
Product pages |
| Organization | snippets/schema-organization.liquid |
layout/theme.liquid |
All pages |
| BreadcrumbList | snippets/schema-breadcrumb.liquid |
Product + collection templates | Products, collections |
| FAQPage | snippets/schema-faq.liquid |
FAQ page template or product section | FAQ pages, product pages |
| Article | snippets/schema-article.liquid |
sections/main-article.liquid |
Blog posts |
Next Steps
- Check what your theme already includes: view source and search for
application/ld+json - Identify which snippets you need from this guide
- Create snippet files and include them in the appropriate templates
- Validate each page type with the Rich Results Test
- Run a JSON-LD Audit for bulk validation across your site
For the strategic view of which types matter most, read Structured Data for Shopify. For product schema details, see the field-by-field template. For FAQ-specific guidance, see FAQ Schema for Shopify. To understand what these schema types actually earn you in search results, read the Rich Results eligibility guide.
Ready to Unlock Your Analytics Potential?
Connect Analytics Agent to your Shopify store and start making data-driven decisions today.
Get Started Free