Your product page looks great, but Google still isn't showing rich results. Structured data is often the missing piece. Valid schema markup helps search engines understand what your products, business, FAQs, and page hierarchy actually are. It doesn't guarantee rankings or rich results, but without it, you may not be eligible at all.
Key Takeaways
- ✓ JSON-LD injected via Liquid is the recommended schema approach for Shopify.
- ✓ Check for existing schema before adding your own, as duplicate or conflicting markup can create validation issues.
- ✓ Schema must reflect content that is actually visible and accurate on the page.
- ✓ Use the Schema Markup Validator for syntax; use Google's Rich Results Test for rich-result eligibility.
- ✓ Liquid variables keep Product Schema accurate automatically. No manual updates needed.
Before You Touch Any Theme File
This checklist prevents the most common implementation mistakes. Complete it before pasting any code.
- ✅ Duplicate your live theme first: go to Online Store → Themes → Actions → Duplicate. Make all edits on the duplicate, then publish when ready.
- ✅ Confirm which theme is live: it's labelled "Current theme" in the Themes list.
- ✅ Search for existing schema: in the theme editor, search for
application/ld+json,"@type": "Product","@type": "Organization", and"@type": "BreadcrumbList". If a type already exists, skip that step. - ✅ Check your SEO apps: apps such as Yoast SEO, Smart SEO, and SEO King often inject schema automatically. Adding your own on top can create conflicting markup.
Theme file locations (navigate via Online Store → Themes → Edit code):
-
layout/theme.liquid→ Organization Schema (homepage-only condition) -
sections/main-product.liquidor your product section → Product Schema - Your FAQ section or page template → FAQPage Schema
- Your breadcrumb snippet (e.g.
snippets/breadcrumbs.liquid) → BreadcrumbList Schema
Filenames vary by theme. If you can't find a file by name, search for a distinctive string from its content using the theme editor's search bar.
Step-by-Step Implementation
1Product Schema
Location: sections/main-product.liquid (filename varies by theme)
Search the file for "@type": "Product" first. If found, your theme already outputs Product Schema, so skip this step. If not, paste the block below at the bottom of the file, before the {% schema %} tag (never inside it).
This example uses selected_or_first_available_variant, the safest single-offer approach for stores with multiple variants. Exposing all variants as separate offers requires a looped offers array and is beyond the scope of this guide.
{% if product %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": {{ product.title | json }},
"url": {{ shop.url | append: product.url | json }},
"description": {{ product.description | strip_html | truncate: 500 | json }},
"brand": {
"@type": "Brand",
"name": {{ product.vendor | json }}
},
"image": {{ product.featured_image | image_url: width: 1200 | prepend: 'https:' | json }},
"offers": {
"@type": "Offer",
"price": {{ product.selected_or_first_available_variant.price | divided_by: 100.0 | json }},
"priceCurrency": {{ shop.currency | json }},
"availability": {% if product.selected_or_first_available_variant.available %}"https://schema.org/InStock"{% else %}"https://schema.org/OutOfStock"{% endif %},
"url": {{ shop.url | append: product.url | json }}{% if product.selected_or_first_available_variant.sku != blank %},
"sku": {{ product.selected_or_first_available_variant.sku | json }}{% endif %}{% if product.selected_or_first_available_variant.barcode != blank %},
"gtin": {{ product.selected_or_first_available_variant.barcode | json }}{% endif %}
}
}
</script>
{% endif %}
⚡ sku and gtin are optional and only render when populated. image_url is the current Shopify filter (img_url is deprecated). divided_by: 100.0 converts Shopify's integer price (stored in pence/cents) to a decimal.
2FAQPage Schema
Location: Your FAQ section or page template
Only use FAQPage schema when the questions and answers are visibly rendered on the page. Do not mark up hidden, collapsed, or fabricated content. Google has significantly restricted FAQ rich-result display compared with earlier years, and valid markup does not guarantee a rich result. Implement it because your page genuinely contains FAQ content, not solely to gain a SERP feature.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "REPLACE: Your first FAQ question, exactly as it appears on the page",
"acceptedAnswer": {
"@type": "Answer",
"text": "REPLACE: Your first FAQ answer, matching the visible text on the page."
}
},
{
"@type": "Question",
"name": "REPLACE: Your second FAQ question",
"acceptedAnswer": {
"@type": "Answer",
"text": "REPLACE: Your second FAQ answer."
}
}
]
}
</script>
3BreadcrumbList Schema
Location: Your breadcrumb snippet or section
The schema must reflect your actual visible navigation hierarchy. The collection object is not always available on a product page, since a customer can arrive directly from search without passing through a collection. The example below handles both cases safely.
{% if product %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": {{ shop.url | json }}
},
{% if collection %}
{
"@type": "ListItem",
"position": 2,
"name": {{ collection.title | json }},
"item": {{ shop.url | append: collection.url | json }}
},
{
"@type": "ListItem",
"position": 3,
"name": {{ product.title | json }},
"item": {{ shop.url | append: product.url | json }}
}
{% else %}
{
"@type": "ListItem",
"position": 2,
"name": {{ product.title | json }},
"item": {{ shop.url | append: product.url | json }}
}
{% endif %}
]
}
</script>
{% endif %}
⚡ This is a simplified example. Adjust the ListItem entries to match your store's actual navigation structure.
4Organization Schema
Location: layout/theme.liquid, homepage only
Add this once, on the homepage only. sameAs links your business to profiles you actually own, so do not include platforms where you don't have a presence. For the logo, use a stable public URL rather than a Shopify CDN URL that may change. Replace every REPLACE value before publishing.
{% if request.page_type == 'index' %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": {{ shop.name | json }},
"url": {{ shop.url | json }},
"logo": "REPLACE: https://yourdomain.com/your-logo.png",
"description": "REPLACE: A brief, accurate description of your business.",
"contactPoint": {
"@type": "ContactPoint",
"contactType": "customer service",
"email": "REPLACE: support@yourdomain.com"
},
"sameAs": [
"REPLACE: https://www.instagram.com/your-handle",
"REPLACE: https://www.facebook.com/your-page"
]
}
</script>
{% endif %}
Validation: Which Tool to Use
These are two distinct tools with different purposes. Use both.
- Schema Markup Validator: checks that your JSON-LD is syntactically correct and uses valid Schema.org properties. Covers all Schema.org types. Use this first.
- Google Rich Results Test: checks whether your markup qualifies for Google's specific rich-result features. Only evaluates types Google supports for rich results. Use this second.
After saving, open the live page URL (not a preview link) and run it through both tools. If schema isn't detected at all, view the page source (Ctrl+U / Cmd+U) and search for application/ld+json to confirm the block is rendering. Errors must be fixed; warnings are advisory.
Schema Isn't Showing? 7 Troubleshooting Checks
| # | Problem | Fix |
|---|---|---|
| 1 | Wrong theme edited. Changes to an unpublished theme won't appear live. | Confirm the "Current theme" label in Online Store → Themes. |
| 2 | Conflicting or duplicate schema. Two blocks of the same type on one page can produce inconsistent signals. | Search theme files and check SEO apps. Remove or disable one source. |
| 3 | Invalid JSON-LD. A missing comma or unclosed bracket breaks the entire block. | Run the page through the Schema Markup Validator and fix every syntax error. |
| 4 |
Liquid variable returning blank. An empty product.vendor outputs "name": "". |
Wrap optional properties in a blank check: {% if product.vendor != blank %} ... {% endif %}. |
| 5 |
Code placed inside {% schema %}. Content inside {% schema %}...{% endschema %} is theme settings JSON, not rendered HTML. |
Place your <script type="application/ld+json"> block before the {% schema %} tag. |
| 6 | SEO app already injecting schema. Your custom code and the app may output the same type. | Disable the app's schema output or remove your custom block. Keep only one. |
| 7 | Testing a cached page. Validators may return stale results shortly after a change. | Use "Test live URL" in the Rich Results Test. Wait a few minutes after saving before re-testing. |
Final Implementation Reference
| Schema | Where to implement | Main purpose | Duplicate? | Validate with |
|---|---|---|---|---|
| Product | sections/main-product.liquid |
Product rich results (price, availability) | No. Check theme first. | Rich Results Test |
| FAQPage | FAQ section or page template | Marks up visible Q&A for search systems | No. Visible FAQs only. | Both validators |
| BreadcrumbList | Breadcrumb snippet or section | Page hierarchy; breadcrumb SERP display | No. Match actual nav. | Rich Results Test |
| Organization |
layout/theme.liquid (homepage only) |
Business entity description | No. Homepage once. | Schema Markup Validator |
Frequently Asked Questions
Do I need an app to add schema to Shopify?
No. You can add JSON-LD directly to your theme files via Online Store → Themes → Edit code. Apps can simplify the process, but if you use one, check whether it already injects schema before adding your own, as overlapping markup can create conflicting signals.
How do I know if my theme already has schema?
View the source of a live product page (Ctrl+U / Cmd+U) and search for application/ld+json. You can also run the URL through Google's Rich Results Test to see which schema types are detected, or search your theme files directly for "@type": "Product".
Which validator should I use?
Use the Schema Markup Validator (validator.schema.org) first to confirm your JSON-LD syntax is correct. Then use the Google Rich Results Test to check whether your markup qualifies for Google's rich-result features. A page can pass the Schema Markup Validator but not appear in the Rich Results Test, which is expected if the schema type isn't one Google supports for rich results.
Should I add FAQ schema if Google restricts FAQ rich results?
Google has significantly reduced FAQ rich-result visibility. Valid FAQPage markup does not reliably produce a rich result. If your page genuinely contains FAQ content, implementing the schema is still reasonable because it helps search systems understand the page structure. Don't implement it solely to gain a SERP feature.
Why does my schema pass validation but not show a rich result?
Passing validation confirms eligibility, not guaranteed display. Google determines whether to show a rich result based on page quality, content relevance, and its own signals. For Product schema, ensure name, image, and offers (with price and availability) are all present and accurate.