DEV Community

Cover image for Breadcrumb Schema Markup: The Complete Guide to Structured Data for Site Navigation
Roman Popovych
Roman Popovych

Posted on

Breadcrumb Schema Markup: The Complete Guide to Structured Data for Site Navigation

Below the title in a search result, Google shows either a raw URL or a clean path like example.com › Tools › Image Converter. The path version comes from breadcrumb schema. It's one of the smallest implementation efforts in structured data and one of the most consistently effective — it works for almost any site, not just specific content types.

This is also where a lot of developers get tripped up on small things. The position field needs to be sequential with no gaps. Every URL in item needs to resolve. Minimum two items — a single-item breadcrumb is invalid per spec. These aren't complicated rules, but they're the ones that silently break implementations.

What's covered: the correct structure for shallow and deep hierarchies, how to generate breadcrumbs dynamically from URL paths, combining breadcrumb schema with other schema types on the same page, and the six mistakes that show up most often in validation reports.


What Is Breadcrumb Schema?

BreadcrumbList is a schema.org type that describes a page's position in a site hierarchy — the navigation path from the homepage to the current page. You implement it as JSON-LD and Google uses it to replace the URL display in search results with a human-readable path.

{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://example.com"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Tools",
      "item": "https://example.com/tools"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "Image Converter",
      "item": "https://example.com/tools/image-converter"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

This produces example.com › Tools › Image Converter in search results instead of the raw URL.


What Breadcrumb Schema Looks Like in Search Results

The breadcrumb trail replaces the green URL text that appears below the page title in standard search results. Before breadcrumb schema (or when it's not detected), Google shows the URL — often long, often ugly, often including query parameters. With it, you get a clean hierarchical path.

Before: https://example.com/en/tools/image/converter?format=webp&quality=85

After: example.com › Tools › Image Converter

The improvement has two practical effects:

Readability. Users understand immediately where this page lives in your site's structure. A page that says Tools › Image Converter communicates category context before the user clicks.

CTR. Cleaner, shorter path displays often improve click-through rates, particularly on mobile where long URLs truncate awkwardly. The breadcrumb display is also slightly more compact, giving more visible space to the title and description.


When Google Shows Breadcrumbs in Search Results

Google uses two sources to generate breadcrumb trails in search results:

  1. Breadcrumb schema — explicit structured data you provide
  2. URL structure — Google's own parsing of your URL path as a fallback

If you implement breadcrumb schema correctly, Google uses your data and shows it as you've defined it. If you don't, Google attempts to construct a breadcrumb from your URL structure — with varying accuracy.

The practical implication: even if your URL structure is clean (/tools/image-converter), you should still implement breadcrumb schema. The schema version gives you precise control over:

  • What names appear at each level
  • Which URLs each crumb points to
  • How many levels to show

URL-derived breadcrumbs sometimes produce odd results with dynamic routes, query parameters, or flat URL structures. Schema removes that uncertainty.


The Minimum Valid Breadcrumb Schema

Google requires at least two ListItem objects in a BreadcrumbList. A single-item list is invalid — breadcrumbs exist to show hierarchy, and hierarchy requires at least two levels. Each item must have position and name. The item (URL) is technically optional for the last item — since it represents the current page, Google infers it from the crawled URL.

For a two-level hierarchy (home → page):

{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://example.com"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "About",
      "item": "https://example.com/about"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

For most tools and blog pages this is all you need. The current page is the last item. The homepage is always position 1.


Breadcrumb Schema for Deep Hierarchies

E-commerce sites, documentation sites, and blogs with categories often have three or four levels of hierarchy. Each level gets its own ListItem:

{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://shop.example.com"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Electronics",
      "item": "https://shop.example.com/electronics"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "Cameras",
      "item": "https://shop.example.com/electronics/cameras"
    },
    {
      "@type": "ListItem",
      "position": 4,
      "name": "Sony A7C II",
      "item": "https://shop.example.com/electronics/cameras/sony-a7c-ii"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

In search results: shop.example.com › Electronics › Cameras › Sony A7C II

Rules for deep hierarchies:

  • position values must be sequential integers starting at 1 — no gaps, no duplicates
  • Each intermediate level should have a real, accessible URL in item
  • The final item represents the current page — its item should match the canonical URL of the page

Field Reference

Field Type Required Notes
@type Text Yes Must be "BreadcrumbList"
itemListElement Array Yes Array of ListItem objects
ListItem.@type Text Yes Must be "ListItem"
ListItem.position Integer Yes Sequential starting at 1. No gaps.
ListItem.name Text Yes Human-readable label for this level
ListItem.item URL Conditional Required for all items except the last. Recommended for the last item too.

That's the complete field set. Breadcrumb schema is deliberately simple — the value is in correct structure and real URLs, not in field variety.


The item Field: Should the Last Crumb Have a URL?

Technically, Google says item is not required for the last list item — since the last crumb represents the current page, Google can infer the URL from the page being crawled.

In practice: always include item for every crumb, including the last one.

Reasons:

  • Explicit is always safer than inferred
  • Some validators throw warnings when the last item has no item URL
  • It guarantees the correct canonical URL is associated with the last crumb, which matters for pages with URL parameters or multiple access paths

The last item's item URL should exactly match the canonical URL of the page — the same URL in your <link rel="canonical"> tag.


Matching Breadcrumb Schema to Visible Breadcrumb HTML

Google requires your breadcrumb structured data to correspond to an actual breadcrumb trail visible on the page. This is the most important rule and the most commonly violated one.

If your page has a visible breadcrumb navigation:

<nav aria-label="Breadcrumb">
  <ol>
    <li><a href="/">Home</a></li>
    <li><a href="/tools">Tools</a></li>
    <li aria-current="page">Image Converter</li>
  </ol>
</nav>
Enter fullscreen mode Exit fullscreen mode

Your JSON-LD must mirror this exactly. Same names, same URLs, same order.

If there's no visible breadcrumb on the page at all — Google may still process the schema, but the risk of a quality flag increases. The best implementation pairs HTML breadcrumb navigation (good for users) with JSON-LD breadcrumb schema (good for machines). They reference the same data.


Combining BreadcrumbList with Other Schema Types

A page can have both BreadcrumbList and another primary schema type. This is common and recommended:

<!-- Primary schema: what the page is about -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "WebApplication",
  "name": "Image Converter",
  "url": "https://devtools.abect.com/png-to-jpg"
}
</script>

<!-- Navigation schema: where the page lives in the site -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://devtools.abect.com"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Image Tools",
      "item": "https://devtools.abect.com/image-tools"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "PNG to JPG",
      "item": "https://devtools.abect.com/png-to-jpg"
    }
  ]
}
</script>
Enter fullscreen mode Exit fullscreen mode

