Breadcrumb navigation shows users their location within a site hierarchy and provides search engines with a machine-readable signal about the content structure — Google displays breadcrumbs in search result snippets when BreadcrumbList JSON-LD is present. WordPress does not include built-in breadcrumb output, but the combination of conditional template tags and the get_ancestors() function provides all the information needed to build one from scratch. JSON-LD structured data for breadcrumbs is output in a separate <script type="application/ld+json"> block in the document head, completely decoupled from the visible HTML breadcrumb trail. The BreadcrumbList schema type contains an ordered list of ListItem entries with name, item (URL), and position properties — the position is 1-indexed. For hierarchical post types like pages, get_ancestors() returns parent IDs in ascending order (root first), which maps directly to the breadcrumb trail. Custom post types with hierarchical taxonomies — categories, custom terms — require fetching the term via get_queried_object() and walking get_term_parents_list() to build the ancestor chain. The visible HTML breadcrumb uses <nav aria-label="Breadcrumb"> and <ol> with aria-current="page" on the final item to meet WCAG 2.1 accessibility guidelines. All URL values must pass through esc_url() and all display strings through esc_html() before output to prevent XSS via crafted post titles. The JSON-LD structured data guide covers Article and FAQ schema that should accompany breadcrumb markup on post pages. The canonical URL post explains how the canonical and breadcrumb URL should align to send consistent hierarchy signals to search engines. Test the JSON-LD output with Google’s Rich Results Test to confirm the breadcrumb snippet is eligible to appear in search results before deploying to production.
Problem: WordPress themes without a breadcrumb component provide no visual site hierarchy to users and no BreadcrumbList JSON-LD to search engines, missing out on rich result breadcrumb snippets in Google search.
Solution: Build a PHP function that outputs an accessible HTML breadcrumb trail and an inline BreadcrumbList JSON-LD script in wp_head, covering singular posts, hierarchical pages, and taxonomy archives.
function render_breadcrumb(): void {
$items = [['name' => 'Home', 'url' => home_url('/')]];
if (is_singular()) {
$post = get_queried_object();
$cats = wp_get_post_terms($post->ID, 'category');
if (!empty($cats) && !is_wp_error($cats)) {
$cat = $cats[0];
$parents = array_reverse(get_ancestors($cat->term_id, 'category', 'taxonomy'));
foreach ($parents as $pid) {
$t = get_term($pid, 'category');
$items[] = ['name' => $t->name, 'url' => get_term_link($t)];
}
$items[] = ['name' => $cat->name, 'url' => get_term_link($cat)];
}
$items[] = ['name' => get_the_title($post), 'url' => get_permalink($post)];
} elseif (is_tax() || is_category() || is_tag()) {
$term = get_queried_object();
$parents = array_reverse(get_ancestors($term->term_id, $term->taxonomy, 'taxonomy'));
foreach ($parents as $pid) {
$t = get_term($pid, $term->taxonomy);
$items[] = ['name' => $t->name, 'url' => get_term_link($t)];
}
$items[] = ['name' => $term->name, 'url' => get_term_link($term)];
}
// Visible HTML breadcrumb
echo '<nav aria-label="Breadcrumb"><ol class="breadcrumb">';
foreach ($items as $i => $item) {
$last = ($i === count($items) - 1);
echo $last
? '<li aria-current="page">' . esc_html($item['name']) . '</li>'
: '<li><a href="' . esc_url($item['url']) . '">' . esc_html($item['name']) . '</a></li>';
}
echo '</ol></nav>';
}
// Inline JSON-LD BreadcrumbList
add_action('wp_head', function() {
if (!is_singular() && !is_tax() && !is_category() && !is_tag()) return;
ob_start();
render_breadcrumb();
ob_end_clean();
$items = [['name' => 'Home', 'url' => home_url('/')]];
if (is_singular()) {
$post = get_queried_object();
$cats = wp_get_post_terms($post->ID, 'category');
if (!empty($cats) && !is_wp_error($cats)) {
$items[] = ['name' => $cats[0]->name, 'url' => get_term_link($cats[0])];
}
$items[] = ['name' => get_the_title($post), 'url' => get_permalink($post)];
}
$list = array_map(function($item, $idx) {
return ['@type' => 'ListItem', 'position' => $idx + 1,
'name' => $item['name'], 'item' => $item['url']];
}, $items, array_keys($items));
echo '<script type="application/ld+json">'
. wp_json_encode(['@context' => 'https://schema.org',
'@type' => 'BreadcrumbList', 'itemListElement' => $list])
. '</script>' . PHP_EOL;
}, 10);
NOTE: Call render_breadcrumb() inside your single.php or page.php template, just above the <article> tag — the JSON-LD is output automatically via the wp_head hook, so no separate call is needed in the template head.