Schema Implementation on Shopify: From Theme Defaults to Full Structured Data Coverage
Shopify is the most popular ecommerce platform for small and mid-market merchants, yet most Shopify stores ship with incomplete structured data. The free Dawn theme provides basic Product schema, but it misses Organization, FAQPage, Article, BreadcrumbList on most page types, and -- critically -- the review schema bridge that makes your customer reviews visible to AI systems. Paid themes under $300 rarely do better. This guide covers everything: what Shopify themes include by default, how to customize schema with Liquid, which apps fill the gaps, how to use metafields for advanced schema, and how to test it all.
What Shopify Themes Include by Default
Dawn Theme (v15.0+)
Dawn is Shopify's free flagship theme and the foundation most stores start with. Here is what it includes for structured data:
Product pages: Dawn generates basic Product schema using the structured_data Liquid filter. This typically includes:
- Product name
- Product description
- Product image (first image only)
- Price and currency
- Availability (InStock/OutOfStock)
- Brand (if set in the product vendor field)
- SKU (from the selected variant)
What Dawn is missing:
- AggregateRating and Review schema -- Even if you have a review app installed, Dawn does not bridge review data into the JSON-LD output. Your 200 customer reviews are invisible to AI and Google's structured data parser.
- Organization schema -- No organization entity on the homepage or any other page.
- FAQPage schema -- No FAQ structured data, even if you build FAQ sections into your pages.
- BreadcrumbList schema -- Limited or absent breadcrumb markup in JSON-LD format.
- Article schema -- Blog post pages may include basic article data through the structured_data filter, but often miss author details, dateModified, and publisher information.
- Multiple product images -- Dawn's schema often only includes the first product image, not the full gallery.
- Shipping details -- OfferShippingDetails schema is not generated.
Horizon Theme (2025+)
Shopify announced Horizon as the new flagship theme to replace Dawn. Horizon includes more comprehensive structured data:
- Product markup with broader property coverage
- Breadcrumbs
- Reviews (when connected to a compatible review app)
- Blog article markup
However, even Horizon does not cover every schema type an ecommerce store needs. Organization schema, FAQPage, and advanced Product properties (shipping details, return policy, GTIN) still require custom implementation.
Paid Themes
Most paid Shopify themes in the $250-$350 range include similar structured data coverage to Dawn -- basic Product schema on product pages and little else. Some premium themes add BreadcrumbList or basic Article schema, but comprehensive structured data coverage from a theme alone is rare.
Bottom line: No Shopify theme gives you complete structured data coverage out of the box. Every store needs either custom Liquid code or a schema app to fill the gaps.
Liquid Customization: Building Schema Into Your Theme
Shopify's Liquid templating language gives you full control over JSON-LD output. Here are the key implementations.
Product Schema With Full Properties
Replace or supplement your theme's default product schema by adding a snippet to your product template:
{% assign current_variant = product.selected_or_first_available_variant %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": {{ product.title | json }},
"description": {{ product.description | strip_html | truncate: 5000 | json }},
"image": [
{% for image in product.images %}
"{{ image | image_url: width: 1200 }}"{% unless forloop.last %},{% endunless %}
{% endfor %}
],
"brand": {
"@type": "Brand",
"name": {{ product.vendor | json }}
},
"sku": {{ current_variant.sku | json }},
{% if current_variant.barcode != blank %}
"gtin": {{ current_variant.barcode | json }},
{% endif %}
"offers": {
"@type": "Offer",
"url": "{{ shop.url }}{{ product.url }}",
"priceCurrency": {{ cart.currency.iso_code | json }},
"price": {{ current_variant.price | money_without_currency | remove: ',' | json }},
"availability": "https://schema.org/{% if current_variant.available %}InStock{% else %}OutOfStock{% endif %}",
"itemCondition": "https://schema.org/NewCondition",
"seller": {
"@id": "{{ shop.url }}/#organization"
}
{% if product.metafields.custom.price_valid_until != blank %}
,"priceValidUntil": {{ product.metafields.custom.price_valid_until | json }}
{% endif %}
}
{% if product.metafields.reviews.rating_value != blank %}
,"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": {{ product.metafields.reviews.rating_value | json }},
"bestRating": "5",
"worstRating": "1",
"reviewCount": {{ product.metafields.reviews.review_count | json }}
}
{% endif %}
}
</script>
Key Liquid techniques:
- Use
| jsonfilter on all string values to properly escape quotes and special characters - Use
| money_without_currency | remove: ','for prices to get a clean decimal number - Use conditional blocks (
{% if %}) to only output properties when data exists - Use
image_url: width: 1200to get high-resolution image URLs
Organization Schema on the Homepage
Add this to your theme.liquid or create a snippet included conditionally:
{% if request.page_type == 'index' %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "OnlineStore",
"@id": "{{ shop.url }}/#organization",
"name": {{ shop.name | json }},
"url": "{{ shop.url }}",
"logo": {
"@type": "ImageObject",
"url": "{{ settings.logo | image_url: width: 600 }}"
},
"description": {{ shop.description | json }},
"contactPoint": {
"@type": "ContactPoint",
"contactType": "customer service",
"email": {{ shop.email | json }}
{% if shop.phone != blank %}
,"telephone": {{ shop.phone | json }}
{% endif %}
},
"sameAs": [
{% assign social_links = '' %}
{% if settings.social_instagram_link != blank %}
{% assign social_links = social_links | append: '"' | append: settings.social_instagram_link | append: '",' %}
{% endif %}
{% if settings.social_facebook_link != blank %}
{% assign social_links = social_links | append: '"' | append: settings.social_facebook_link | append: '",' %}
{% endif %}
{% if settings.social_twitter_link != blank %}
{% assign social_links = social_links | append: '"' | append: settings.social_twitter_link | append: '",' %}
{% endif %}
{% if settings.social_youtube_link != blank %}
{% assign social_links = social_links | append: '"' | append: settings.social_youtube_link | append: '",' %}
{% endif %}
{% if settings.social_tiktok_link != blank %}
{% assign social_links = social_links | append: '"' | append: settings.social_tiktok_link | append: '",' %}
{% endif %}
{% if settings.social_pinterest_link != blank %}
{% assign social_links = social_links | append: '"' | append: settings.social_pinterest_link | append: '",' %}
{% endif %}
{{ social_links | remove_last: ',' }}
]
}
</script>
{% endif %}
BreadcrumbList Schema Sitewide
Add this snippet to your theme.liquid layout:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{ shop.url }}"
}
{% if request.page_type == 'collection' %}
,{
"@type": "ListItem",
"position": 2,
"name": {{ collection.title | json }}
}
{% elsif request.page_type == 'product' %}
{% if product.collections.size > 0 %}
,{
"@type": "ListItem",
"position": 2,
"name": {{ product.collections.first.title | json }},
"item": "{{ shop.url }}{{ product.collections.first.url }}"
}
,{
"@type": "ListItem",
"position": 3,
"name": {{ product.title | json }}
}
{% else %}
,{
"@type": "ListItem",
"position": 2,
"name": {{ product.title | json }}
}
{% endif %}
{% elsif request.page_type == 'article' %}
,{
"@type": "ListItem",
"position": 2,
"name": {{ blog.title | json }},
"item": "{{ shop.url }}{{ blog.url }}"
}
,{
"@type": "ListItem",
"position": 3,
"name": {{ article.title | json }}
}
{% elsif request.page_type == 'blog' %}
,{
"@type": "ListItem",
"position": 2,
"name": {{ blog.title | json }}
}
{% elsif request.page_type == 'page' %}
,{
"@type": "ListItem",
"position": 2,
"name": {{ page.title | json }}
}
{% endif %}
]
}
</script>
Article Schema for Blog Posts
Add this to your article template:
{% if request.page_type == 'article' %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": {{ article.title | json }},
"description": {{ article.excerpt_or_content | strip_html | truncate: 200 | json }},
"image": {% if article.image %}"{{ article.image | image_url: width: 1200 }}"{% else %}null{% endif %},
"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": {
"@id": "{{ shop.url }}/#organization"
},
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "{{ shop.url }}{{ article.url }}"
},
"wordCount": "{{ article.content | strip_html | split: ' ' | size }}"
}
</script>
{% endif %}
Schema Apps for Shopify
If you prefer not to write Liquid code, several Shopify apps handle schema generation. Here are the main options:
Schema App Total Schema Markup
The most comprehensive schema app for Shopify. It automatically generates schema for products, collections, articles, organization, and more. Key features:
- Automatic Product, Organization, BreadcrumbList, and Article schema
- Integration with popular review apps for AggregateRating
- Supports custom schema types via their editor
- Handles variant products with multiple Offers
- $15-30/month depending on plan
JSON-LD for SEO by Kaikoma
A popular free/affordable option that covers the basics:
- Product schema with variant support
- BreadcrumbList schema
- Organization schema
- Article schema for blog posts
- Review integration for some review apps
Schema Plus SEO
Focuses specifically on Product schema optimization:
- Full Product schema with all Google-recommended properties
- Automatic review data integration
- Rich snippet preview showing how your listing will appear
- Variant product handling
Naridon
An AI-focused solution that goes beyond schema markup to cover full AI search optimization:
- Generates and monitors structured data across all page types
- Bridges review app data to schema automatically
- Tracks AI citation performance
- Provides fix suggestions for schema errors
- Monitors competitor schema implementations
Choosing Between Apps
Use a schema app if:
- You do not have a developer on staff
- You want ongoing monitoring and alerts
- You need review app integration without custom code
- You want automated updates when Google changes requirements
Use custom Liquid if:
- You need full control over schema output
- You have specific schema requirements beyond standard types
- You want to minimize app dependencies
- Your theme already includes some schema and you just need to fill gaps
Common approach: Use an app for the baseline (Product, Organization, BreadcrumbList) and custom Liquid for specialized schema (FAQPage with dynamic questions, HowTo for guide pages).
Using Metafields for Advanced Schema
Shopify metafields let you store custom data on products, collections, pages, and other objects. This data can power your schema without hardcoding values.
Setting Up Schema-Related Metafields
In Shopify Admin, go to Settings > Custom data and create metafields for:
Product metafields:
custom.schema_gtin(Single line text) -- GTIN/EAN/UPC barcodecustom.schema_mpn(Single line text) -- Manufacturer part numbercustom.schema_material(Single line text) -- Product materialcustom.schema_color(Single line text) -- Product colorcustom.schema_size_system(Single line text) -- Size system (US, EU, UK)custom.price_valid_until(Date) -- When the current price expirescustom.primary_collection(Collection reference) -- Primary collection for breadcrumbs
Blog article metafields:
custom.author_job_title(Single line text) -- Author's titlecustom.author_linkedin(URL) -- Author's LinkedIn profilecustom.schema_article_section(Single line text) -- Article category
Using Metafields in Schema Liquid
{% assign gtin = product.metafields.custom.schema_gtin %}
{% assign mpn = product.metafields.custom.schema_mpn %}
{% assign material = product.metafields.custom.schema_material %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": {{ product.title | json }},
{% if gtin != blank %}"gtin": {{ gtin | json }},{% endif %}
{% if mpn != blank %}"mpn": {{ mpn | json }},{% endif %}
{% if material != blank %}"material": {{ material | json }},{% endif %}
"offers": {
"@type": "Offer",
"price": {{ product.selected_or_first_available_variant.price | money_without_currency | remove: ',' | json }},
"priceCurrency": {{ cart.currency.iso_code | json }},
"availability": "https://schema.org/{% if product.selected_or_first_available_variant.available %}InStock{% else %}OutOfStock{% endif %}"
}
}
</script>
Bulk Populating Metafields
For stores with hundreds of products, populating metafields one by one is impractical. Options:
Shopify bulk editor: Select multiple products, add the metafield column, and edit values in bulk.
CSV import with Matrixify: Export your products, add metafield columns, populate data in a spreadsheet, and reimport. Matrixify handles metafield imports natively.
Shopify API: For programmatic population, use the Admin API to update metafields in batch:
const response = await fetch(
`https://${shop}/admin/api/2024-01/products/${productId}/metafields.json`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': accessToken
},
body: JSON.stringify({
metafield: {
namespace: 'custom',
key: 'schema_gtin',
value: '0012345678905',
type: 'single_line_text_field'
}
})
}
);
Preventing Theme Update Breakage
Shopify theme updates can overwrite your custom schema code. Here is how to protect your implementation:
Use snippets, not inline edits. Put your schema code in dedicated snippet files (like schema-product.liquid, schema-organization.liquid, schema-breadcrumbs.liquid) and include them in your templates with {% render 'schema-product' %}. Theme updates are less likely to overwrite snippet files.
Document your changes. Keep a list of every file you modified and what you changed. After a theme update, compare the updated files against your documentation.
Use a version-controlled theme. Shopify's GitHub integration lets you track theme changes in a Git repository. This makes it easy to see what a theme update changed and re-apply your customizations.
Test after every update. Run the Google Rich Results Test on one page of each type (product, collection, blog post, homepage) after every theme update. This takes 5 minutes and catches breakage immediately.
Testing Your Shopify Schema
After implementing schema through Liquid or apps, run this verification checklist:
- Test a product page in the Rich Results Test. Verify Product, AggregateRating (if reviews exist), and BreadcrumbList all appear.
- Test the homepage in the Rich Results Test. Verify Organization schema appears.
- Test a blog post in the Rich Results Test. Verify Article and BreadcrumbList appear.
- Test a collection page in the Rich Results Test. Verify BreadcrumbList appears.
- View page source on each page type. Search for
application/ld+json. Verify there are no duplicate Product blocks. - Compare schema values to visible content. Check that price, availability, rating, and product name in the schema match what appears on the page.
- Test with a variant product. Select a different variant on a product page and check if the schema updates (if using client-side variant switching, the server-rendered schema may show the default variant, which is acceptable).
- Check Search Console 48-72 hours after deployment for any new enhancement errors.
Shopify's flexibility makes comprehensive schema implementation achievable for any store, whether through Liquid customization, apps, or a combination of both. The key is closing the gaps that themes leave open -- particularly Organization schema, review data bridging, and BreadcrumbList coverage across all page types.