Obliterate the main query and replace it

There are lots of good resources on how to alter the main query, or add secondary loops…

But I actually want to completely replace the main query with a different query altogether.

Read More

I am hijacking one of my pages to serve, essentially, as a category page. (Note I am changing from “page” to “post” in my effort to do this.) But when I alter the main query by doing this:

function front_page_announcements( $query ) {
    if ( $query->query_vars['page_id'] == 84 && $query->is_main_query() && !$query->is_admin ) {
        $query->set("category_name", "special-announcement");
        $query->set("page_id", NULL);
    }
}
add_action( 'pre_get_posts', 'front_page_announcements' );

I end up getting every page (not post) in the database. What I actually want is every post (not page) with a category id of 3, or special-announcement in my case.

I don’t want to make unnecessary calls to the database, so it seems like the pre_get_posts action hook is what I’ll want, but I can’t figure it out. Even just setting $query = new WP_Query('cat=3') doesn’t seem to work.

Related posts

Leave a Reply

1 comment

  1. When 'pre_get_posts' is fired, there are a lot of things that WordPress has already done like setup all the query variables (also the query you don’t send) and setup all the conditional properties (all the is_* properties). So, if you want completely replace the query you should reset all that things and set your own.

    In fact, even if your code works, WordPress will use page.php as template.

    However there’s a simpler approach: act before WordPress do all the things.

    A good place is the 'request' filter hook, at that time WordPress has created the query vars from url and is going to pass that vars to WP_Query, and you can intercept and change query arguments before are sent.

    That action is fired by WP class, and pass the query vars to be set (see code).

    add_filter( 'request', function( array $query_vars ) {
    
      // triggered also in admin pages
      if ( is_admin() )
        return $query_vars;
    
      // you should check also for page slug, because when pretty permalink are active
      // WordPress use 'pagename' query vars, not 'page_id'
      $id = isset($query_vars['page_id']) && (int) $query_vars['page_id'] === 84;
      // remember to replace "slug" with real page slug
      $name = isset($query_vars['pagename']) && $query_vars['pagename'] === 'slug';
    
      if ( ( $id || $name)  && ! isset($query_vars['error']) ) {
        $query_vars = array('category_name' => 'special-announcement');
      }
    
      return $query_vars;
    
    });
    

    Replace 'slug' with the real slug of your page, then let WordPress do its work.

    Note that I’ve not check for main query, because 'request' is triggered only for main query.

    If you want to use a specific template, you should use a filter on template_include otherwise 'category.php' (or the right template according to template hierarchy) will be used.