Why does the_title() filter is also applied in menu title?

Posting this answer because it was the search result I ended up clicking on while searching about targeting the filter hook the_title while ignoring the filter effect for navigation items.

I was working on a section in a theme which I wanted to add buttons to the page title within the heading one tag.

It looked similar to this:

<?php echo '<h1>' . apply_filters( 'the_title', $post->post_title ) . '</h1>'.PHP_EOL; ?>

I was then "hooking in" like this:

add_filter( 'the_title', 'my_callback_function' );

However, the above targets literally everything which calls the_title filter hook, and this includes navigation items.

I changed the filter hook definition like this:

<?php echo '<h1>' . apply_filters( 'the_title', $post->post_title, $post->ID, true ) . '</h1>'.PHP_EOL; ?>

Pretty much every call to the_title filter passes parameter 1 as the $post->post_title and parameter 2 as the $post->ID. Search the WordPress core code for apply_filters( 'the_title'* and you'll see for yourself.

So I decided to add a third parameter for situations where I want to target specific items which call the_title filter. This way, I can still receive the benefit of all callbacks which apply to the_title filter hook by default, while also having the ability to semi-uniquely target items that use the_title filter hook with the third parameter.

It's a simple boolean parameter:

/**
 * @param String $title
 * @param Int $object_id
 * @param bool $theme
 *
 * @return mixed
 */
function filter_the_title( String $title = null, Int $object_id = null, Bool $theme = false ) {

    if( ! $object_id ){
        return $title;
    }

    if( ! $theme ){
        return $title;
    }

    // your code here...

    return $title;

}

add_filter( 'the_title', 'filter_the_title', 10, 3 );

Label the variables however you want. This is what worked for me, and it does exactly what I need it to do. This answer may not be 100% relevant to the question asked, but this is where I arrived while searching to solve this problem. Hope this helps someone in a similar situation.


You can do something like that :

In your function.php :

add_filter( 'the_title', 'ze_title');
function ze_title($a) {
    global $dontTouch;
    if(!$dontTouch && !is_admin())
        $a = someChange($a);
    return $a;
}

In your template :

$dontTouch = 1;
wp_nav_menu( array('menu' => 'MyMenu') );
$dontTouch = 0;

Nikola is correct:

Because menu items also have titles and they need to be filtered :).

To make this only call in the posts, and not in menus, you can add a check for in_the_loop() - if it is true, you're in a post.

So change the first line in the function to:

if( is_admin() || !in_the_loop() )

and all should be well.


It's a bit of a hack but you can solve this by adding your action to loop_start.

function make_custom_title( $title, $id ) {
    // Your Code Here
}

function set_custom_title() {
   add_filter( 'the_title', 'make_custom_title', 10, 2 );
}

add_action( 'loop_start', 'set_custom_title' );

By embedding the_title filter inside of a loop_start action, we avoid overwriting the menu title attributes.