Use Case
Schema errors created at publish time don't surface until Google drops your rich results — sometimes weeks later. SchemaCheck gives you an API to catch them the moment content is published, not after.
Most schema errors are introduced by CMS updates, theme changes, or content editors removing structured data blocks. By the time you notice a drop in rich results, the damage has already been done. Validating on publish catches issues within seconds — before they reach Google.
Hours
to notice a schema regression from Google Search Console
Seconds
to catch the same error with a publish-time webhook
100%
of schema errors are preventable with CI or publish hooks
WordPress + WooCommerce
Hook into publish_post, publish_page, and woocommerce_update_product. Display schema health scores in the admin editor.
Shopify Apps
Verify HMAC-signed webhooks on products/create and products/update. Write schema scores back as metafields.
Webflow CMS
Receive cms_item_published webhooks and validate every newly published collection item.
Any CMS with webhooks
The API is platform-agnostic. If your CMS can fire an HTTP request on publish, SchemaCheck can validate it.
Hook into WordPress publish actions to validate schema on every post, page, or WooCommerce product update. Shows schema score and errors directly in the admin editor.
<?php
/**
* Plugin Name: SchemaCheck Validator
* Description: Validate Schema.org structured data on post publish/update
*/
define('SCHEMACHECK_API_KEY', get_option('schemacheck_api_key'));
define('SCHEMACHECK_ENDPOINT', 'https://schemacheck.dev/api/v1/validate');
// Hook into post publish and update
add_action('publish_post', 'schemacheck_validate_on_publish', 10, 2);
add_action('publish_page', 'schemacheck_validate_on_publish', 10, 2);
add_action('woocommerce_update_product', 'schemacheck_validate_product');
function schemacheck_validate_on_publish($post_id, $post) {
$permalink = get_permalink($post_id);
$result = schemacheck_validate_url($permalink);
if (!$result || !$result['success']) return;
$errors = array_merge(
...array_map(fn($s) => $s['errors'], $result['schemas'])
);
if (count($errors) > 0) {
// Store validation result as post meta for admin notices
update_post_meta($post_id, '_schemacheck_errors', $errors);
update_post_meta($post_id, '_schemacheck_score', $result['summary']['score']);
// Optionally notify via email
$admin_email = get_option('admin_email');
wp_mail(
$admin_email,
"Schema errors on: {$post->post_title}",
"Found " . count($errors) . " schema error(s) on {$permalink}\n\n" .
implode("\n", array_map(fn($e) => "• {$e['property']}: {$e['message']}", $errors))
);
}
}
function schemacheck_validate_url($url) {
$response = wp_remote_get(
SCHEMACHECK_ENDPOINT . '?url=' . urlencode($url) . '&access_key=' . SCHEMACHECK_API_KEY,
['timeout' => 30]
);
if (is_wp_error($response)) return null;
return json_decode(wp_remote_retrieve_body($response), true);
}
// Admin notice on post edit screen
add_action('admin_notices', function() {
$screen = get_current_screen();
if (!in_array($screen->base, ['post', 'page'])) return;
$post_id = get_the_ID();
$errors = get_post_meta($post_id, '_schemacheck_errors', true);
$score = get_post_meta($post_id, '_schemacheck_score', true);
if (!$errors || count($errors) === 0) return;
echo '<div class="notice notice-warning">
<p><strong>Schema issues detected</strong> (score: ' . esc_html($score) . '/100)</p>
<ul>' .
implode('', array_map(
fn($e) => '<li>' . esc_html($e['property']) . ': ' . esc_html($e['message']) . '</li>',
$errors
)) .
'</ul></div>';
});Build a Shopify app that validates product schema on every create or update webhook. Writes the schema health score back as a product metafield so it's visible in the Shopify admin.
// Shopify app extension — validate schema on product create/update
// Uses Shopify webhooks + SchemaCheck API
import { json } from "@remix-run/node";
import crypto from "crypto";
const SCHEMACHECK_KEY = process.env.SCHEMACHECK_API_KEY!;
const SHOPIFY_SECRET = process.env.SHOPIFY_WEBHOOK_SECRET!;
// Verify Shopify webhook signature
function verifyWebhook(body: string, hmacHeader: string): boolean {
const digest = crypto
.createHmac("sha256", SHOPIFY_SECRET)
.update(body, "utf8")
.digest("base64");
return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(hmacHeader));
}
export async function action({ request }: { request: Request }) {
const body = await request.text();
const hmac = request.headers.get("x-shopify-hmac-sha256") ?? "";
const topic = request.headers.get("x-shopify-topic") ?? "";
const shopDomain = request.headers.get("x-shopify-shop-domain") ?? "";
if (!verifyWebhook(body, hmac)) {
return json({ error: "Unauthorized" }, { status: 401 });
}
// Handle products/create and products/update
if (!["products/create", "products/update"].includes(topic)) {
return json({ skipped: true });
}
const product = JSON.parse(body);
const productUrl = `https://${shopDomain}/products/${product.handle}`;
// Give Shopify time to publish before fetching
await new Promise((r) => setTimeout(r, 2000));
const result = await fetch(
`https://schemacheck.dev/api/v1/validate?url=${encodeURIComponent(productUrl)}&access_key=${SCHEMACHECK_KEY}`
).then((r) => r.json());
if (!result.success) return json({ validated: false });
const productSchema = result.schemas.find(
(s: { type: string }) => s.type === "Product"
);
if (productSchema?.errors.length > 0) {
// Create a metafield on the product with the schema health score
// so it's visible in the Shopify admin
await setProductMetafield(shopDomain, product.id, {
namespace: "schemacheck",
key: "score",
value: String(result.summary.score),
type: "number_integer",
});
console.log(`Schema issues on ${productUrl}:`, productSchema.errors);
}
return json({ score: result.summary.score, errors: productSchema?.errors ?? [] });
}
async function setProductMetafield(
shop: string,
productId: number,
metafield: { namespace: string; key: string; value: string; type: string }
) {
// Use Shopify Admin API to write metafield
const endpoint = `https://${shop}/admin/api/2024-01/products/${productId}/metafields.json`;
await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Shopify-Access-Token": process.env.SHOPIFY_ACCESS_TOKEN!,
},
body: JSON.stringify({ metafield }),
});
}Receive Webflow's cms_item_published webhook, validate each page, and post a Slack alert if required properties are missing. Deploy as a Vercel function or Cloudflare Worker.
// Webflow CMS integration — validate on publish via Webflow webhook
// Deploy as a serverless function (Vercel, Netlify, or Cloudflare Workers)
export async function POST(request: Request) {
const payload = await request.json();
// Webflow sends { site, items } on cms_item_published
const { site, items } = payload;
if (!items || items.length === 0) {
return Response.json({ skipped: true });
}
const results = await Promise.all(
items.map(async (item: { slug: string; url: string }) => {
const pageUrl = item.url ?? `https://${site.shortName}.webflow.io/${item.slug}`;
const res = await fetch(
`https://schemacheck.dev/api/v1/validate?url=${encodeURIComponent(pageUrl)}&access_key=${process.env.SCHEMACHECK_API_KEY}`
);
const data = await res.json();
return {
url: pageUrl,
score: data.summary?.score ?? 0,
errors: data.schemas?.flatMap((s: { errors: unknown[] }) => s.errors) ?? [],
richResultEligible: data.summary?.rich_result_eligible ?? false,
};
})
);
// Post summary to Slack if any items have errors
const itemsWithErrors = results.filter((r) => r.errors.length > 0);
if (itemsWithErrors.length > 0 && process.env.SLACK_WEBHOOK_URL) {
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `⚠️ Schema issues detected on ${itemsWithErrors.length} newly published page(s)`,
attachments: itemsWithErrors.map((item) => ({
color: "warning",
title: item.url,
text: `Score: ${item.score}/100 — ${item.errors.length} error(s)\n` +
item.errors
.slice(0, 3)
.map((e: { property: string; message: string }) => `• ${e.property}: ${e.message}`)
.join("\n"),
})),
}),
});
}
return Response.json({ validated: results.length, errors: itemsWithErrors.length });
}Publish webhook → validate → admin notice
Trigger validation immediately after publish. Show schema errors inline in the CMS editor so authors can fix them before the page is live for long.
Validate → write score back as metadata
Store the schema health score as a custom field or metafield. Query pages with scores below 80 for targeted remediation.
Validate → Slack or email alert
Route schema errors to the team who can fix them — SEO leads, developers, or content managers — without requiring anyone to manually check.
Nightly cron across full content archive
Re-validate your entire content library weekly. Catch regressions introduced by theme updates, plugin upgrades, or third-party schema injectors.
“A Yoast plugin update removed the
datePublishedfield from our Article schema across 4,000 posts. Our publish hook caught it on the first post — we fixed the config before the rest of the site was affected.”
Free plan — 100 validations/month. No credit card. The webhook handler above takes about 30 minutes to deploy.