Structured data is machine-readable information added to a web page that helps search engines understand its content and display rich results — star ratings, FAQ dropdowns, breadcrumb trails, and article publication dates directly in the search results page. Google recommends JSON-LD (JavaScript Object Notation for Linked Data) as the preferred format because it is embedded in a <script> tag in the page head rather than inline with the HTML content, making it easier to add and maintain without touching the content markup. The Schema.org vocabulary defines the types and properties: Article for blog posts, BreadcrumbList for navigation context, FAQPage for question-and-answer content, Product for WooCommerce product pages, and Organization or WebSite for site-wide identity signals. In WordPress, the correct place to output JSON-LD is inside a wp_head action hook — the script tag renders in the <head> of every page, and the callback can check the current query type to output the appropriate schema for that page. The Article schema requires at minimum headline, datePublished, dateModified, author, and image. The FAQPage schema maps a list of question-and-answer pairs and is eligible for the FAQ rich result in Google Search, which can expand the SERP snippet significantly. Always validate your structured data with Google’s Rich Results Test tool after implementation. Combine this with the canonical URL guide and the custom sitemap guide for a complete on-page SEO setup.
Problem: Your WordPress posts and pages lack structured data, so Google cannot generate rich results like article cards, FAQ dropdowns, or breadcrumb trails for your content in search results.
Solution: Hook into wp_head and output JSON-LD schema for the current page type:
add_action( 'wp_head', 'ha_output_json_ld', 5 );
function ha_output_json_ld() {
$schemas = [];
// Article schema for single posts
if ( is_single() && get_post_type() === 'post' ) {
$post = get_queried_object();
$author = get_the_author_meta( 'display_name', $post->post_author );
$thumbnail = get_the_post_thumbnail_url( $post->ID, 'large' );
$schemas[] = array_filter( [
'@context' => 'https://schema.org',
'@type' => 'Article',
'headline' => get_the_title( $post->ID ),
'datePublished' => get_the_date( 'c', $post->ID ),
'dateModified' => get_the_modified_date( 'c', $post->ID ),
'author' => [ '@type' => 'Person', 'name' => $author ],
'image' => $thumbnail ?: null,
'publisher' => [
'@type' => 'Organization',
'name' => get_bloginfo( 'name' ),
'logo' => [ '@type' => 'ImageObject', 'url' => get_site_icon_url() ],
],
] );
}
// BreadcrumbList for all non-front pages
if ( ! is_front_page() ) {
$items = [];
$items[] = [ '@type' => 'ListItem', 'position' => 1, 'name' => 'Home', 'item' => home_url( '/' ) ];
if ( is_singular() ) {
$cats = get_the_category();
if ( $cats ) {
$items[] = [ '@type' => 'ListItem', 'position' => 2, 'name' => esc_html( $cats[0]->name ), 'item' => get_category_link( $cats[0]->term_id ) ];
$items[] = [ '@type' => 'ListItem', 'position' => 3, 'name' => esc_html( get_the_title() ) ];
}
}
$schemas[] = [ '@context' => 'https://schema.org', '@type' => 'BreadcrumbList', 'itemListElement' => $items ];
}
// FAQPage schema — read from post meta (set via custom field or block)
if ( is_singular() ) {
$faqs = get_post_meta( get_the_ID(), '_ha_faq_items', true );
if ( is_array( $faqs ) && ! empty( $faqs ) ) {
$entities = array_map( function( $faq ) {
return [
'@type' => 'Question',
'name' => sanitize_text_field( $faq['question'] ),
'acceptedAnswer' => [ '@type' => 'Answer', 'text' => wp_kses_post( $faq['answer'] ) ],
];
}, $faqs );
$schemas[] = [ '@context' => 'https://schema.org', '@type' => 'FAQPage', 'mainEntity' => $entities ];
}
}
foreach ( $schemas as $schema ) {
echo '' . "
";
}
}
NOTE: The array_filter() call on the Article schema removes null values — this prevents outputting properties with empty values, which would fail Google’s Rich Results Test validation. For the FAQPage schema, store FAQ data in the _ha_faq_items post meta as a serialised array via a custom metabox or ACF repeater field. Always test structured data after deployment with the Rich Results Test and the Schema Markup Validator at schema.org/SchemaValidator to catch property name errors before Google’s crawler sees them.