I've been pulling my hair over this strange issue for several hours now and need some help figuring out what's going on. Allow me to explain my setup followed by the behavior and question.


  1. I've set my 'posts page' to domain.com/search/ where I intend to list all my posts.

  2. Since I need a custom layout for displaying my posts, I've the following redirect in 'template_include' hook:

    /*Use custom template to render posts on /search/ */
    if ( is_home() ) {
        return plugin_dir_path( __FILE__ ) . 'partials/display-speaker-list.php';
  3. My posts list also displays tags associated with each post. I want these tags to be clickable. When user clicks on any tag, I want to filter the posts using the clicked tag as keyword. The HTML for my displaying tags for each post looks like this:

     <a href="<?php echo esc_url( home_url('/') . 'search/?tag=' . str_replace(' ', '+', $topic ) ); ?>" class="btn btn-outline-primary" role="button"><?php echo esc_html( $topic ); ?></a>

In order to make the search work, I decided to make use of 'pre_get_posts' hook. This is where the strange things began to happen. Look at the code I execute with pre_get_posts :

/* Filter posts using the tag   
public function io_speakers_pre_get_posts( $query ) {

    if ( $query->is_main_query() && $query->is_home() ) {
        $query->set('tag', $_GET['tag']);
        return $query;
    return $query;

Here's the Odd behavior:

If I remove the $query->set('query_var', 'value'); statement from the condition, WordPress returns all posts, completely ignoring the search/?tag=xyz query. But it works even if I set it to something random or even empty. That is, WordPress correctly filters the posts if I change that line to $query->set('', '');

In short, WordPress expects me to have a $query->set(WHATEVER); in that condition for it to acknowledge existence of 'tag' parameter. Otherwise it just returns all the posts.

My questions are:

  1. What's really happening with $query->set()?
  2. Why does it require me to set any random stuff in $query->(blah, blah) in order to work properly?
  3. What if I want to search by /search/?topic=someTopic instead of /search/?tag=someTopic?

I hope I've explained my question properly. It's a pretty long explanation and I look forward to your help. Would really appreciate it. Thank you in advance for your time.



You don't need to add a pre_get_posts filter for ?tag=xyz to work, URLs in WordPress should already work that way. If you can write it as a string and pass it to WP_Query then you can append it to a URL with a ? to add additional query parameters. That is assuming that the main query hasn't been replaced with a new one, or that other plugins haven't interfered

What's really happening with $query->set()?

Before a query executes it's passed through the pre_get_posts filter, and set changes the query variables. That's it, there's no strange voodoo going on

Why does it require me to set any random stuff in $query->(blah, blah) in order to work properly?

It shouldn't, but when the front page settings are involved things can get a little strange, as it changes what is_homeand is_front_page mean. On the main query this means that is_front_page can't be used because the logic that figures it out hasn't run yet

What if I want to search by /search/?topic=someTopic instead of /search/?tag=someTopic?

Do what you've been doing but instead of $_GET['tag'] do $_GET['topic']

A Final Note on Your Template

I see this in your question:

return plugin_dir_path( __FILE__ ) . 'partials/display-speaker-list.php';

That template will need a standard post loop for any of this to work. If any of the following are true, then it will not:

  • A query_posts call
  • A WP_Query loop
  • Using get_posts

None of those are the main query, so the pre_get_posts filter will fail the check and move on without making the modification

As an aside, have you considered changing the tag permalinks instead and using real archive URLs instead of hacking the main blog page with URL parameters?

| improve this answer | |
  • I changed the permalink structure to plain and then http://io.dev/speakers/?page_id=72?tag=xyz works good. If I change back to using post name, /speakers/search/?tag=xyz doesn't work. If I comment out the if condition inside my io_speakers_pre_get_posts, then WP returns all posts by ignoring the ?tag=xyz parameter; which is where I'm facing issue. That said, I've tried replacing $_GET['tag'] with $_GET['topic'], but as I said, $query->set('tag', $_GET['topic']); completely ignores it. – TheBigK Jul 14 '17 at 14:01
  • So I could replicate your problem by setting permalinks to pretty/post names, the homepage to a page named search and adding ?tag=xyz on the end? And this reproduces the problem in a default install with a default theme and no plugins? – Tom J Nowell Jul 15 '17 at 0:01
  • I'm getting the problem on a fresh sub-site of a multi-site install; which is how I'm structuring my app. I just want to be sure that it's not a problem in my code; and something that needs to be reported to the core team. – TheBigK Jul 16 '17 at 5:18

So I have a bit of a curveball setup on my own site, but I realise it could be used to completely sidestep your issue.

For example, on my own site tomjn.com, my homepage is a normal homepage, set to latest posts, which I then use home.php as the template for.

However, my articles page isn't a page at all, it's an archive powered by a rewrite rule. And since it's a rewrite rule I have total control over the query variables:

// add my articles/ archive, and pagination rules!
// I also added a query variable so that I know
// if I'm on this page or not
function tomjn_posts_rewrite_rule() {
add_action('init', 'tomjn_posts_rewrite_rule', 10, 0);

// does the same thing as the next function but
// specifically to the main query. Seems redundant
// but it was necessary for some reason :/
function tomjn_template_redirect_intercept () {
        global $wp_query;
        if ( $wp_query->get( 'toms_custom_posts' ) ) {
                $wp_query->is_home = false;
                $wp_query->is_archive = true;
                $wp_query->show_posts = true;
add_action( 'template_redirect', 'tomjn_template_redirect_intercept' );

// WP has some ideas about what query vars are set,
// I have my own, lets make sure this is an archive,
// not the homepage, and that posts are fetched
// but only if my custom query var is set
function _tomjn_setup_posts_page( $query, \WP_Query $q ) {
        if ( $q->is_main_query() && $q->get( 'toms_custom_posts' ) ) {
                $q->is_home = false;
                $q->is_archive = true;
                $q->show_posts = true;
        return $query;
add_filter( 'posts_request', '_tomjn_setup_posts_page', 1, 2 );

// whitelist my custom query var
function tomjn_rewrite_query_vars( $vars ) {
        $vars[] = 'toms_custom_posts';
        return $vars;
add_filter( 'query_vars', 'tomjn_rewrite_query_vars' );

Now I trust you know how to change every mention of tom to thebigk etc, and swap out articles and so on.

I also trust that you know how to use template_include to load the template file of your own choice, else it will load archive.php.

Note that you will need to flush permalinks after installing the code.

The general idea is that you add a rewrite rule to create a new archive. In the query params, you add a marker so that you know when it's active. WP doesn't know it's an archive though, so the code intercepts the main query and make some adjustments so that it knows it's an archive

As an aside, testing with /articles/?tag=php on my site works with this code. It should work with what you have set up too but that's clearly not the case. So I provide this as an alternative, and slightly crazy solution

hint: bonus points if you copy the rewrite rule a third time and with some regular expression magic add in the topic so that your /search/?topic=xyz becomes /search/topic/xyz

| improve this answer | |
  • Tom, I think what I'm trying to do should be very straightforward. Yes, I'm loading the correct template through my plugin using template_include hook and then using is_home() check to redirect WP to the appropriate file as I mentioned in my question. It's all good till that point. You're right that /?tag=xyz doesn't require pre_get_posts and should work out of the box. But here's the deal: In my default setup (no special plugins), it doesn't work unless I write $query->set('','') /* empty strings or whatever, but required! */ in my pre_get_posts. That's driving me nuts! – TheBigK Jul 15 '17 at 6:04
  • Does that mean the above answer doesn't give you what you want? I think the magic fix you're looking for either doesn't exist or it's the weird kludge you found. Not everything is straight forward however trivial it might seem. I would open a Core Trac ticket – Tom J Nowell Jul 15 '17 at 10:43
  • Well, I'm really not looking for a magic fix. I'm confused about what's really happening and why something that's so trivial isn't behaving the way it should. I've already done more complex programming on WordPress and it feels stupid to be stuck with this, seemingly simple, problem. – TheBigK Jul 16 '17 at 5:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.