Title card: Customizing Yoast Breadcrumbs

Modify Yoast Breadcrumb Text for Parent and Child pages

I recently had a situation where I wanted to add some text to two items in the breadcrumb trail. It took me an absurdly long time to figure it out so I wanted to post my solution in case someone else runs into the same issue.

A brief intro to customizing Yoast Breadcrumbs

If you’re using the Yoast WordPress SEO plugin and want to programmatically modify breadcrumbs, there are two filters available specifically for this purpose:

1. wpseo_breadcrumb_links

This filter is for editing the full breadcrumbs pathway. What the heck does that mean? It’s easiest to show you an example.

Let’s say you want to insert a new item into the breadcrumbs.

The old breadcrumb might look like:

Home / Some Page

And you want to update it to:

Home / Another Page / Some Page

That’s where you’d use wpseo_breadcrumb_links. Here are loads of examples of wpseo_breadcrumb_links in action.

2. wpseo_breadcrumb_single_link

As the name implies, this filter is for changing a single item in the breadcrumb pathway.

So if you’re breadcrumb looked like:

Home / Parent Page / Child Page

You could use this filter to achieve:

Home / Parent Page / My Awesome Child Page

Here are some examples of using wpseo_breadcrumb_single_link. But alas, none of those examples met my needs.

What I was trying to accomplish

For my use case, I had a custom post type and wanted to append some text to both the parent page breadcrumb and the child page breadcrumb.

On a parent page I wanted to modify the breadcrumb from:

Home / Salary Data / [Job] to:

Home / Salary Data / [Job] Salary

On child pages, I wanted to modify the breadcrumb from:

Home / Salary Data / [Job] / [Job] in [Location] to:

Home / Salary Data / [Job] Salary / Salary data for a [Job] in [Location]

See the final solution in action

If you’re the least bit curious about the project I was working on for this breadcrumb customization, it’s a site called zengig.com, a site for job searchers. You can do cool things there, like check to see how much a WordPress developer makes in your location. 🙂

Let’s start by unpacking the filter wpseo_breadcrumb_single_link

If you want to go straight to the source code, this filter is defined in WordPress SEO here:

/**
* Filter: 'wpseo_breadcrumb_single_link' - Allow changing of each link being put out by the Yoast SEO breadcrumbs class.
*
* @param array $link The link array.
*
* @api string $link_output The output string.
*/
return \apply_filters( 'wpseo_breadcrumb_single_link', $link, $breadcrumb );

The filter has 2 parameters you can pass as arugments to your callback function. Both are arrays. The first parameter, $link, contains an array of strings that correspond to the pages in the breadcrumb.

So, in my example, dumping that $link variable on a parent page would output:

<span><a href="https://zengig.local/">Home</a><span><a href="https://zengig.local/salary-data">Salary Data</a><strong class="breadcrumb_last" aria-current="page">Job</strong></span></span>

And a var_dump($breadcrumb) would output:

array(3) {
  ["url"] => string(21)
  "https://zengig.local/" ["text"] => string(4)
  "Home" ["id"] => int(1567)
}
array(2) {
  ["url"] => string(32)
  "https://zengig.local/salary-data" ["text"] => string(11)
  "Salary Data"
}
array(3) {
  ["url"] => string(43)
  "https://zengig.local/salary-data/job/" ["text"] => string(9)
  "Job" ["id"] => int(9971)
}

So, when we get to the part about actually modifying the breadcrumb text, you can see that $link contains the HTML output, while $breadcrumb isolates the url, text, and post ID.

Just tuck that away and we’ll circle back around to it later.

The solution

Let’s walk through building the solution together.

Scaffold the callback function

First things first, we’ll add the filter and specify the name of the callback function. Then we create a “shell” for the callback function where we’ll put our logic.

If you’re new to using hooks in WordPress, I’ve got a course for you on LinkedIn Learning.

<?php
/**
* Filter breadcrumbs on Salary Data pages.
*
* @param array $link The link array.
* @param array $breadcrumb The breadcrumb item array.
*
* @return str $link The link output.
*/
function zg_add_text_to_breadcrumb( $link, $breadcrumb ) {
// Do stuff here.
return $link;
}
add_filter( 'wpseo_breadcrumb_single_link', 'zg_add_text_to_breadcrumb', 10, 2 );

Set the stage with WordPress conditionals

