Skip to content

About

Zero-dependency CLI that validates a Shopify page's JSON-LD (Product, Offer, ratings, breadcrumbs) against Google's documented requirements.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

shopify-structured-data-validator

A zero-dependency CLI that checks the JSON-LD on your Shopify product pages against Google's documented Product requirements, and catches the mistakes themes and apps actually make: a price written as $1,299.00, an InStock that isn't a schema.org URL, an image URL starting with //, raw {{ product.description }} left in the output, and two plugins both emitting a Product.

Not sure your store's SEO and speed basics are covered? Run the free audit - it checks SEO, speed, CRO and AI-visibility signals on a live store in about a minute.

Example output

Run against the sample-pages/product-broken.html fixture in this repo, which plants these mistakes on purpose:

$ ssdv check sample-pages/product-broken.html

Structured data check: sample-pages/product-broken.html

Page type: product | JSON-LD blocks: 3 | Types found: BreadcrumbList, Product

[ERROR] JSON-LD block is not valid JSON: Expected double-quoted property name in JSON at position 111 (line 5 column 3). Usually an unescaped quote or line break in a Liquid-rendered description, or a trailing comma. (JSON-LD block at line 45)
[ERROR] description contains raw Liquid ("{{ product.description | strip_html }}"). The template output was not evaluated. (JSON-LD block at line 7)
[ERROR] Product "Trail Runner Backpack 20L" offer price "$1,299.00" is not a plain decimal number. Currency symbols, thousands separators and decimal commas are rejected; put the currency in priceCurrency. (JSON-LD block at line 7)
[ERROR] Product "Trail Runner Backpack 20L" offer priceCurrency "usd" is not a 3-letter uppercase ISO 4217 code (for example USD). (JSON-LD block at line 7)
[ERROR] aggregateRating ratingValue 6 is outside the 1 to 5 scale. (JSON-LD block at line 7)
[ERROR] aggregateRating needs a ratingCount or reviewCount greater than 0. A rating with zero reviews is ineligible for rich results. (JSON-LD block at line 7)
[ERROR] BreadcrumbList item 2 has no numeric position. (JSON-LD block at line 52)
[WARN ] Product "Trail Runner Backpack 20L" image "//cdn.shopify.com/s/files/1/0001/trail-runner.jpg" is protocol-relative (starts with //). Structured data needs an absolute https:// URL. Liquid's img_url filter and some older themes output this form. (JSON-LD block at line 7)
[WARN ] Product "Trail Runner Backpack 20L" offer availability "InStock" is a bare name. Use the full URL form https://schema.org/InStock. (JSON-LD block at line 7)
[WARN ] Product "Trail Runner Backpack 20L" offer priceValidUntil 2024-12-31 is in the past. Google can treat the offer as expired. (JSON-LD block at line 7)
[WARN ] Product "Trail Runner Backpack 20L" has no brand. Recommended by Google for merchant listings. (JSON-LD block at line 7)
[WARN ] Product "Trail Runner Backpack 20L" has no description. (JSON-LD block at line 30)
[WARN ] 2 Product entities share the name "trail runner backpack 20l". A theme and an app (often a reviews widget) are usually both emitting markup; Google may report duplicate or conflicting values.
[WARN ] The page has both JSON-LD Product markup and 1 Microdata Product block(s). Keep one format so the two cannot disagree.
[INFO ] Product "Trail Runner Backpack 20L" offer has no url. Recommended so the offer points at the variant a shopper lands on. (JSON-LD block at line 30)
[INFO ] Product "Trail Runner Backpack 20L" has no sku, gtin or mpn. Product identifiers help Google match the product across merchants. (JSON-LD block at line 30)

7 error(s), 7 warning(s), 2 info

sample-pages/product-clean.html is the same product written correctly; it exits 0 with no findings. Compare the two files side by side to see exactly what triggers each line.

Features

  • Reads what a crawler reads. Fetches the page (or reads a saved .html file, or a whole directory of them), extracts every <script type="application/ld+json"> block, and parses it. No browser, no dependencies.
  • Invalid JSON, with a line number. A single unescaped quote in a Liquid-rendered description makes the whole block unparseable, and search engines silently skip it. ssdv reports the block's line and the parser's message.
  • Product checks. Name, image, and at least one of offers / aggregateRating / review; description, brand and identifiers as warnings and info.
  • Offer checks. Price must be a plain decimal (no $, no 1,299.00, no 12,99), priceCurrency must be a 3-letter uppercase code, availability must be a real schema.org ItemAvailability value (bare InStock is flagged as a warning, Available as an error), priceValidUntil must be an ISO date and not already past. AggregateOffer (variant price ranges) and @id references inside an @graph are followed.
  • Rating and review checks. ratingValue inside the bestRating scale, a review count above zero, review authors present.
  • BreadcrumbList checks. Empty lists, missing position, missing name.
  • Shopify-shaped bugs. Protocol-relative (//cdn.shopify.com/...) and relative image/offer URLs, unrendered {{ }} / {% %} Liquid inside any value, a missing or non-schema.org @context.
  • Duplicate markup. Two Product entities with the same name, or JSON-LD Product alongside Microdata Product on one page: the usual result of a theme and a reviews app both marking up the same product.
  • CI-friendly. Exit 0 clean, 1 on errors (or warnings with --strict), 2 if the tool itself could not run. --json for machine-readable output.

Installation

Needs Node 18 or newer. No npm install step: there are no dependencies.

git clone https://github.com/Ecom-Swift-LLC/shopify-structured-data-validator.git
cd shopify-structured-data-validator
node bin/ssdv.js --help

Or run it without cloning:

npx github:Ecom-Swift-LLC/shopify-structured-data-validator check https://your-store.com/products/your-product

Usage

# a live product page
ssdv check https://your-store.com/products/your-product

# several pages at once
ssdv check https://your-store.com/products/a https://your-store.com/products/b

# a saved page, or every .html file under a directory
ssdv check ./page.html
ssdv check ./saved-pages

# fail CI on warnings too, or get JSON
ssdv check ./saved-pages --strict
ssdv check ./page.html --json > report.json

If you installed it from a clone, replace ssdv with node bin/ssdv.js.

A product page is recognised by a /products/ path, an og:type of product, or any Product markup. On a product page with no structured data at all, that is an error; on other pages it is only an info line.

The library API is a single function if you want to wire it into your own tooling:

const { validateHtml } = require('./lib/validate');
const report = validateHtml(htmlString, { url: 'https://your-store.com/products/x' });
console.log(report.summary); // { errors, warnings, info }

What it does NOT do

  • It does not run JavaScript. It validates the HTML your server returns. If an app injects JSON-LD in the browser after load, ssdv will not see it (some crawlers will, some won't). Save the rendered DOM to a file and point ssdv at that if you need to check it.
  • It is not Google's Rich Results Test. The rules mirror the Product, Offer, AggregateRating, Review and BreadcrumbList requirements Google documents, but passing here does not guarantee rich-result eligibility, and Google can change its rules. Use the official test as the final word.
  • It does not validate the full schema.org vocabulary. Only the types listed above are checked. Other types (Organization, FAQPage, VideoObject, and so on) are parsed for validity and @context, nothing more.
  • It does not check shipping, return policy or Merchant Center data. Those live in your product feed and Merchant Center settings; see the feed validator linked below.
  • It does not crawl. It checks the pages you give it, one HTML document each. It does not follow canonicals, variants (?variant=), or pagination.
  • It cannot get past bot protection or a password page. If your store blocks the request, you will get an HTTP error (exit 2); save the page from your browser and check the file instead.

FAQ

Does this work on any site, or only Shopify? The checks are generic schema.org Product rules, so it works anywhere. The Shopify-specific messages (img_url, Liquid, theme plus app duplicates) are about the mistakes we keep finding in Shopify themes.

My theme outputs the JSON-LD with {{ product | json }}. Is that fine? Often not: the raw product object is not schema.org Product markup. Point ssdv at the rendered page and see what it says; a missing offers, image or @context is the usual result.

Why is InStock without the URL only a warning? Google's documentation uses the full https://schema.org/InStock form, so we flag the bare name to push you toward the documented one. An unrecognised value like Available is an error.

Why does a rating with zero reviews fail? A star rating needs reviews behind it. If your reviews app outputs reviewCount: 0 for products with no reviews yet, drop the aggregateRating block until there is at least one.

How do I check every product in my store? Export your product URLs (the sitemap lists them), loop ssdv check <url> --json over them, and filter for errors. We keep it to one page per target on purpose so it never hammers a store.

Found problems here? Check the rest of the store too

Structured data is one slice of what decides whether a product page earns clicks and sales. If this turned up errors, the free store audit looks at the rest in the same pass: SEO, page speed, conversion and how AI shopping assistants read your catalogue.

Related Shopify tools

Also from Ecom Swift LLC:

Contributing

Issues and PRs welcome, especially a real page this tool got wrong (false positive or miss). Run the suite with npm test (plain node --test, no test framework dependency) before opening a PR, and add a case to test/validate.test.js for any new rule.

Roadmap

  • Checks for Offer shipping details and return policy markup
  • A --sitemap mode that samples product URLs from a store's sitemap with a polite rate limit
  • A GitHub Action wrapper, matching the one in shopify-theme-deploy-action
  • FAQPage and VideoObject rules

License

MIT, Ecom Swift LLC. See LICENSE.

Need help?

We build Shopify themes, storefronts and automation for a living. If a check here turned up something you would rather have fixed than DIY, or you want a second pair of eyes on your store generally:


Built because we kept finding the same few structured-data mistakes in client themes, usually after a store owner wondered why their competitors had price and star ratings in search results and they did not. Free and complete, no catch.

About

Zero-dependency CLI that validates a Shopify page's JSON-LD (Product, Offer, ratings, breadcrumbs) against Google's documented requirements.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages