Skip to content
Published Authored byBilly Reiner

Schema · How-to

Custom JSON-LD via theme.liquid

Custom JSON-LD on Shopify lives in two scopes1. Site-wide entities (Organization, WebSite) go in theme.liquid inside <head>, firing once per page. Per-template entities (Product, BreadcrumbList, Article) go in the section file for the template — main-product.liquid for PDPs, main-list-collections.liquid for collection pages, main-article.liquid for blog posts. The Liquid block uses dynamic variables (product.title, shop.url, article.published_at) so the markup composes per-page. Deployment is via Theme Editor (Online Store > Themes > Actions > Edit code), Shopify CLI (shopify theme push), or the GitHub theme integration.

The split between theme.liquid and section files matters because of cardinality. Organization describes the brand — one entity, every page. Product describes a single product — one entity, one PDP. Putting Product schema in theme.liquid means the product.* Liquid variables are unbound on non-product pages and the schema fails to render. Putting Organization in main-product.liquid means the entity fires only on PDPs and not on the homepage, contact page, or cart, leaving the brand signal incomplete.

What custom JSON-LD on Shopify means

Custom JSON-LD on Shopify is structured data the merchant adds to the theme above and beyond what Shopify auto-emits. Shopify's SEO overview confirms themes auto-emit Product schema. Custom JSON-LD covers everything else — Organization, WebSite, BreadcrumbList, Article on the blog, FAQPage on the FAQ hub, plus the upgraded Product fields (gtin, hasMerchantReturnPolicy, shippingDetails) the theme's auto-emission leaves out. The injection pattern is a <script type='application/ld+json'>…</script> block placed in theme.liquid or a per-template section file.

Mental model: Shopify gives you Product for free; everything else is a Liquid block you write once and reuse. The work is one block per entity type, each block parametrised by Liquid variables so it composes per product, per page, per article.

Three placement scopes

JSON-LD on Shopify lives in one of three scopes: (1) theme.liquid <head>, for site-wide entities like Organization and WebSite that fire once per page. (2) Per-template section files (main-product.liquid, main-list-collections.liquid, main-article.liquid, page.liquid), for entities that need template-specific Liquid context. (3) Schema apps via Script Tag API or app blocks, for merchants who prefer a managed app to writing Liquid by hand.

theme.liquid placement

theme.liquid is the layout file that wraps every storefront page. Its <head> section is the canonical place for site-wide JSON-LD. Open Online Store > Themes > Actions > Edit code > Layout > theme.liquid. Find the closing </head> tag. Add the Organization and WebSite blocks immediately before </head>. Save.

Liquid theme.liquid <head> placement — Organization and WebSite, paired
 <!-- Site-wide JSON-LD (Organization + WebSite) --> <script type="application/ld+json"> { "@context": "https://schema.org", "@graph": [ { "@type": "Organization", "@id": "{{ shop.url }}#organization", "name": "{{ shop.name | escape }}", "url": "{{ shop.url }}/" }, { "@type": "WebSite", "@id": "{{ shop.url }}#website", "url": "{{ shop.url }}/", "name": "{{ shop.name | escape }}", "publisher": { "@id": "{{ shop.url }}#organization" } } ] } </script> 

One @graph array consolidates Organization and WebSite into a single script tag — cleaner than two separate blocks and equally valid per Schema.org. The full Organization and WebSite field sets are on the Organization leaf and WebSite leaf respectively.

Per-template section files

Modern Shopify themes (Dawn-derivative and similar) follow Online Store 2.0 architecture: each template (product, collection, article, page) is composed of sections, and the section file for the dominant section carries the template's structured data. Open Online Store > Themes > Actions > Edit code > Sections. Find main-product.liquid (for the PDP Product + BreadcrumbList block), main-list-collections.liquid (for the collection BreadcrumbList block), main-article.liquid (for the Article block). Paste your complementary JSON-LD inside the section, typically near the bottom before any closing tags.

  • main-product.liquid — Product (gtin, hasMerchantReturnPolicy, shippingDetails, AggregateRating) + per-PDP BreadcrumbList.
  • main-list-collections.liquid — Collection BreadcrumbList.
  • main-article.liquid — Article (headline, author, publisher, dateModified) + per-article BreadcrumbList.
  • page.liquid / sections/main-page.liquid — FAQPage on /pages/faq, custom JSON-LD on landing pages.

The @graph pattern for one block per page

A Shopify page can carry multiple JSON-LD blocks (Shopify's auto-emitted Product, plus your complementary blocks for BreadcrumbList, Article, etc.), and Google's parser handles them. The cleaner pattern is one script tag per page containing a @graph array of all the entities. The @graph form deduplicates entities by @id automatically — when your Product references Organization by @id, the same Organization defined in theme.liquid resolves cleanly.

Practical implementation on Shopify: you'll typically have two script tags per page — one in theme.liquid (Organization + WebSite as a @graph) and one in the section file (per-template entities as another @graph). Both validate. Consolidating into a single page-wide @graph requires moving the section-file content into theme.liquid with template conditionals, which gets messy. Two blocks is fine.

Deploying via Theme Editor, Shopify CLI, or GitHub

Three deployment paths in 2026. (1) Theme Editor: edit theme.liquid and section files directly in Online Store > Themes > Actions > Edit code. Saves are live immediately on the published theme; merchants should use a draft theme for risky changes. (2) Shopify CLI: shopify theme pull to download a local copy, edit, shopify theme push to deploy. CLI is the modern recommendation. (3) GitHub integration: connect a repo, commit theme changes, Shopify auto-deploys. Best for teams; requires the Online Store Theme version control feature.

Validation workflow

After deploying, validate one URL per template type. PDP: submit one product URL to Rich Results Test. Collection: submit one collection URL. Blog post: submit one article URL. Homepage: submit shop.url. Expected pass-fail per template: PDP detects Product + Offer + BreadcrumbList (zero errors); Collection detects BreadcrumbList; Blog post detects Article + BreadcrumbList; Homepage detects Organization + WebSite. For any errors, edit the relevant section file or theme.liquid, redeploy, re-validate.

A subtle gotcha: Shopify caches theme assets aggressively. After a push, the new JSON-LD may not appear in the Rich Results Test for 30–60 seconds. If the validator returns stale results, wait briefly and re-run.

Shopify gotchas on custom JSON-LD

Five gotchas. First: Shopify's admin code editor auto-corrects straight quotes to smart quotes — author the JSON in VS Code or another plain editor, paste into Shopify only at the final step. Second: forgetting | escape on every Liquid string variable that produces user-input text (product.title, shop.name, article.title) — apostrophes break the JSON. Third: putting Product schema in theme.liquid (where product.* is unbound off PDPs). Fourth: putting Organization schema in a section file (where it fires per-template instead of site-wide). Fifth: missing the application/ld+json mime type on the script tag — the block is then ignored by validators.

A sixth gotcha for stores using schema apps alongside custom JSON-LD: the schema app injects its own JSON-LD via Script Tag API after page load, which means view-source on a Shopify storefront sometimes shows your hand-rolled block AND the app's block, and Rich Results Test parses both. Either remove the app's emission (most apps offer a toggle) or let the app own Product entirely and only author site-wide blocks in theme.liquid yourself. The trade-off is covered on the schema-app-vs-theme leaf.