Next up, we need to put some conditionals in place so that this filter runs only when we want it to. (If you need a primer for WordPress conditionals, here’s a guide).

Here’s a list of conditions where we want to execute:

  1. Only run on the single posts for the custom post type salary_data
  2. For parent pages, check to see if the breadcrumb text matches the page title
  3. For child pages, identify the parent page title in the breadcrumb
  4. For child pages, identify the last breadcrumb (which is for the current page title)

If that sounds confusing, don’t worry — it’ll come together shortly.

The first conditional check is pretty straightforward. We use is_singular() to see if we’re on a single post for our CPT, salary_data.

// Only run this on salary pages.
if ( is_singular( 'salary_data' ) ) {

For the second check, we get a little fancy. We need TWO conditions to ring true. First, we’ll use has_post_parent() to see if we’re on a parent page (if the current page does NOT have a parent, then we know we’re on a parent page).

But just knowing we’re on a parent page isn’t enough. We also need to know that of allllll the breadcrumbs that might be on that page, we’re targeting the correct breadcrumb to change. To do that, we need the current page title (which you get with get_the_title()) to match the breadcrumb text (whose value we can pluck from the array with $breadcrumb['text']).

// If we are on the parent page AND targeting the correct breadcrumb in the array.
if ( ! has_post_parent() && ( get_the_title() === $breadcrumb['text'] ) ) {

If the above check doesn’t ring true, then we know we’re on a child page and that brings us to our third check, which is the trickiest of all!

Remember, we’ve got two breadcrumbs to modify on child pages — the parent page breadcrumb and the child page breadcrumb.

We’ll start by identifying which breadcrumb belongs to the parent. To do that, we must compare the parent page title to the breadcrumb.

First, we grab the title of the parent page and shove it into a variable creatively named $parent_title.

global $post;
$parent_title = get_the_title( $post->post_parent );

Then, as part of this check, we need to make sure that the $parent_title matches the breadcrumb text AND that it’s not the last breadcrumb. We can be sure it’s not the last breadcrumb because Yoast automatically adds a class of breadcrumb_last to the final link.

if ( ( $parent_title === $breadcrumb['text'] ) && ( strpos( $link, 'breadcrumb_last' ) === false ) ) {

I’m honestly not sure if that’s the most elegant way of doing things. It feels a little convoluted but it gets the job done. Feel free to leave a comment if you have another approach.

Anyhow, that check identifies the parent breadcrumb for us, which brings us to the final check. This one is straightforward. If breadcrumb_last is part of the link, we know we’re dealing with the final breadcrumb, which is for the current page.

Adding our conditionals in, this is now what our function looks like:

<?php
/**
* Filter breadcrumbs on Salary Data pages.
*
* @param array $link The link array.
* @param array $breadcrumb The breadcrumb item array.
*
* @return str $link The link output.
*/
function zg_add_text_to_breadcrumb( $link, $breadcrumb ) {
// Only run this on salary pages.
if ( is_singular( 'salary_data' ) ) {
// If we are on the parent page AND targeting the correct breadcrumb in the array.
if ( ! has_post_parent() && ( get_the_title() === $breadcrumb['text'] ) ) {
// We're on a child page.
} else {
// Modify parent breadcrumb.
global $post;
$parent_title = get_the_title( $post->post_parent );
if ( ( $parent_title === $breadcrumb['text'] ) && ( strpos( $link, 'breadcrumb_last' ) === false ) ) {
}
// Add text to final breadcrumb.
if ( strpos( $link, 'breadcrumb_last' ) !== false ) {
}
}
return $link;
}
}
add_filter( 'wpseo_breadcrumb_single_link', 'zg_add_text_to_breadcrumb', 10, 2 );

Actually edit the breadcrumbs

Ok! The stage is set, and we’re ready to customize our breadcrumb titles.

Honestly, the part that took me the longest was getting the conditionals right. With that in hand, actually modifying the breadcrumbs is relatively simple.

We’ll start with editing the breadcrumb on the parent page. I’m gonna create a new variable called $link_text and set it to the value of the page title plus the word ‘ Salary’. From there, we’ll build out the $link, which is the HTML output for that breadcrumb.

// If we are on the parent page AND targeting the correct breadcrumb in the array.
if ( ! has_post_parent() && ( get_the_title() === $breadcrumb['text'] ) ) {
$link_text = get_the_title() . ' Salary';
$link = sprintf( '<span><a href="%1$s">%2$s</a></span>', $breadcrumb['url'], $link_text );

Moving on from there, we’ll deal with breadcrumbs for the child page.

First, we’ll deal with the parent breadcrumb. Again, I’ll set $link_text equal to the name of the parent page plus the word ‘ Salary’ and build out the HTML markup and set it equal to $link, same as before.

// Modify parent breadcrumb.
global $post;
$parent_title = get_the_title( $post->post_parent );
if ( ( $parent_title === $breadcrumb['text'] ) && ( strpos( $link, 'breadcrumb_last' ) === false ) ) {
$link_text = $parent_title . ' Salary';
$link = sprintf( '<span><a href="%1$s">%2$s</a></span>', $breadcrumb['url'], $link_text );
}

Finally, we’re gonna reconstruct that final breadcrumb for the child page, which is the current page. This time I’m gonna throw in a bonus! Because job titles can start with any letter, I want to make sure I’m using the appropriate article (a/an) to go with the job title.

To do that, I use a couple of PHP functions to identify if the first character of $title is a vowel and set the $a_or_an variable to the appropriate article. From there, we reconstruct the HTML markup the same as we’ve already done.

// Add text to final breadcrumb.
if ( strpos( $link, 'breadcrumb_last' ) !== false ) {
$title = get_the_title();
// Decide which article to use.
$a_or_an = in_array( strtolower( $title[0] ), array( 'a', 'e', 'i', 'o', 'u' ) ) ? 'an ' : 'a ';
$link_text = 'Salary data for ' . $a_or_an . get_the_title();
$link = sprintf( '<span class="breadcrumb_last">%1$s</span>', $link_text );
}

And, finally, because we’re dealing with a filter, we gotta return some value.

return $link;

Voila!

Putting it all together

In case you’d like to see what the whole shebang* looks like, here ya go.

* Last week I learned that “shebang” is a programmatic term for the #! that goes at the beginning of a bash script. And now that I’ve said “shebang”, I invite you to enjoy this delightful earworm, courtesy of the American Idol archives.

<?php
/**
* Filter breadcrumbs on Salary Data pages.
*
* Format should be:
* Home / Salary Data / [Job] Salary / Salary data for a [Job] in [Location]
*
* @param array $link The link array.
* @param array $breadcrumb The breadcrumb item array.
*
* @return str $link The link output.
*/
function zg_add_text_to_breadcrumb( $link, $breadcrumb ) {
// Only run this on salary data pages.
if ( is_singular( 'salary_data' ) ) {
// If we are on the parent page AND targeting the correct breadcrumb in the array.
if ( ! has_post_parent() && ( get_the_title() === $breadcrumb['text'] ) ) {
$link_text = get_the_title() . ' Salary';
$link = sprintf( '<span><a href="%1$s">%2$s</a></span>', $breadcrumb['url'], $link_text );
// We're on a child page.
} else {
// Modify parent breadcrumb.
global $post;
$parent_title = get_the_title( $post->post_parent );
if ( ( $parent_title === $breadcrumb['text'] ) && ( strpos( $link, 'breadcrumb_last' ) === false ) ) {
$link_text = $parent_title . ' Salary';
$link = sprintf( '<span><a href="%1$s">%2$s</a></span>', $breadcrumb['url'], $link_text );
}
// Add text to final breadcrumb.
if ( strpos( $link, 'breadcrumb_last' ) !== false ) {
$title = get_the_title();
// Decide which article to use.
$a_or_an = in_array( strtolower( $title[0] ), array( 'a', 'e', 'i', 'o', 'u' ) ) ? 'an ' : 'a ';
$link_text = 'Salary data for ' . $a_or_an . get_the_title();
$link = sprintf( '<span class="breadcrumb_last">%1$s</span>', $link_text );
}
}
return $link;
}
}
add_filter( 'wpseo_breadcrumb_single_link', 'zg_add_text_to_breadcrumb', 10, 2 );

That’s it! I hope this helps somebody else out there. If you’ve found this helpful or have suggestions for how I could improve the logic, leave a comment!

Leave a Comment

Your email address will not be published. Required fields are marked *

Carrie Dils uses Accessibility Checker to monitor our website's accessibility.