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.

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.

1 Answer 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.

  • Works great! I am on (and want to stay on) front-page.php for this task, and it looks as though the front page has zero query_vars. So I added empty($wp->query_vars) to the if statement. if ( $id || $name || empty($wp->query_vars) ) { ... And I did end up using template_include to force WordPress to stay on (or, rather, choose) front-page.php.
    – Don
    Apr 1, 2014 at 13:28
  • A word to the wise for anyone who finds this question/answer useful: the answer above uses php 5.3.0+ Anonymous Functions and as of this writing, WordPress only requires php 5.2.4 so alter the code as needed. Using the answer (anon function) and the question (pure callback) as examples, you should be fine.
    – Don
    Apr 1, 2014 at 13:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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