wp-structured-data — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited wp-structured-data (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Model note: Building a single schema type is mechanical (haiku). Reach forsonnet/opusonly when reconciling a complex graph against an existing SEO-plugin graph.
Emit schema.org JSON-LD from theme/plugin code for content an SEO plugin can't generate on its own — without duplicating what the SEO plugin already ships.
ItemList / HowTo.Most sites already run Rank Math, Yoast, or SEOPress. They emit a @graph with WebSite, WebPage, Organization, Person, BreadcrumbList, and an article type (Article/BlogPosting/Product). Never re-emit those — duplicate/competing nodes confuse parsers and can suppress rich results.
Always check first what is already on the page:
# View source, then list the @type values in every ld+json block
curl -s "<url>" | grep -A99 'application/ld+json'Or in the browser console:
[...document.querySelectorAll('script[type="application/ld+json"]')]
.flatMap(s => { const j = JSON.parse(s.textContent); return (j['@graph']||[j]).map(n => n['@type']); });Only add types the existing graph is missing (commonly FAQPage, HowTo, Recipe, custom ItemList).
wp_headadd_action( 'wp_head', function () {
if ( ! is_singular( 'post' ) ) {
return;
}
$faqs = get_post_meta( get_the_ID(), 'my_faqs', true ); // stripped from content
if ( empty( $faqs ) || ! is_array( $faqs ) ) {
return;
}
$questions = array();
foreach ( $faqs as $faq ) {
if ( empty( $faq['question'] ) ) {
continue;
}
$questions[] = array(
'@type' => 'Question',
'name' => wp_strip_all_tags( $faq['question'] ),
'acceptedAnswer' => array(
'@type' => 'Answer',
'text' => wp_kses_post( wpautop( $faq['answer'] ?? '' ) ),
),
);
}
if ( ! $questions ) {
return;
}
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => $questions,
);
echo '<script type="application/ld+json">' . wp_json_encode( $schema ) . '</script>' . "\n";
} );wp_json_encode() escapes for the <script> context; output is safe to print as-is. (PHPCS's EscapeOutput sniff doesn't recognise it as an escaper; suppress it via a centralized phpcs.xml.dist exclude scoped to the file rather than scattering inline ignores.)wp_strip_all_tags() for short names/titles; wp_kses_post() for answer/description bodies that may carry HTML.wp_head to sit alongside the SEO plugin's graph (convention), or wp_footer if you need late data; both work.is_singular(), post type, a feature toggle) so schema only appears where the content does.| Type | Use for | Usually missing from SEO plugins? |
|---|---|---|
FAQPage | Q&A stored in meta / repeater | Yes, when stripped from content |
HowTo | Step-by-step tutorial sections | Often |
ItemList | TOC, related items, rankings | Yes |
Recipe | Recipe meta fields | Yes (unless a recipe plugin) |
Article/BlogPosting | The post itself | No — SEO plugin owns this |
BreadcrumbList | Breadcrumb trail | No — SEO plugin owns this |
Article/FAQPage).| Mistake | Fix |
|---|---|
Re-emitting Article/BreadcrumbList/Organization the SEO plugin already outputs | Never duplicate — inspect existing ld+json first; add only missing types |
| Hand-building the JSON string | Always build a PHP array + wp_json_encode() |
| FAQ schema present but FAQs not visible on the page | Mirror visible content — only emit schema for rendered content |
| Assuming head vs footer affects indexing | It doesn't; place by convention (wp_head) |
Inline // phpcs:ignore for the wp_json_encode echo | Centralize the EscapeOutput exclude in phpcs.xml.dist, scoped to the file |
| Emitting schema on every template | Gate with is_singular() / post type / feature toggle |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.