hook to get (and change) the search string before processing

In order to achieve a deeper “injection” into the search and catch what the user has searched as soon as possible, we have to look at the WP_Query class and see how it treats a query for posts:

In its parse_query function wee that it uses a flag to signal to the whole class that, indeed, the query is a “search”, not surprisingly, it looks for s inside the query:

if ( isset( $this->query['s'] ) ) {
    $this->is_search = true;
}

Great, but there are no actions like do_action( 'super_important_hook' . $handle_that_we_could_use, $query) where WordPress lets us hook into this big, vital process on a “per name basis”, so what can we do?

We have parse_query which fires before pre_get_posts:

function replace_search( $query_object )
{
    if( $query_object->is_search() ) {
        $raw_search = $query_object->query['s'];

        $replacement = str_replace( 'red', 'rosso', $raw_search );

        if( $replacement ) {
            $query_object->set( 's', $replacement );
        }
    }
}

add_action( 'parse_query', 'replace_search' );

…and it works! We’re hooking as early as we possibly can and modify the query as soon as it happens. The set function does exactly what it says, it’s a setter for any of the internal variables used for the class.

By the way, the hook parse_tax_query fires before parse_query since $this->parse_tax_query() which contains parse_tax_query is called inside the parse_query() function of WP_Query which passes the same WP_Query Object to the action. But here comes my opinion which could be very wrong: I wouldn’t use this hook as it seems parse_query is way more popular to hook into for this type of behavior.

First, Remember that unless plugins also use this hook to look for the search string, they might look for $_REQUEST['s'] which comes before these hooks and it might be modified at that point. Regardless, the earliest point where the user’s search enters the system is $_REQUEST['s'] so, you’d need to hook into this value as early as possible, my answer is pretty good here, but I think there are way more things to consider when playing with search, and of course, if their functions have higher priority and they transform the string into something else, you’re doomed.

Obviously, you’ll have a more sophisticated “replacement” mechanism but that’s just to show what hooks you need.

Second, an interesting point about these actions is that they pass their objects by reference:

/**
 * Fires after the main query vars have been parsed.
 *
 * @since 1.5.0
 *
 * @param WP_Query $this The WP_Query instance (passed by reference).
 */
do_action_ref_array( 'parse_query', array( &$this ) );

What this means and it’s good to keep in mind – every single modification you do when your scripts hook into parse_query is reflected on the actual class itself that gets passed to the next script that it’s hooked to or, well, your changes will be reflected for the entire life-cycle of the said class. I can’t tell whether or not this was a good design choice, just letting you know in case you’ll have some “why does this variable not reset? It’s a new class instance.”, the class behaves like a singleton, but it’s not, so remember you’re playing with the same class up until WP is finished with it and all the changes you do to it will be used by whatever comes after you, in short: be very mindful with what you do, there are a lot of processes / plugins using this as WP_Query is the actual core of WP itself.


If you’re looking to ‘hook’ as late as possible, this will still modify the query itself but it’ll (I believe) hook late, giving the theme / plugins possibility to play with this value before you see it:

add_filter( 'get_search_query', function( $user_search_query ) {
    $replacement = str_replace( 'red', 'rosso', $user_search_query );

    if( $replacement ) {
        return $replacement;
    }

    return $user_search_query;
}, 999);

The hook you’re looking for is get_search_query. But please know that there’s a lot of plugins / things that play with this, so, be aware of the implications of changing this. The main function that uses it is:

/**
 * Displays the contents of the search query variable.
 *
 * The search query string is passed through esc_attr() to ensure that it is safe
 * for placing in an html attribute.
 *
 * @since 2.1.0
 */
function the_search_query() {
    /**
     * Filters the contents of the search query variable for display.
     *
     * @since 2.3.0
     *
     * @param mixed $search Contents of the search query variable.
     */
    echo esc_attr( apply_filters( 'the_search_query', get_search_query( false ) ) );
}

From general-template.php in WP Core. This parameter is passed to our filter from get_search_query which is just a simple wrapper over getting whatever is in $_REQUEST['s'].