Schema Markup Validator - JSON-LD Structured Data Checker
Pricing
from $1.60 / 1,000 validated pages
Schema Markup Validator - JSON-LD Structured Data Checker
Validate JSON-LD and Microdata schema markup on a URL list, a sitemap or a whole site against Google rich result rules (Product, FAQ, Recipe, Event, Job...), plus Open Graph. $2 per 1,000 pages.
Pricing
from $1.60 / 1,000 validated pages
Rating
0.0
(0)
Developer
Yukai Lin
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
4 days ago
Last modified
Categories
Share
What does Schema Markup Validator do?
Paste a list of page URLs (or a sitemap, or a site to crawl) and get one row per page: is the schema.org JSON-LD and Microdata valid for Google rich results, with stable issue codes for every problem. $0.002 per validated page ($2 per 1,000); pages that cannot be read are free.
Try it on 5 to 10 pages first: that costs about $0.01 to $0.02 and takes under a minute. Then switch on Validate the pages in the sitemap for the whole site.
Schema markup validator for many pages at once: it extracts and validates schema.org JSON-LD and Microdata against Google rich result rules, with stable issue codes. A bulk alternative to Google's Rich Results Test and validator.schema.org, which check one page at a time.
It checks what Google needs for rich results: stars, prices, breadcrumbs, recipes, events, job postings and more. It also checks the Open Graph and X (Twitter) tags that control how shared links look. Check a list of URLs, every page in your sitemap, or crawl a whole website, then export every problem as a table with a stable issue code.
- β Rich result checks for Product, Product variants (ProductGroup), Offer, Review and AggregateRating, Article, Breadcrumb, FAQ, Q&A, Recipe, Event, JobPosting, LocalBusiness (and 60 subtypes such as Restaurant or Dentist), Organization, WebSite, VideoObject, SoftwareApplication, Course, Dataset
- π§ͺ Value checks that break rich results silently: dates that are not ISO 8601, prices like
$1,299.00, currencies that are not ISO codes, invalidavailability, relative image URLs, bad durations, offers withoutpriceCurrency, ratings outside their scale. A bad format in a required property is an error; in an optional property (e.g.datePublishedon a Recipe) it is a warning and does not remove rich result eligibility - π·οΈ JSON-LD and Microdata: Microdata (
itemscope/itemprop) is validated with the same rules and markedformat: "microdata"; RDFa types are listed inrdfaTypes - π§© Understands real-world markup:
@graph(Yoast, Rank Math, Shopify),@idreferences, nodes that share an@id(merged like Google does, also across separate script blocks), arrays, variants on separate pages - π« Catches broken blocks: invalid JSON, missing
@contextor@type - π Breadcrumb checks: missing names or URLs, and positions that do not run 1, 2, 3β¦
- π Stable issue codes (
REQUIRED_MISSING,INVALID_PRICE,INVALID_DATE,RELATIVE_URL,INVALID_JSONβ¦) with severity, type, property and path, ready to filter in a spreadsheet - π£ Social tags:
og:title,og:description,og:image(missing or relative),twitter:cardand canonical, returned with every page - πΊοΈ Sitemap mode: validate every page listed in your XML sitemap, filtered by URL pattern (e.g. only
/products/) - π Site summary: which types you use, which rich results are eligible, and the most frequent issues across all pages
- π¦ Also an extractor: optionally returns the raw JSON-LD of every page
- πΈ $2 per 1,000 pages, and pages that cannot be read are free
Who is it for?
- SEO specialists and agencies: audit structured data across hundreds of product, article or location pages at once, instead of pasting URLs into a testing tool one by one
- E-commerce teams: make sure every product page keeps its price, availability and review markup after theme or plugin updates
- Developers: add a structured data check to your release process or schedule it weekly
- Data teams: extract product, recipe, event or job data that sites publish as JSON-LD or Microdata
How much does it cost?
| Event | Price |
|---|---|
| Validated page | $2 / 1,000 pages |
Example: validating 2,000 pages costs 2,000 Γ $0.002 = $4.00 on the Free plan.
No start fee. Pages without structured data are still checked and charged (you learn that they have none). Pages that cannot be read (blocked, 4xx/5xx, not HTML) are reported with an errorType but not charged. Higher Apify plans get volume discounts (see the Pricing tab).
Control your cost
- Charged: one
Validated pageevent per page read (charged: true), including pages without structured data (status: "none"): knowing a page has none is the result. The Pages in the results setting only filters the dataset; every checked page is charged and counted inSUMMARY. - Free (
charged: false, theerrorends with "(not charged)"): invalid input lines, including private or local addresses such aslocalhostor192.168.x.x(errorType: "invalid_input"), duplicate URLs (checked once; the row lists every original line ininputs) and pages that cannot be read (blocked, 4xx/5xx, unreachable site or bad SSL certificate, not HTML, timed out, or disallowed by the site's robots.txt:errorType: "robots_disallowed"). - Before it starts, the run logs its plan (pages Γ price, with Max pages as the upper bound when crawling) and compares it with your maximum charge per run (run options). The plan is saved as
costPlaninSUMMARY. - When the maximum is reached, the run stops: the status message says so,
SUMMARY.statusisLIMIT_REACHEDandSUMMARY.notProcessedhas the number of pages not checked and the first 100 of them. - If Apify restarts the run (server migration or Resurrect), pages already checked are skipped and not charged again (
SUMMARY.resumedSkipped). In crawl and sitemap mode the crawl continues from where it stopped.
How to use it
- Paste the page URLs to check in URLs or domains, one per line (for example a few product, article and category pages). A bad line never stops the run: it becomes a row with
errorType: "invalid_input"(not charged). - Or turn on Validate the pages in the sitemap (optionally with an include pattern such as
https://example.com/products/**), or Crawl the website, and set Max pages. - Click Start. Export the results as CSV, Excel or JSON, and open the Summary for the site overview.
Input example
{"urls": ["https://www.allbirds.com/products/mens-tree-runners","https://www.ikea.com/us/en/p/billy-bookcase-white-00263850/","stripe.com"],"crawl": false,"report": "all","includeJsonLd": true}
Validate every product page in a sitemap:
{"urls": ["https://www.allbirds.com/"],"useSitemap": true,"includeUrlPatterns": ["https://www.allbirds.com/products/**"],"maxPages": 500}
Use urls (a plain list of strings) in API calls. The older startUrls field ([{ "url": "https://β¦" }]) still works and can hold uploaded or linked URL list files, but Apify rejects the whole request with HTTP 400 if one of its entries is a bare domain or a blank line.
Pages in the results (report): all (default), problems+missing (errors, warnings and pages without structured data, which are usually the pages you are looking for) or problems (errors and warnings only). The old onlyProblems: true means problems+missing. SUMMARY.pagesWithoutStructuredData lists up to 200 checked pages that have none (pagesWithoutStructuredDataCount counts all of them), whatever the setting.
Each page is requested exactly as you gave it (only a #fragment is dropped), so https://www.python.org/about/?utm_source=newsletter is checked and reported as is. Start pages keep your line in inputUrl and their position in inputIndex, so results map back to a spreadsheet. Each result also has googleTestUrl, a link to Google's Rich Results Test for the page.
The existing richResults, each item's eligible, eligibleRichResultCount, and SUMMARY.richResults use the Actor's legacy rule checks. They can still include a result from a JSON-LD block with a missing or invalid @context; keep these fields for compatibility, and do not treat them as proof that markup passed every check or that Google will display a rich result. The new markupEligibleRichResultCount counts distinct rich result types that pass the Actor's rule checks and have no blocking error on their own JSON-LD block. Errors in other independent blocks do not lower that count. For a top-level FAQPage, the item also has faqGoogleDisplayStatus: limited_google_decides means the FAQ passed those markup checks but Google's site-specific display eligibility is unknown; markup_invalid means the FAQ failed validation. The page row lists its distinct values in faqGoogleDisplayStatuses (empty when there is no top-level FAQ) and has googleDisplayEligibilityUnknownCount (0 or 1 for the FAQ result type). SUMMARY.faqGoogleDisplayStatusPages counts pages in each state. A page with both valid and invalid FAQ items can appear in both counts. This Actor does not decide whether a site is authoritative.
Output example
Real results from the input above (shortened). The product page's main ProductGroup is valid and eligible for product variant results, but a second JSON-LD block on the page has neither @context nor @type, so search engines ignore it:
{"url": "https://www.allbirds.com/products/mens-tree-runners","inputUrl": "https://www.allbirds.com/products/mens-tree-runners","inputIndex": 0,"title": "Men's Tree Runners - Black - Everyday Sneakers | Allbirds","status": "errors","types": ["ProductGroup"],"richResults": ["Product variants"],"errorCount": 2,"warningCount": 0,"hasStructuredData": true,"jsonLdBlocks": 2,"invalidJsonLdBlocks": 0,"eligibleRichResultCount": 1,"errors": ["$[1]: missing \"@context\": \"https://schema.org\"","$[1]: missing \"@type\""],"warnings": [],"issues": [{ "code": "MISSING_CONTEXT", "severity": "error", "type": null, "property": "@context", "path": "$[1]", "message": "missing \"@context\": \"https://schema.org\"" },{ "code": "MISSING_TYPE", "severity": "error", "type": null, "property": "@type", "path": "$[1]", "message": "missing \"@type\"" }],"items": [{ "type": "ProductGroup", "path": "$[0]", "topLevel": true, "richResult": "Product variants", "eligible": true, "errors": [], "warnings": [] }],"canonical": "https://www.allbirds.com/products/mens-tree-runners","openGraph": { "og:title": "Men's Tree Runner - Jet Black (White Sole)", "og:type": "product", "og:image": "http://www.allbirds.com/cdn/shop/files/TR3MJBW080_SHOE_LEFT_GLOBAL_MENS_TREE_RUNNER_JET_BLACK_WHITE.png?v=1751165486", "...": "..." },"twitterCard": "summary_large_image","socialIssues": [],"via": "backend","jsonLd": [ { "@context": "https://schema.org/", "@type": "ProductGroup", "name": "Men's Tree Runner", "...": "..." } ]}
Repeated problems are grouped, so a list of 24 products gives one line:
{"url": "https://www.ikea.com/us/en/p/billy-bookcase-white-00263850","status": "warnings","types": ["Organization", "WebSite", "CollectionPage", "BreadcrumbList"],"richResults": ["Organization (logo, knowledge panel)", "Site name", "Breadcrumb"],"warnings": ["$[0].@graph[0]: Organization: missing recommended \"logo\"","$[0].@graph[0]: Organization: missing recommended \"sameAs\"","$[0].@graph[2].mainEntity.itemListElement[*].item.offers: Offer: missing recommended \"availability\" (x24)"]}
Microdata example (real result, September 2026)
A product page marked up only with Microdata. The price has a currency symbol and the rating has no ratingValue, so the product snippet is not eligible:
{"url": "https://webscraper.io/test-sites/e-commerce/allinone/product/60","status": "errors","formats": ["microdata"],"types": ["SiteNavigationElement", "Product"],"richResults": [],"errorCount": 2,"warningCount": 3,"jsonLdBlocks": 0,"microdataItems": 2,"rdfaTypes": [],"errors": ["microdata[1].offers.price: \"price\" must be a plain number with a dot as decimal separator, no currency symbol or thousands separator: \"$295.99\"","microdata[1].aggregateRating: AggregateRating: missing required \"ratingValue\""],"issues": [{ "code": "INVALID_PRICE", "severity": "error", "type": "Offer", "property": "price", "path": "microdata[1].offers.price", "format": "microdata", "...": "..." },{ "code": "REQUIRED_MISSING", "severity": "error", "type": "AggregateRating", "property": "ratingValue", "path": "microdata[1].aggregateRating", "format": "microdata", "...": "..." }]}
Microdata paths start with microdata[i] (the i-th top-level Microdata item on the page) instead of $[i]. With Include the raw JSON-LD on, the extracted Microdata items are returned in microdata too. Turn off Also validate Microdata to check JSON-LD only.
Status values
| status | Meaning |
|---|---|
valid | Structured data found, no errors or warnings |
warnings | Only recommended properties are missing; the markup may still qualify for a rich result, subject to Google's display rules |
errors | Required properties are missing or values are invalid; the affected rich results will not show |
none | The page has no JSON-LD or Microdata (listed with Pages in the results = problems+missing or all) |
failed | The page could not be read (not charged) |
The site summary (SUMMARY in the key-value store) has a machine-readable status (SUCCESS, PARTIAL_RESULTS, FAILED, NO_RESULTS, LIMIT_REACHED), counts per type, legacy rich result type (richResults) and issue code, FAQ display-status page counts (faqGoogleDisplayStatusPages), pages with Microdata (withMicrodata) and RDFa (withRdfa), the most frequent social tag issues, pagesWithoutStructuredData, invalidInputs, duplicatesMerged, costPlan and, when the run stopped at your limit, notProcessed. SUMMARY.richResults retains the legacy rule-check meaning above; it is not a sum of markupEligibleRichResultCount.
Errors are problems that stop a rich result. Warnings are recommended properties that make results richer. Paths like $[0].offers show where the problem is: $[0] is the first JSON-LD block on the page, microdata[0] the first Microdata item. A rich result with an error in a nested item (for example an invalid offers.price) is not eligible.
Issue codes
| code | Meaning |
|---|---|
REQUIRED_MISSING / RECOMMENDED_MISSING | A required or recommended property is missing (property names it; a|b means one of them) |
INVALID_DATE, INVALID_DURATION, INVALID_PRICE, INVALID_CURRENCY, INVALID_NUMBER, RATING_OUT_OF_RANGE | Value in the wrong format, or a ratingValue outside worstRating-bestRating (default 1-5) |
INVALID_AVAILABILITY, INVALID_EVENT_STATUS, TIMEZONE_MISSING | Value not in the schema.org list, or an event time without time zone |
RELATIVE_URL, EMPTY_VALUE | URL property that is not absolute, or an empty value |
BREADCRUMB_NAME_MISSING, BREADCRUMB_URL_MISSING, BREADCRUMB_POSITION | Breadcrumb item problems |
INVALID_JSON, MISSING_CONTEXT, INVALID_CONTEXT, MISSING_TYPE | Broken JSON-LD blocks (including a block that is not an object) |
RICH_RESULT_LIMITED, RICH_RESULT_RETIRED | Notes about FAQ and HowTo results (severity info) |
Social tag codes (in socialIssues): OG_TITLE_MISSING, OG_DESCRIPTION_MISSING, OG_IMAGE_MISSING, OG_IMAGE_RELATIVE, TWITTER_CARD_MISSING and CANONICAL_MISSING (the last two are info).
Use with AI agents (MCP)
Connect Apify's MCP server (https://mcp.apify.com?tools=tidytools/structured-data-validator) to Claude, Cursor or any MCP client, then ask e.g. "Check the structured data of these five product pages and tell me which ones are not eligible for product rich results."
{ "urls": ["https://www.allbirds.com/products/mens-tree-runners"] }
Pages that cannot be read are not charged and carry an errorType (blocked, not_found, robots_disallowed, unreachable, invalid_input, timeoutβ¦).
Use it from code and integrations
Run it from your own code with the Apify API. This call waits for the run and returns the results as JSON (replace YOUR_TOKEN with your Apify API token):
curl -X POST "https://api.apify.com/v2/acts/tidytools~structured-data-validator/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"urls":["https://www.allbirds.com/products/mens-tree-runners","https://www.ikea.com/us/en/p/billy-bookcase-white-00263850/","https://stripe.com/","https://webscraper.io/test-sites/e-commerce/allinone/product/60"]}'
The synchronous endpoint waits up to 5 minutes. For bigger runs, start the run with POST https://api.apify.com/v2/acts/tidytools~structured-data-validator/runs and read the dataset when it finishes, or use the apify-client package for JavaScript or Python.
Schedules and integrations: run it daily or weekly with Apify Schedules, get a webhook when a run finishes, or send the results to Zapier, Make, n8n, Google Sheets, Slack and other apps with Apify integrations. Results can be exported as JSON, CSV, Excel or XML.
Tips
- Use Browser mode if your markup is added by JavaScript or Google Tag Manager; the default reads the HTML that search engines fetch first.
- For large sites set Pages in the results to
problems+missing(orproblems); the summary still counts every page. - For a full on-page SEO audit (titles, meta descriptions, H1, canonical, alt text), use SEO Audit Crawler (by TidyTools). To find broken links, use Broken Link Checker (by TidyTools).
Advanced settings
- Plain HTTP requests from: Auto (recommended) reads pages from our servers and retries from Apify's network when a site blocks data-center requests, before falling back to a browser. Each result has
via(backend,directorbrowser). - Time limit per page: default 120 seconds for reading a page, all retries and the browser fallback included. A slower page gets
errorType: "timeout"and is not charged. 0 = no limit. - Proxy: optional Apify Proxy for requests sent from Apify's network (billed to your Apify account).
Limitations
- JSON-LD and Microdata are validated. RDFa is only detected: its types are listed in
rdfaTypes(with a note), but its properties are not checked. - The rules follow Google's documented required and recommended properties for common rich result types. Google alone decides whether a rich result appears, so "eligible" means "no blocking errors found", not a guarantee.
- Google shows FAQ rich results regularly only for well-known, authoritative government and health sites. Valid FAQ markup on another site is not invalid schema, but it does not mean Google will show an FAQ result; Google makes that decision. Check
faqGoogleDisplayStatusrather than treatingeligible: trueorrichResults: ["FAQ"]as display approval. Google no longer shows HowTo results; that markup is still validated and flagged with a note. See Google Search Central's FAQ and HowTo update (August 8, updated September 14, 2023). - robots.txt is respected for every page the Actor reads, including the pages you list: the
TidyToolsFetchgroup is used first, thenTidyTools, then*. A disallowed page (or a page that a redirect lands on and robots.txt disallows) is not read and gets a freerobots_disallowedrow;SUMMARY.skippedByRobotsTxtcounts them. In crawl and sitemap mode, disallowed pages are skipped and counted. - A JSON-LD block wrapped in
<!-- -->comments or//<![CDATA[markers is reported asINVALID_JSON, because the wrapper makes it invalid JSON; remove the wrapper. - A review nested in a Recipe, Article, Event or similar without
reviewRatingis a warning (that review is not shown), not an error: the Recipe itself stays eligible. In a Product, the same problem is an error.
Extract or validate? (checked September 2026)
Several Store Actors only extract JSON-LD. This one also validates it against Google's rich result rules:
| Actor | Price per 1,000 pages (free plan) | Validates against rich result rules |
|---|---|---|
| Schema Markup Validator (this Actor) | $2 | Yes, with value checks and issue codes |
| logiover/json-ld-schema-meta-tag-extractor | $5 | No (extraction only) |
| pink_comic/schema-markup-extractor | $2 plus a start fee | No (extraction only) |
| autofacts/metadata-scraper | $5; the max charge per run must be set to at least $0.10 | Metadata extraction and SEO score |
| maximedupre/schema-markup-validator | $0.05 | "Rich-result readiness signals"; reads JSON-LD, Microdata and RDFa; no value format checks mentioned |
| jungle_synthesizer/structured-data-validator-pro | $0.10 + $0.10 start fee per run (up to 15 URLs per run) | Required fields for 7 types |
maximedupre is much cheaper. Choose this Actor when you need Google's required and recommended properties for about 20 types, value format checks (dates, prices, currencies, availability), stable issue codes with JSON paths, sitemap input with URL patterns, @graph/@id handling and a real browser for pages that block plain requests or load almost empty until JavaScript runs (Browser mode renders every page, for markup added by Google Tag Manager).
FAQ
How many pages should I start with? Start with 5 to 10 URLs (about $0.01 to $0.02): paste them in URLs or domains and click Start. When the results look right, use the sitemap option with an include pattern such as https://example.com/products/** to check one section of the site, or leave the pattern out to check every page.
What is charged and what is free? Each validated page costs $2 per 1,000 pages, with no start fee, and pages without structured data are charged too, because knowing a page has none is the result. Pages that cannot be read (blocked, 4xx/5xx, not HTML, timed out, disallowed by robots.txt), invalid input lines (including private or local addresses) and duplicate URLs are free. Pages in the results only filters the dataset; every checked page is still charged.
How is it different from Google's Rich Results Test? You can check a list of URLs, every page in your sitemap with Validate the pages in the sitemap, or a whole site with Crawl the website, instead of pasting URLs into a testing tool one by one. Every problem comes with a stable issue code, severity and path, ready to filter in a spreadsheet, and each result links to Google's Rich Results Test in googleTestUrl. Google alone decides whether a rich result appears, so eligible means no blocking errors found, not a guarantee.
Does the Actor collect usage data? The Actor sends one anonymous usage counter per run (input type, item count range, outcome) to improve the tool; no URLs, content or account data.
Which structured data formats does it validate? It extracts schema.org markup in JSON-LD and Microdata and checks it against what Google needs for rich results, such as Product, Article, Breadcrumb, Recipe, Event and JobPosting. Microdata is validated with the same rules unless you turn off Also validate Microdata. RDFa is only detected: its types are listed in rdfaTypes, but its properties are not checked.
Can it check JSON-LD that is added by JavaScript or Google Tag Manager? Yes. Set Page fetching (HTTP or browser) to Browser to always validate the JavaScript-rendered page. The default Auto mode reads the raw HTML that search engines see first and uses a real browser only when a site blocks plain requests or the page comes back almost empty (a JavaScript app shell).
What happens if I give no valid URL? The run does not fail: the dataset gets a free row with errorType: "invalid_input" explaining what is wrong, SUMMARY.status is FAILED and nothing is charged.
Does it follow robots.txt? Yes, for every page it reads, including the ones you list. It identifies itself as TidyToolsFetch and reads the matching robots.txt group (TidyToolsFetch, then TidyTools, then *). A disallowed page is not requested and gets a free row with errorType: "robots_disallowed".
Support
Open an issue in the Issues tab with an example URL. Issues are checked regularly.
Found this useful? A short review on the Store helps other users find it.