Skip to content
Development
Skill

/wp-structured-data

Use when emitting JSON-LD structured data (schema.org) from a WordPress theme or plugin — FAQPage, HowTo, ItemList, Recipe, Event, Product, Review — especially when the data lives in post meta / custom fields the SEO plugin can't see, OR when you must avoid duplicating the

From plugin
wp-dev-skills
2719 skills1 command
Install
$ npx -y skills add mralaminahamed/wp-dev-skills --skill wp-structured-data --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/wp-structured-data

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when emitting JSON-LD structured data (schema.org) from a WordPress theme or plugin — FAQPage, HowTo, ItemList, Recipe, Event, Product, Review — especially when the data lives in post meta / custom fields the SEO plugin can't see, OR when you must avoid duplicating the

SKILL.md

wp-structured-data.SKILL.md
name: wp-structured-data
description: "Use when emitting JSON-LD structured data (schema.org) from a WordPress theme or plugin — FAQPage, HowTo, ItemList, Recipe, Event, Product, Review — especially when the data lives in post meta / custom fields the SEO plugin can't see, OR when you must avoid duplicating the schema an SEO plugin (Rank Math, Yoast SEO, SEOPress) already outputs. Covers building the @graph in PHP, escaping with wp_json_encode, printing on wp_head, validating, and coexisting with SEO-plugin graphs. Triggers: \"add FAQ schema\", \"FAQPage JSON-LD\", \"add structured data\", \"schema.org markup WordPress\", \"rich results\", \"HowTo schema\", \"ItemList schema\", \"my FAQ rich result isn't showing\", \"duplicate Article schema\", \"Rank Math already outputs schema\", \"Yoast schema graph\", \"json-ld in head or footer\", \"wp_json_encode schema\", \"schema for custom fields\", \"breadcrumb schema duplicate\", \"validate structured data\". Not for: configuring an SEO plugin's built-in schema UI (use the plugin's own settings); meta tags / OpenGraph (that's SEO-plugin territory); on-page heading/markup SEO."

WordPress Structured Data (JSON-LD)

> **Model note:** Building a single schema type is mechanical (`haiku`). Reach for `sonnet`/`opus` only 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.

When to use

  • Content lives in **post meta / custom fields** (e.g. an FAQ repeater) and is *stripped from the rendered content*, so Rank Math/Yoast can't detect it.
  • You render a **custom section** (table of contents, related items, steps) that warrants `ItemList` / `HowTo`.
  • You need a schema type the SEO plugin doesn't offer on that template.

Rule 0 — never duplicate the SEO plugin's graph

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`).

Pattern — build in PHP, print on `wp_head`

add_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";
} );

Key points

  • **Build a PHP array, then `wp_json_encode()`** — never hand-concatenate JSON. `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.)
  • **`@type` text:** `wp_strip_all_tags()` for short names/titles; `wp_kses_post()` for answer/description bodies that may carry HTML.
  • **head vs footer is irrelevant to Google** — it parses JSON-LD anywhere. Use `wp_head` to sit alongside the SEO plugin's graph (convention), or `wp_footer` if you need late data; both work.
  • **Gate to the right context** (`is_singular()`, post type, a feature toggle) so schema only appears where the content does.
  • **Mirror visible content.** Schema must reflect what users actually see on the page (Google's structured-data policy). Don't emit FAQ schema for FAQs you don't render.

Common types worth emitting from code

| 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** |

Validate

  • Google **Rich Results Test** (search.google.com/test/rich-results) — confirms eligibility per type.
  • Schema.org **validator** (validator.schema.org) — structural correctness.
  • Confirm there's exactly **one** node per type across all blocks (no duplicate `Article`/`FAQPage`).

Common Mistakes

| 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 t

Read more
Ships withwp-dev-skills

Covers the complete WordPress plugin development lifecycle — build, test, audit, release, and ship to WP.org — for Claude Code, Gemini CLI, Cursor, Windsurf, Cline, Codex, GitHub Copilot, opencode, and more.

Get the whole plugin
Stats
27
Stars
3
Forks
Maintained
Maintenance
PHP
Language
MIT
License
1mo ago
Last commit
3mo ago
Created

Repo: mralaminahamed/wp-dev-skills

Other skills on wp-dev-skills.