Two separate <script> blocks — one for the primary content type, one for navigation. Don't merge them into a single object.


Alternative: Embedding BreadcrumbList Inside WebPage Schema

Some implementations nest the breadcrumb inside a WebPage schema using the breadcrumb property:

{
  "@context": "https://schema.org",
  "@type": "WebPage",
  "name": "PNG to JPG Converter",
  "url": "https://example.com/png-to-jpg",
  "breadcrumb": {
    "@type": "BreadcrumbList",
    "itemListElement": [
      {
        "@type": "ListItem",
        "position": 1,
        "name": "Home",
        "item": "https://example.com"
      },
      {
        "@type": "ListItem",
        "position": 2,
        "name": "PNG to JPG",
        "item": "https://example.com/png-to-jpg"
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

This is valid schema.org syntax. Google supports both the standalone BreadcrumbList and the nested breadcrumb property approach. Use whichever fits your existing schema architecture — if you already have WebPage schema, nesting it is clean. If you don't, use a standalone block.


Implementation in Next.js (App Router)

For a tool site where every tool lives at a flat URL like /tool-name:

// components/BreadcrumbSchema.tsx

type BreadcrumbItem = {
  name: string
  url: string
}

export function BreadcrumbSchema({ items }: { items: BreadcrumbItem[] }) {
  const schema = {
    '@context': 'https://schema.org',
    '@type': 'BreadcrumbList',
    itemListElement: items.map((item, index) => ({
      '@type': 'ListItem',
      position: index + 1,
      name: item.name,
      item: item.url,
    })),
  }

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
    />
  )
}
Enter fullscreen mode Exit fullscreen mode

Usage in a tool page:

// app/png-to-jpg/page.tsx

import { BreadcrumbSchema } from '@/components/BreadcrumbSchema'

export default function PngToJpgPage() {
  return (
    <>
      <BreadcrumbSchema
        items={[
          { name: 'Home', url: 'https://devtools.abect.com' },
          { name: 'PNG to JPG', url: 'https://devtools.abect.com/png-to-jpg' },
        ]}
      />
      {/* page content */}
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

For category-based sites, pass the category as the middle item:

<BreadcrumbSchema
  items={[
    { name: 'Home', url: 'https://example.com' },
    { name: 'Electronics', url: 'https://example.com/electronics' },
    { name: 'Cameras', url: 'https://example.com/electronics/cameras' },
    { name: 'Sony A7C II', url: 'https://example.com/electronics/cameras/sony-a7c-ii' },
  ]}
/>
Enter fullscreen mode Exit fullscreen mode

The component handles position automatically via the array index — one less thing to manually maintain.


Dynamic Breadcrumbs from URL Path

For sites where the URL structure directly maps to the breadcrumb hierarchy (clean, no vanity URLs), you can generate the breadcrumb schema dynamically from the current path:

// lib/breadcrumbs.ts

const LABELS: Record<string, string> = {
  '': 'Home',
  'tools': 'Tools',
  'image': 'Image Tools',
  'seo': 'SEO Tools',
  'png-to-jpg': 'PNG to JPG',
  'compress-jpg': 'Compress JPG',
  'meta-tags-generator': 'Meta Tags Generator',
}

export function buildBreadcrumbsFromPath(pathname: string, baseUrl: string) {
  const segments = pathname.split('/').filter(Boolean)

  const items = [{ name: 'Home', url: baseUrl }]

  let accumulatedPath = baseUrl
  for (const segment of segments) {
    accumulatedPath = `${accumulatedPath}/${segment}`
    items.push({
      name: LABELS[segment] ?? segment.replace(/-/g, ' '),
      url: accumulatedPath,
    })
  }

  return items
}
Enter fullscreen mode Exit fullscreen mode

Usage:

import { usePathname } from 'next/navigation'
import { buildBreadcrumbsFromPath } from '@/lib/breadcrumbs'
import { BreadcrumbSchema } from '@/components/BreadcrumbSchema'

export default function Page() {
  const pathname = usePathname()
  const breadcrumbs = buildBreadcrumbsFromPath(pathname, 'https://devtools.abect.com')

  return (
    <>
      <BreadcrumbSchema items={breadcrumbs} />
      {/* content */}
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

The LABELS map handles the display name for each segment — because png-to-jpg should display as "PNG to JPG", not "png to jpg". Segments without a defined label fall back to replacing hyphens with spaces.


Implementation in React (with react-helmet-async)

import { Helmet } from 'react-helmet-async'
import { useLocation } from 'react-router-dom'

function BreadcrumbSchema({ crumbs }) {
  const schema = {
    '@context': 'https://schema.org',
    '@type': 'BreadcrumbList',
    itemListElement: crumbs.map((crumb, i) => ({
      '@type': 'ListItem',
      position: i + 1,
      name: crumb.name,
      item: crumb.url,
    })),
  }

  return (
    <Helmet>
      <script type="application/ld+json">
        {JSON.stringify(schema)}
      </script>
    </Helmet>
  )
}

// Usage:
function ToolPage() {
  return (
    <>
      <BreadcrumbSchema
        crumbs={[
          { name: 'Home', url: 'https://example.com' },
          { name: 'Tools', url: 'https://example.com/tools' },
          { name: 'Image Converter', url: 'https://example.com/tools/image-converter' },
        ]}
      />
      {/* tool UI */}
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

Implementation in WordPress

WordPress has a built-in breadcrumb trail concept via plugins (Yoast, RankMath) or theme functions. For manual implementation without plugins:

function add_breadcrumb_schema() {
  // Build breadcrumbs based on current page type
  $items = [];
  $position = 1;
  $base_url = home_url( '/' );

  // Always start with home
  $items[] = [
    '@type'    => 'ListItem',
    'position' => $position++,
    'name'     => 'Home',
    'item'     => $base_url,
  ];

  if ( is_category() || is_single() ) {
    $categories = get_the_category();
    if ( $categories ) {
      $cat = $categories[0];
      $items[] = [
        '@type'    => 'ListItem',
        'position' => $position++,
        'name'     => $cat->name,
        'item'     => get_category_link( $cat->term_id ),
      ];
    }
  }

  if ( is_single() || is_page() ) {
    $items[] = [
      '@type'    => 'ListItem',
      'position' => $position,
      'name'     => get_the_title(),
      'item'     => get_permalink(),
    ];
  }

  if ( count( $items ) < 2 ) return; // Don't output single-item breadcrumbs

  $schema = [
    '@context'        => 'https://schema.org',
    '@type'           => 'BreadcrumbList',
    'itemListElement' => $items,
  ];

  echo '<script type="application/ld+json">' . wp_json_encode( $schema ) . '</script>';
}
add_action( 'wp_head', 'add_breadcrumb_schema' );
Enter fullscreen mode Exit fullscreen mode

For WooCommerce product pages, add a check for is_product() and use wc_get_breadcrumb() to get the WooCommerce breadcrumb data, then map it to the schema structure.


The 6 Most Common Breadcrumb Schema Mistakes

1. position values with gaps or duplicates

// Wrong  gap at 2, duplicate at 3
[
  { "position": 1, "name": "Home" },
  { "position": 3, "name": "Tools" },
  { "position": 3, "name": "Image Converter" }
]

// Correct  sequential, no gaps
[
  { "position": 1, "name": "Home" },
  { "position": 2, "name": "Tools" },
  { "position": 3, "name": "Image Converter" }
]
Enter fullscreen mode Exit fullscreen mode

Google's validator flags non-sequential positions as errors. The position values must be consecutive integers starting at 1.

2. item URLs that don't match actual pages

Every URL in item must resolve to a real, accessible page. If /tools returns 404 but you include it as a breadcrumb item, Google will flag it. Only include levels that have real pages.

3. Breadcrumb schema that doesn't match the visible breadcrumb

If the page shows "Home > Tools > Converter" but the schema says "Home > Image > PNG Converter", it's a mismatch. Google cross-validates structured data against visible content. Keep them in sync — ideally by generating both from the same data source.

4. Using a single-item breadcrumb

A BreadcrumbList with only one item — just the current page — is pointless and may trigger a warning. Breadcrumbs are about hierarchy. If a page is the homepage, it doesn't need a breadcrumb. If it's one level deep, include at minimum Home + current page (two items).

5. Non-canonical item URLs

The item URLs must match the canonical URLs of those pages — the same URLs in their <link rel="canonical"> tags. Don't use trailing-slash variants, www vs non-www variants, or http vs https variants that differ from canonical. Inconsistency weakens the schema's value.

6. Putting @type: BreadcrumbList in the wrong place

A BreadcrumbList should not be nested inside properties of other schema types (except the breadcrumb property of WebPage). It should be a top-level JSON-LD object or nested specifically under WebPage.breadcrumb. Nesting it inside, say, a Product object is invalid.


Breadcrumb Best Practices for SEO

Beyond the structural requirements, a few content decisions affect how useful your breadcrumbs are:

Use meaningful names, not URLs. The name field is what users see in search results. "Image Tools" reads better than "image-tools" or "Image_Tools". Capitalize correctly and use the human-readable label, not the slug.

Reflect your actual URL structure. Your breadcrumb schema should mirror your URL hierarchy. If your URL is /tools/image/png-to-jpg, the breadcrumb should be Home › Tools › Image › PNG to JPG. Don't fabricate a hierarchy that doesn't exist in your URL structure — it confuses both users and crawlers.

Keep depth reasonable. Google will display breadcrumbs in search results, but very deep hierarchies (6+ levels) may be truncated. More than 4 levels is rare for most sites. If you find yourself at 5+ levels, consider whether your site architecture needs simplification.

Include breadcrumbs on every non-homepage page. Homepage doesn't need a breadcrumb. Every other page benefits from one — it's a small implementation cost with consistent SEO upside.


Validating Breadcrumb Schema

Google Rich Results Testsearch.google.com/test/rich-results

Paste the page URL or raw JSON-LD. The test shows whether your BreadcrumbList is detected and lists any errors. Common errors include non-sequential positions, missing name fields, and unreachable item URLs.

Schema.org Validatorvalidator.schema.org

Validates the structural correctness of your JSON-LD. Use it to catch type errors that Google's tool sometimes misses.

Live search preview: After deploying, search your site by name in Google and check whether the URL display has changed to the breadcrumb format. This can take anywhere from a few days to a few weeks after Google re-crawls the page.

Google Search Console → Enhancements → Breadcrumbs

Shows real crawl data across your entire site. If breadcrumb schema is deployed at scale (e-commerce, blog), this report surfaces structural errors and which URLs are affected.


Wrapping Up

Breadcrumb schema is probably the least exciting thing to write about in structured data, but it's also the one with the most predictable payoff. Small field set, works on almost any site, you can see the effect in search results within days of the page being re-crawled. The things that break it are mechanical — sequential positions, real URLs, minimum two items — and they're all easily caught with the Rich Results Test before you deploy.

Before shipping:

  • position values start at 1 and increment by 1, no exceptions
  • Every URL in item must return a real page — 404s silently invalidate the crumb
  • Last item's URL must match the page's canonical URL exactly
  • If you have visible breadcrumb navigation on the page, the JSON-LD must match it

Generate breadcrumbs from a reusable component that shares its data with both the HTML navigation and the JSON-LD block. That way they're always in sync and you only update one place.

If you'd rather generate the BreadcrumbList JSON through a form: Breadcrumb Schema Generator. Free, no backend, runs in the browser.


Working with a site that has a flat URL structure with no real category hierarchy? Drop the setup in the comments — the "one level deep" breadcrumb case is more nuanced than it looks.

Top comments (0)