Steve Taylor photo

Adding custom post type landing pages to Yoast breadcrumbs

Joost de Valk’s WordPress SEO plugin¬†has decent breadcrumbs functionality built into it. One thing it doesn’t handle is to insert a custom post type’s landing page into the breadcrumb trail. Luckily, the plugin is well written, and includes a hook to modify the breadcrumbs.

Now, CPTs aren’t really designed to having “landing pages”. They have “archives”. You set the has_archive flag with register_post_type() to true, and create a template in your theme called archive-{cpt-name}.php. Then, if you’ve set the rewrite slug for the post type to my-archive, when you go to http://example.com/my-archive/, that archive template will be used, and the loop will be ready-populated with your posts. And the WPSEO plugin’s breadcrumbs should kick in and work in this instance.

However, in some projects, I need the page where a custom post type lives to be an actual page. It might need certain custom fields available to configure its contents (in addition to the list of posts). And it might be somewhere further down the page hierarchy than the top level, e.g. top-level-page/landing-page/. So I set the rewrite slug for the post type to top-level-page/landing-page/. Instead of using the has_archive flag, I use Scott Cariss’s CPT Structure Helper plugin. (This plugin’s a bit rough, but works. You need to use sc_cpt_sh_register_post_type() instead of register_post_type() to register the post types you need landing pages for, then on the Settings > Reading screen you can assign your post types to their landing pages. The plugin will also add classes, e.g. current_page_ancestor, to nav menu items as appropriate.)

OK, with all that in place, how to easily make the WPSEO breadcrumbs play along? Here’s my code:

add_filter( 'wpseo_breadcrumb_links', 'my_wpseo_breadcrumb_links' );
function my_wpseo_breadcrumb_links( $links ) {

	if ( is_single() ) {
		$cpt_object = get_post_type_object( get_post_type() );
		if ( ! $cpt_object->_builtin ) {
			$landing_page = get_page_by_path( $cpt_object->rewrite['slug'] );
			array_splice( $links, -1, 0, array(
				array(
					'id'	=> $landing_page->ID
				)
			));
		}
	}

	return $links;
}

First we test if we’re on a single post page. Then we check if that post type is custom (i.e. _builtin isn’t true). You might want to adjust the conditions here, depending on how you’re using different custom post types.

Then we get the landing page details based on the slug of the post type, which should correspond to the landing page’s path, e.g. top-level-page/landing-page/. Then we use array_splice() to stick a breadcrumb in just before the last item (-1). We can just pass the page’s ID and the WPSEO plugin code will work out the text and URL for the breadcrumb by getting the page’s details.

10 comments

  1. Remue avatar Remue

    Hi Steve,

    thanks for the great tutorial, this was exactly what I was looking for!!! :)

    One thing, the break command in line 14 creates a blank page for me. It seems to stop rendering the content. I removed the break and it works fine now.

    Best,
    Remue

  2. Thanks, I’ve edited it out. Looking at it now, not sure why it’s there – surely redundant if there’s no loop or switch! Maybe I was using a loop at some point, and the break got left behind.

  3. Thanks so much for showing me this approach. I was looking at an evening of using some complex PHP and then some jQuery to insert the CPT parent links into the right place. Your solution gave me what I need.

    For other peoples ref:

    I used

    if (is_singular(array('international-course','short-course', etc..))) {

    instead of the simple ‘is_single’

    and I added a link to the parent’s parent page by adding

    $parent_page_ID = $landing_page->post_parent;

    then duplicated the array_splice section with this instead

    'id' => $parent_page_ID

    Works a treat. But would not taken this approach unless I had not seen your blog. Big thanks again!

  4. James avatar James

    This is awesome thanks!

    I ended up editing mine just a bit because I am also using register_post_type(‘rewrite’ => array( ‘slug’ => ‘resources/custom-post-type’) for some of my CPTs to change their url structure and I needed to make sure the Yoast breadcrumbs followed the correct parent path. Here was the code I used for that:


    $oldest_parent = $landing_page;
    $safty = 0;
    while($oldest_parent->post_parent != 0){

    $oldest_parent = get_post($oldest_parent->post_parent);
    array_splice( $links, -1, 0, array(
    array(
    'id' => $oldest_parent->ID
    )
    ));

    if($safty++ > 5) break;
    }

    I hope this helps someone :)

  5. James avatar James

    I forgot to include the rest of the code so you know where to put that:

    add_filter( ‘wpseo_breadcrumb_links’, ‘my_wpseo_breadcrumb_links’ );
    function my_wpseo_breadcrumb_links( $links ) {

    if ( is_single() ) {
    $cpt_object = get_post_type_object( get_post_type() );
    if ( ! $cpt_object->_builtin ) {
    $landing_page = get_page_by_path( $cpt_object->rewrite[‘slug’] );

    $oldest_parent = $landing_page;
    $safty = 0;
    while($oldest_parent->post_parent != 0){

    $oldest_parent = get_post($oldest_parent->post_parent);
    array_splice( $links, -1, 0, array(
    array(
    ‘id’ => $oldest_parent->ID
    )
    ));

    if($safty++ > 5) break;
    }

    array_splice( $links, -1, 0, array(
    array(
    ‘id’ => $landing_page->ID
    )
    ));
    }
    }

    return $links;
    }

  6. Thank you, exactly what I was looking for! Strange that Yoast haven’t added an option for this in their Advanced settings yet.

  7. Steve Taylor avatar Steve Taylor

    If you don’t understand how to use custom code in WordPress, please consult the official docs.

Comments are closed.