How to Add Breadcrumbs to WordPress Without a Plugin (PHP and Schema)
If you are tired of installing bulky SEO plugins just to display a simple navigation trail, you are in the right place. In this tutorial, we will show you exactly how to add WordPress breadcrumbs without a plugin, using only PHP inside your theme’s functions.php file, plus proper JSON-LD schema markup so Google can display them in search results. This method is lightweight, fast, and gives you full control over the HTML output. No plugin bloat, no unnecessary database queries, just clean code that works. Why Code Breadcrumbs Instead of Using a Plugin? Plugins like Yoast or RankMath do offer breadcrumb features, but they come with a full ecosystem you might not need. Here is a quick comparison: Aspect Plugin Method Custom PHP Method Performance Extra queries and assets loaded Minimal footprint Customization Limited to plugin options Full control over HTML and CSS Schema Markup Often included but generic Tailored JSON-LD output Dependencies Tied to plugin updates Independent, portable code Step 1: Add the Breadcrumb Function to functions.php Open your child theme’s functions.php file (never edit the parent theme directly) and paste the following function. It handles posts, pages, categories, tags, custom post types, author archives, search results, and 404 pages. function express_custom_breadcrumbs() { $separator = ‘»’; $home_title = ‘Home’; $home_url = home_url(); global $post; $output = ‘<nav class=”express-breadcrumbs” aria-label=”Breadcrumb”>’; $output .= ‘<a href=”‘ . $home_url . ‘”>’ . $home_title . ‘</a> ‘ . $separator . ‘ ‘; if ( is_category() ) { $output .= single_cat_title(”, false); } elseif ( is_tag() ) { $output .= single_tag_title(”, false); } elseif ( is_author() ) { $output .= ‘Author: ‘ . get_the_author(); } elseif ( is_search() ) { $output .= ‘Search results for: ‘ . get_search_query(); } elseif ( is_404() ) { $output .= ‘Page not found’; } elseif ( is_single() ) { $post_type = get_post_type(); if ( $post_type != ‘post’ ) { $pt_obj = get_post_type_object($post_type); $output .= ‘<a href=”‘ . get_post_type_archive_link($post_type) . ‘”>’ . $pt_obj->labels->singular_name . ‘</a> ‘ . $separator . ‘ ‘; } else { $categories = get_the_category(); if ( $categories ) { $cat = $categories[0]; $output .= ‘<a href=”‘ . get_category_link($cat->term_id) . ‘”>’ . $cat->name . ‘</a> ‘ . $separator . ‘ ‘; } } $output .= get_the_title(); } elseif ( is_page() ) { if ( $post->post_parent ) { $ancestors = array_reverse(get_post_ancestors($post->ID)); foreach ( $ancestors as $ancestor ) { $output .= ‘<a href=”‘ . get_permalink($ancestor) . ‘”>’ . get_the_title($ancestor) . ‘</a> ‘ . $separator . ‘ ‘; } } $output .= get_the_title(); } $output .= ‘</nav>’; return $output; } Step 2: Display the Breadcrumbs in Your Theme Now you need to call this function where you want the breadcrumbs to appear. Typical locations include: header.php – right below the main navigation single.php – above the post title page.php – above the page content archive.php – above the archive listing Add this line where you want the trail to appear: <?php echo express_custom_breadcrumbs(); ?> Using a Shortcode Instead If you prefer inserting breadcrumbs via the block editor or a widget, register a shortcode: function express_breadcrumbs_shortcode() { return express_custom_breadcrumbs(); } add_shortcode(‘breadcrumbs’, ‘express_breadcrumbs_shortcode’); Then simply use [breadcrumbs] anywhere in your content. Step 3: Add Schema Markup for SEO This is where most tutorials stop, but Google needs structured data to actually display breadcrumbs in the search snippet. We will use JSON-LD BreadcrumbList schema, the format Google officially recommends. Add this function to your functions.php: function express_breadcrumbs_schema() { if ( is_front_page() ) return; $items = array(); $position = 1; $items[] = array( ‘@type’ => ‘ListItem’, ‘position’ => $position++, ‘name’ => ‘Home’, ‘item’ => home_url() ); if ( is_single() ) { $categories = get_the_category(); if ( $categories ) { $cat = $categories[0]; $items[] = array( ‘@type’ => ‘ListItem’, ‘position’ => $position++, ‘name’ => $cat->name, ‘item’ => get_category_link($cat->term_id) ); } $items[] = array( ‘@type’ => ‘ListItem’, ‘position’ => $position++, ‘name’ => get_the_title(), ‘item’ => get_permalink() ); } elseif ( is_page() ) { global $post; if ( $post->post_parent ) { $ancestors = array_reverse(get_post_ancestors($post->ID)); foreach ( $ancestors as $ancestor ) { $items[] = array( ‘@type’ => ‘ListItem’, ‘position’ => $position++, ‘name’ => get_the_title($ancestor), ‘item’ => get_permalink($ancestor) ); } } $items[] = array( ‘@type’ => ‘ListItem’, ‘position’ => $position++, ‘name’ => get_the_title(), ‘item’ => get_permalink() ); } elseif ( is_category() ) { $items[] = array( ‘@type’ => ‘ListItem’, ‘position’ => $position++, ‘name’ => single_cat_title(”, false), ‘item’ => get_category_link(get_queried_object_id()) ); } $schema = array( ‘@context’ => ‘https://schema.org’, ‘@type’ => ‘BreadcrumbList’, ‘itemListElement’ => $items ); echo ‘<script type=”application/ld+json”>’ . wp_json_encode($schema) . ‘</script>’; } add_action(‘wp_head’, ‘express_breadcrumbs_schema’); Step 4: Style Your Breadcrumbs Add this to your theme’s style.css file to make the breadcrumbs look clean and readable: .express-breadcrumbs { font-size: 14px; color: #666; padding: 12px 0; margin-bottom: 20px; } .express-breadcrumbs a { color: #0073aa; text-decoration: none; } .express-breadcrumbs a:hover { text-decoration: underline; } Step 5: Test Your Breadcrumbs Before considering the job done, run through these checks: Visit a post, a page, a category archive, and a 404 page. Confirm the trail matches the actual hierarchy. Open the page source and verify the JSON-LD script is present in the <head>. Use the Google Rich Results Test at search.google.com/test/rich-results to validate the BreadcrumbList schema. Check the Schema Markup Validator at validator.schema.org for any warnings. Submit the updated pages via Google Search Console to speed up re-indexing. Handling Custom Post Types and Taxonomies The function above already handles custom post types by pulling the archive link. If your CPT uses a custom taxonomy (like a WooCommerce product with product categories), extend the is_single() block like this: if ( is_singular(‘product’) ) { $terms = get_the_terms(get_the_ID(), ‘product_cat’); if ( $terms && !is_wp_error($terms) ) { $term = array_shift($terms); $output .= ‘<a href=”‘ . get_term_link($term) . ‘”>’ . $term->name . ‘</a> ‘ . $separator . ‘ ‘; } } Common Mistakes to Avoid Editing the parent theme directly. Your changes will be lost on the next update. Always use a child theme. Duplicating schema. If another plugin already outputs BreadcrumbList schema, remove one of them to avoid conflicts. Forgetting the aria-label. Accessibility matters, and screen readers rely on
How to Add Breadcrumbs to WordPress Without a Plugin (PHP and Schema) Read More »









