JSON-LD for Shopify: Copy-Paste Snippets & Validation

JSON-LD for Shopify: Copy-Paste Snippets & Validation

December 19, 2024

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.name pulls 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 item URL (it's the current page)
  • collection context 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.

Learn More →

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_at and article.updated_at must be formatted as ISO 8601 dates
  • article.image may be nil if no featured image is set — the conditional handles this
  • publisher should 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

  1. Check what your theme already includes: view source and search for application/ld+json
  2. Identify which snippets you need from this guide
  3. Create snippet files and include them in the appropriate templates
  4. Validate each page type with the Rich Results Test
  5. 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