Customizing the WordPress Query

One of the most powerful features of WordPress is the WP Query. It is what determines what content is displayed on what page. And often you’ll want to modify this query to your specific needs. Some examples:

  • Don’t display posts from Category X on the homepage
  • Increase or decrease the number of posts displayed per page for a specific post type
  • Determine which posts are shown and their order based on postmeta
  • Inside your page, do a separate query for different content

Andrew Nacin recently gave a great talk on using the query, which you can look at for some more technical information on what’s happening behind the scenes. I’m going to focus this tutorial on common uses.

First, what not to do. Don’t use query_posts(). As you can see from the Codex page, there’s a lot of caveats to it. I really can’t think of an instance where this function is advisable.

There’s two approaches you should take depending on your needs.

Create a new query to run inside your page or template.

This is best when the content you’re displaying is being loaded in addition to your current page’s content. For instance, if you had a page about Spain and wanted to show your 5 most recent blog posts about Spain at the bottom.

For this you’ll use the WP_Query class. For more information, see my post on Custom WordPress Queries. Also take a look at my Display Posts Shortcode plugin, which might save you from having to write any code.

Customize the Main Query

If you want to alter which content is returned on a page, you most likely will be modifying the main query. The first three examples above all require altering the main query.

WordPress has a very handy hook called pre_get_posts. It fires once all the query settings are ready but right before the actual query takes place. This is where we’ll jump in and modify the query settings if needed.

You must place your function in functions.php (or a plugin). WordPress needs to build the query to figure out what template to load, so if you put this in a template file like archive.php it will be too late.

All our functions are going to have a similar structure. First we’re going to make sure we’re accessing the main query. If we don’t check this first our code will affect everything from nav menus to recent comments widgets. We’ll do this by checking $query->is_main_query(). This was added in WordPress 3.3. For earlier versions, compare $query to the global $wp_the_query variable, as shown below in the 3.2 code below.

Then we’ll check to make sure the conditions are right for our modification. If you only want it on the homepage, we’ll make sure the query is for home ( $query->is_home() ).

Finally, we’ll make our modification by using the $query->set( 'key', 'value' ) method. To see all possible modifications you can make to the query, review the WP_Query Codex page.

Exclude Category from Blog

We’re checking to make sure the $query is the main query, and we’re making sure we’re on the blog homepage using is_home(). When those are true, we set ‘cat’ equal to ‘-4′, which tells WordPress to exclude this category.

If we were using WordPress 3.2 or earlier, the code could look like this:

Change Posts Per Page

Let’s say you have a custom post type called Event. You’re displaying events in three columns, so instead of the default 10 posts per page you want 18. If you go to Settings > Reading and change the posts per page, it will affect your blog posts as well as your events.

We’ll use pre_get_posts to modify the posts_per_page only when the following conditions are met:

  • On the main query
  • Not in the admin area (we only want this affecting the frontend display)
  • On the events post type archive page

Modify Query based on Post Meta

This example is a little more complex. We want to make some more changes to our Event post type. In addition to changing the posts_per_page, we want to only show upcoming or active events (so not ones that have ended), and sort them by start date with the soonest first.

I’m storing Start Date and End Date in postmeta as UNIX timestamps. With UNIX timestamps, tomorrow will always be a larger number than today, so in our query we can simply make sure the end date is greater than right now.

Creating the metabox and the fields within it are beyond the scope of this tutorial, but take a look at my post on Custom Metaboxes and this Events Plugin if you you want more informaton.

If all the conditions are met, here’s the modifications we’ll do to the query:

  • Do a meta query to ensure the end date is greater than today
  • Order by meta_value_num (the value of a meta field)
  • Set the ‘meta_key’ to the start date, so that’s the meta field that posts are sorted by
  • Put it in ascending order, so events starting sooner are before the later ones

chat142 Comments

  1. says

    Awesome post, Bill, and most serendipitous in the timing. I’d just started going through the Codex on the WordPress query and this gave me a great leg up. Thanks very much.

  2. Max says

    Useful post, Bill – thanks! I really like your Gist implementation within this blog. I just started using GitHub myself recently and like it more very single day. It’s irrelevant question, but if you don’t mind, what are the benefits of using Gist for code snippet examples that you’re using within the blog? Are you re-using snippets later or collaborating via Gist on them? I’m just curious about usage cases. Thanks!

    • Bill Erickson says

      I do plan to reuse them. I’m on the StudioPress support forums a lot and when someone is asking how to do a specific change I’ll post a link directly to the code rather than the whole post.

      But mainly I’ve found it’s easier to maintain and control the code if it’s embedded using Gist than directly added to the post. When I change up my theme I have to go through and make sure all my past code snippets didn’t break (and they have quite a few times).

    • Bill Erickson says

      Actually, the ‘query_args’ custom field that StudioPress recommends you use also falls under my “Don’t do this” recommendation. When you use it, Genesis is adding query_posts() to it. So if you use the blog page template and put ‘posts_per_page=5′ in the ‘query_args’ custom field, your pagination will be messed up for the reason I describe above.

      Even on Genesis I recommend you use the approach detailed above. If you’re modifying the main query (which is what you’re doing if you’re using the ‘query_args’ custom field), you should do it using the pre_get_posts hook.

  3. says

    Thanks Bill! I’ve recently written a post on my blog (harriswebsolutions.co.uk/blog/2011/create-your-own-archive-page-with-wordpress/) on a very similar theme. But I’ve altered the query directly with SQL statements rather than using the $wp_the_query; object. Your method is much cleaner I feel. How would (is it even possible?) to use it to access other tables though (say, from a plug-in)?

  4. says

    Bill – just reaching out here in case you have time to share some advice. I’m not that familiar with the WP Query big picture.

    I have a need to have sticky posts for categories and to _not_ have sticky posts for the home page. Basically opposite of what the query.php does.

    In WP 3.2.1, I was just able to change it from “if( $this->is_home && …” to “if( !(this->is_home) && ….” and it worked like I expected.
    When I upgraded to WP 3.3, that simple change no longer works. It causes single post pages to fail (actually just shows the same post no matter what pageid i am trying).
    I’m guessing the three other conditions on this line (line 2692 in query.php) don’t help indicate if it’s a category list vs a single post, and it was dumb luck to work in WP 3.2.1.

    I’d love to use a cleaner method to do this with add_action or add_filter, or whatever else, but I don’t understand the constructs very well. I also wonder if they will even work, because the query is coded to do the is_home check, so i’m not sure how a filter/action would change that.
    I’m hoping to not have to rewrite the entire query.php in my template, but that seems to be what I’ll need to do.

    Any advice is greatly appreciated.

    • Bill Erickson says

      To be honest, I don’t have much experience with sticky posts and wouldn’t use them. They’re a relic from an old version of WordPress, and the functionality you’re looking for can be better implemented with post meta.

      1. Add a metabox to the edit post screen that has a “Category Featured” checkbox.
      2. On category.php, add a custom loop at the top that lists the category featured posts. Your query might be something like this: https://gist.github.com/1519651
      3. In functions.php, exclude featured posts from the main query on category pages. Something like this: https://gist.github.com/1519659

      Note that none of the code above is tested, I just typed it real quick in GitHub. It might not work, but should point you in the right direction.

      • says

        Thanks for the pointers!! I’m working on implementing this now. This is something I want to be theme independent, so I’m trying to combine your custom metabox idea (which is made to add to a theme) with your custom function (core functionality) plug-in idea.
        Being new to this, I’m not 100% sure how to combine them. Any tips there?

        • Bill Erickson says

          If you take a look at my core functionality plugin, you’ll see that it already has metaboxes built-in. You just have to uncomment one line in plugin.php, then create your metaboxes in /lib/functions/metaboxes.php.

          • says

            I noticed that as I started digging in – good stuff, thank you! I spent 2 hours trying to debug why it wouldn’t work only to discover I had to change ‘pages’ => array(‘pages’) to ‘pages’ => array(‘post’).

            I haven’t got the page/category loop implemented yet, but thanks to your code mock up, I’m on my way. Thanks for your help!

  5. says

    Just wanted to follow up and say thanks again. I was able to get the double loop working in my theme’s archive.php.

    For the first loop, I used array_merge to keep the current wp_query args, and then added the argument to check for the category featured metabox setting i added using your custom function code.
    For the second loop, i actually had to loop through the results of the first query and create an array of post id’s and then used that to pass as a post__not_in argument to a new wp_query.

    Works great- thanks for the help!

    • Bill Erickson says

      I don’t believe so. If you look at the WP_Query page in the codex, you’ll see all the ways you can order the results.

      I think you’ll have to do a custom loop for that. In your taxonomy template, check to see if the current tax term has any children. If it does, loop through each child and pull those posts: https://gist.github.com/1597218

      Note that this is very inefficient (there’s a lot of extra queries going on in there). Another approach could be to do a preliminary loop through the posts, storing each post object in an array and organized by tax term. Then sort that array by the tax term, and use the results to actually display the posts.

      • Paul says

        thanks Bill, it seems your solution is the same as the one I found here:
        http://wpquestions.com/question/show/id/926

        with the advantage that you’re using WP_Query instead of query_posts, so I will modify it to use WP_Query.

        And I agree that getting the dataset first would be better to avoid multiple queries. I did something similar recently for an archives widget that grouped by year then month. Maybe I can merge all these ideas together :)

  6. says

    Thanks – helped me out with the pagination problem where pg 2 on custom taxonomies shows a 404 unless you set the backend # of posts to less than the # you’re showing using the custom query on the page.

  7. fallenboy says

    Thanks you for the tips!

    I have a question tho — is it possible to make “Customize Event Query using Post Meta”-snippet to play well with calendar? I have tried to change the code but it doesnt seem to work :(

    The calendar makes URL for future ie. something.com/archive/2012/05. All i get is 404, even tho there are events in april :(

    Is it possible?

    Thank you!

    • Bill Erickson says

      Based on your URL it looks like your calendar is using the post date, not a separate field in post meta. WordPress will only show posts that have been published already (so have a post date in the past).

      Create a custom metabox and create a field for event date (use the text_date_timestamp field type so it is a UNIX timestamp). Then you can use the event query code.

      • fallenboy says

        Thanks for reply.

        The calendar generates link based on custom date — if in april there are events, it gives april’s link. Now when you go to that aadress, it checks posts — there are none, and it gives 404.

        if ( $query->is_main_query() && !is_admin() && is_post_type_archive( ‘event’ ) ) { <– this is never true because is_post_type_archive( 'event' ) is never true. :S

  8. chrismccoy says

    how would you set the posts per page for an else?

    i set posts per page for is_search, on the search.php template the else shows 10 posts with random, was hoping to avoid doing query_posts on the search.php if it can be done via pre_get_posts

  9. says

    I am build­ing a direc­tory via Word­Press and Stu­dio­Press and would like to know if I can use the query_arg method via cus­tom fields to pull posts/pages that are category1 and category2? After reading your post I’m starting to think this might not be the best method for what I need to do.

    I have cre­ated a cus­tom post type of Direc­tory and then have set-up mul­ti­ple tax­onomies with par­ent and child such as:

    Loca­tions — Par­ent
    City1 — Child
    City2 — Child
    City3 — Child

    Restau­rants — Par­ent
    Amer­i­can — Child
    Mex­i­can — Child
    Chi­nese — Child
    etc, etc, etc…

    I need to be able to have a page that will pull in all restau­rants that are in City1. Can I do it using the query_arg method and if not or not recommended, what is the best method to do this? I have to do this for a lot of pages and I’m try­ing to find a sim­ple solu­tion with­out hav­ing to build a page tem­plate for every sin­gle category. Can you help set me on the right path here so I do it right the first time?

    Any help you could pro­vide would be awe­some! Thanks!

    • Bill Erickson says

      When you say “multiple taxonomies”, do you really mean you’ve created multiple taxonomies, or that you have a single taxonomy with multiple terms?

      If you create a taxonomy for Location and a taxonomy for Restaurants, then you can automatically view the posts matching a specific term using the taxonomy archive.

      If taxonomy = ‘location’ and term = ‘city-1′, the URL for the archive is yoursite.com/location/city-1. There’s no need to create a page – WordPress does this for you.

      As an example, see the code snippets section of my site. The post type archive is here ( http://www.billerickson.net/code/ ). I have a taxonomy called ‘code tags’, and here’s a term inside that taxonomy: http://www.billerickson.net/code-tag/metabox/

      • says

        Hey Bill,

        Thanks for the reply. I think we may be onto something here, but I need to figure out how to pull a page that has all the restaurants in City1 as an example. If I were to give you the login to my site, would you be willing to look at it real quick to see my set-up and let me know if I am heading in the right direction with Custom Post Types and Taxonomies? If so, let me know how to get it to you and I will send it asap. Thanks for your help with this.

        • Bill Erickson says

          I wish I could help but I don’t have any time to do free consultations (even though it should only take a few minutes). I have about 15 active projects right now and am hopping on a 5am flight to a WordCamp tomorrow morning.

          If you’d like, you can schedule to work with me in a few weeks. As of right now, I’m scheduling projects to start Monday, July 2nd.

          • says

            Hey Bill,

            I respect your time and thank you for the help you have given thus far. If I can’t get this figured out here soon I might just take you up on your offer.

            Have a great time at WordCamp!

  10. says

    Sorry, Bill. I’m new to commenting and did not realize I could not paste a php function in the input field here. I may be out of luck with this. :(

  11. says

    Hi Bill!

    Thank you so much for getting back to me. I really appreciate it. I pasted the code like you said at the following URL:

    git clone git://gist.github.com/2883980.git gist-2883980

    The address of my site is: http://www.serv-hot.com but the site is in maintenance mode so you will need to login with the following credentials to take a look:

    USERNAME: admin
    PASSWORD: 3000hot

    (you will see the tiny ‘login’ button on the bottom right-hand side of the screen of the maintenance mode page.)

    Once inside you can navigate to the Appearance editor where you must choose the SIGHT theme from the pulldown menu above the list of template files. This is the theme that I’m using; not the default theme TWENTYELEVEN.

    Any insight you can provide on this issue will be a godsend. Thank you. Hope to hear from you soon. — monti

    • Bill Erickson says

      Can you post just the specific code you have a question about, and the question itself?

      Logging into your site and troubleshooting is beyond the free help I provide on my site, but if you’d like to hire me feel free to use my contact form: http://www.billerickson.net/contact

  12. says

    Bill, I used your first code under “Exclude Category from Blog” including it inside of home.php. It didn’t work. I tried several positions, in one of them it turns me the site completely blank.
    Can you give me your opinion to get me in the right direction. ?
    My site uses the Genesis Design Framework from StudioPress Version: 1.8.2 in a WordPress 3.4.1 platform.
    Thank you Bill & congratulations for you good job.
    Julio

    • Bill Erickson says

      That’s actually a very common problem, which I addressed about halfway down the page in bold.

      You must place your function in functions.php (or a plugin). WordPress needs to build the query to figure out what template to load, so if you put this in a template file like home.php it will be too late.

      • says

        Bill, I put the code in functions.php but it didn’t work .

        I think I have a BIG confusion. When this article starts you say “Some examples: Don’t display posts from Category X on the homepage.” I thought, that is what I want to do. On my WordPress Reading Settings I have My Front page displays>> Your latest post set. So my Homepage shows all my posts from the more recent in the top of the page. I’m not using the Blog Template in other words.

        MY CONFUSION is when you write about Exclude Category from Blog and you give the first code for copying and pasting it. Is that code exclusive only when you are using the Blog template ? Let’s say Settings>>Reading>>Front page displays>>A statis page>>Front page: Blog

        Is it a different code when y have my homepage not set as a Blog, only the default homepage?

        Sorry the mess, I can not explain it in other words.

        Thank you

        • Bill Erickson says

          The is_home() parameter will return true only on the blog. This will be on the front page if you have your blog on the front page, or on some other page if you have the blog set to some other page.

          So this code should be working for you. Have you tried switching to the default WordPress theme and doing this code to see if it’s an issue with your theme? Your theme might be modifying the query incorrectly which is messing up your code.

          • says

            I switched to the default WordPress theme Twenty Ten. Then I went to Theme Functions
            (functions.php), I went all the way to the bottom and pasted there your code on…(Exclude Category from Blog). And it showed all the posts excluding the ones corresponding to the category I inserted in your code ( ‘cat’, ‘-115′ ). I tested with others categories and it worked well.
            So which would it be the problem with the theme I want to use, Eleven40 Child theme ?
            Thank you Bill

  13. says

    Julio, this is most likely because of the genesis grid loop. I’ve had issues with it since it creates a new query in the page rather than using the main one.

    You can open home.php and place your query arguments directly in the genesis_grid_loop() function, but you’ll get 404 errors when you get to the last of your blog posts (you’ll actually be getting 404 errors right now because of eleven40’s ‘posts_per_page’ => 5 argument).

    A better approach would be to rebuild the homepage to use the main WordPress query.

    • says

      Hmmmm, I do not want to be up to my neck in mud.
      Ok, I want to determine how to show posts of a specified category in my homepage. It can be one specified category or two specified categories, etc, and filter or block the other categories, that’s my goal.
      As you publicize in your article…”Don’t display posts from Category X on the homepage”, that’s good, but it doesn’t work with my eleven40 child theme, I copied the code you obsequiously give us under “Exclude Category from Blog” and pasted it inside of function.php and it didn’t work.
      Bill when you write “You must place your function in functions.php (or a plugin)….” Which is that Plug-in ? Do you think that Plug-in could solve my problem instead of trying different codes in function.php?
      Is that Plug-in the one you present in “Also take a look at my Display Posts Shortcode plugin, ….”?
      Thanks Bill

      • Bill Erickson says

        The proper method outlined above will not work with your theme unless you remove the genesis_grid_loop() function from your home.php file.

        Alternatively, you can use an improper method by modifying the genesis_grid_loop like this:
        Actual code: https://gist.github.com/3060649
        Difference: http://diffchecker.com/YVeR7tmA

        Change ‘homepage-posts’ to the category slug you want displayed on your homepage.

  14. Wes Linda says

    So i’m attempting to figure out how to offset posts on the homepage of the new education theme from StudioPress. I’ve read the tutorial and I guess I’m simply unsure of how to do this. I’ve tried numerous ways shown on a number of sites, with no success. Any ideas?

  15. Gary Darling says

    Great tutorial Bill. I’m trying to implement this on a site that uses `query_posts` in several template files, but I’m struggling with a custom query. The user wants his posts sorted by first by the meta_field ‘builder’, then by the meta_field ‘squareft’. So the result would be all builders named ‘A’ would be first, and within that group all his homes ordered by ‘2000’, ‘2100’, etc.

    I’ve tried variations of Orderby with two meta_values, with ‘title’ and ‘meta_value’, I tried two meta_keys, no luck. Any ideas? Here is the code I have now:

    http://gist.github.com/3678753

    • Bill Erickson says

      I don’t believe you can have WordPress sort the results by two keys like that. You should build your query, then run through it once or twice to manually sort it the way you want, then output it.

  16. Devplus says

    Thanks so much Bill for the tutorial.

    I looked at your example on excluding a category from the loop above, great! How do I exclude an array of post format from the loop? Let say I want to exclude aside and quote post format from the loop. Hope I didn’t ask you too much.

      • Devplus says

        Thanks so much for the code.

        However, I’ve problem with the code. My blog simply doesn’t show any blogpost with the message:

        “Sorry, no posts matched your criteria.”

        Here’s what I’ve in my functions.php https://gist.github.com/3712088 . Nothing much there, a fresh functions.php file from Sample Child Theme. I add support for post format and just paste your code above at the very bottom of the file.

        I’ve tried two ways:
        1. Since I’ve no home.php file, so I just leave it as it is. By default, genesis will show the loop of latest blogspot. No luck.
        2. Secondly, I tried creating a “blog” page, using the blog template. Then, from the reading settings page, I tick on the static page, and from the dropdown of “post page”, I chose the “blog” page I’ve created previously. No luck.

        Did I miss something here?

  17. Nicholas says

    Thank you for such great tutorial, very detailed explanation on how to use pre_get_posts.

    However, can pre_get_posts be used to be applied only to one custom query?

    Because, i want to list all posts with closed comments, only in one custom query that runs on one of my custom page templates.
    On another page i want to list all posts with opened comments, and on a third page i want to list all posts.

    Therefore, i’m using three different custom queries, the last one is simple to make, but i’m having problem with the first two – can i apply pre_get_posts on two different custom queries separately?

      • Nicholas says

        Thanks for the reply,
        but how can i include the comment status into a custom query?

        Under $args for WP_Query, there is nothing related to comment status?

        I have this bit of code https://gist.github.com/3709991
        Here, i display 10 posts per page, and for each post check if the comments are open – but that breaks my pagination – if among 10 posts, there are no posts with open comments, it will display a blank page, and i don’t want that.
        Or, if there are 2 posts with open comments among 10 posts, it will display 2 posts on a page.

        I want to list all posts with open comments, 10 posts per page.

        • Bill Erickson says

          It doesn’t look like comment status is an available query arg. You’ll need to craft a custom SQL query that looks for posts with ‘open’ in the ‘comment_status’ column of the posts table.

        • Bill Erickson says

          I don’t see anywhere on that page where it says comment_status can be used with pre_get_posts. The only place comment_status is located on that page is in showing what is returned in the $post.

  18. says

    Bill,
    I used your “Better, and easier grid loop” tutorial to display my CPT in a grid format (worked great, by the way). Now I’m simply trying to add those CPT posts to the main query.

    After reading your tutorial on customizing the query, I’m still not clear on how to simply include a category. If cat,-4 excludes a given category, does cat,+4 include one?

    • Bill Erickson says

      Are you trying to mix in your custom post type posts (ex: ‘products’ post type) into your blog ( the ‘post’ post type)? If so, use $query->set( ‘post_type’, array( ‘post’, ‘products’ ) )

          • says

            Hi Bill,

            I’m looking to add a CPT to my main blog page in genesis (which is not the home page). When I try to add in is_page(‘archives’), the main blog archives page turns to to a 404 page.

            Here’s the code. Do you have any suggestions?

            function namespace_add_custom_types( $query ) {
            if( $query->is_main_query() || is_category() || is_tag() && empty( $query->query_vars['suppress_filters'] ) ) {
            $query->set( 'post_type', array(
            'post', 'tips', 'nav_menu_item'
            ));
            return $query;
            }
            }

            add_filter( ‘pre_get_posts’, ‘namespace_add_custom_types’ );

          • Bill Erickson says

            A few issues:
            1. You need to make sure you’re always affecting the main query. You used an OR in there, not an AND.
            2. You never targeted the main blog page ( is_home() )
            3. I don’t think you want ‘nav_menu_item’ showing up in your blog archives.
            4. You don’t need to return the query. This is an action, not a filter.

            Here’s updated code: https://gist.github.com/billerickson/8919367

          • says

            Hi Bill,

            That code snippet is so much cleaner! Thank you.

            However, it still didn’t add a CPT to the main blog page:
            http://www.landlordlogy.com/articles.

            I’m using genesis, and the Article page is using the page_blog.php page template. Too bad there isn’t a condition called “$query->is_blog()”.

            I’ve tried adding $query->is_page_tempate( ‘page_blog.php’), and $query->is_page( 1207 ), to your code, but when I do, the Articles page turns into a 404.

            Do you have any other advice?

            Thanks in advance. I really appreciate it!

          • Bill Erickson says

            Never use the blog page template in Genesis. Just don’t do it.

            Go to Settings > Reading and set your blog page as the posts page. If your theme is written incorrectly (as many StudioPress themes are) and uses home.php as a widgetized front page, go into your theme files and rename that to front-page.php.

            I’ll be writing a blog post about things not to use in Genesis, and the Blog page template is #1.

        • says

          Hi Bill,

          Thanks for the clarification! I got it working now. Why don’t the folks at studiopress follow this practice instead of making their themes with a blog template. Maybe you’ll answer that in your upcoming post.

          Thanks again!
          Lucas

  19. says

    Don’t forget, $query->set( ‘meta_query’, …) will obliterate any meta query conditions that have already been set up by other plugins and filters. You need to get the current meta_query array (which may not be set at all, so needs initialising to an array if not) using $query->get( ‘meta_query’) then merge your conditions into that.

    — Jason

  20. Lee says

    Our home.php doesn’t require any default posts query. I’m a little lost on how to skip the main query entirely.

    So far the best I’ve been able to come up with is set posts_per_page to 1 (because if it is 0 it gets set back to the default). Any thoughts on how one might cancel the query from pre_get_posts?

    • Bill Erickson says

      home.php is used for displaying your latest posts. If you don’t want to display posts, you shouldn’t be using home.php. If this is for the front page of your site, go to Settings > Reading and select a page to be on the homepage.

      If for some reason you must use home.php but you don’t want posts listed, find where the loop is being generated in that template file and remove it. In genesis, you’d accomplish this by adding the following to home.php: remove_action( 'genesis_loop', 'genesis_do_loop' );

      • Lee says

        I hear you about the static homepage option. The problem for us is that we still want the query to run for paged requests (e.g. /page/2).

        Here is what I have in a pre_get_posts filter. It more or less works:
        if ($query->is_main_query() && $query->is_home && !$query->is_paged) {
        $query->is_home = false;
        $query->is_page = true;
        $query->is_posts_page = false;
        $query->is_singular = true;
        $query->set(“page_id”, HOME_PAGE_ID); // constant set in wp-config.php
        $query->set(“post_type”, “page”);
        }

        Thanks for the genesis loop tip, I’ll give that shot.

        • Bill Erickson says

          What are you using the paged requests for if you’re not displaying posts? In any case, those paged requests work on static pages as well.

  21. Simon says

    Hi Bill, when using the genesis loop, when a post is set as sticky, (for display on the homepage), the post also appears on page 2 (i.e. the posts overflowing from the front page onto subsequent pages).
    I’m thinking one way to remove the sticky from the overflow (page 2) listing, would be to assign it to a category–& then exclude that category from the standard genesis loop display.
    I writing to ask how I would do that–&/or if there’s a better way to achieve the same.
    Thanks,
    Simon

      • Simon says

        Actually Bill, there is one other issue that’s arises from the use of:
        ‘ignore_sticky_posts’ => 1
        Because there’s a single sticky post at the top of the home page, (which the template layout being utilised) it means one extra post is present on the home page compared to the overflow pages–which means the layout is affected on page 2, 3, 4, etc.

        • Bill Erickson says

          I don’t think there’s anything you can do about this. Why not just drop the sticky posts altogether? Or, add ignore_sticky_posts for all pages (I assume you’re only doing it for inner pages), then add a new custom query to the top that only runs on the first page and only displays the sticky post.

          • Simon says

            The homepage sticky’s such an integral part of the design–aesthetics wise. I need the single sticky to count for two posts. Is there a counter I can use to achieve this.

            What you’ve written (below) sounds like a possibility, (if I can get the sticky using it’s unique display formatting). How would I create/incorporate this query:
            ===
            add a new custom query to the top that only runs on the first page and only displays the sticky post
            ===

  22. says

    Hi Bill! First, thank you so much for your tutorials on here, they have helped so much! I am learning php on the fly, and am trying to do something specific on my archive page using the grid loop. I believe I have to use a custom query and I’m clueless, so I’m here to beg for help lol!
    Basically, I would like an archive page that shows the title and featured image for each post, but I would like all posts displayed by category. I don’t know if I am just being a glutton for punishment, but I’m really intrigued to see if this is possible, and how.

    My archive page template is set up as follows:
    https://gist.github.com/4562711
    I have it set up so I choose the categories displayed on the page via the: “genesis_get_custom_field(‘query_args’); / custom field setting on the page.

    I have tried using the wp codex, and adding
    echo ” . get_cat_name( $cat_id ) . ”;
    to the query section, but it doesn’t work… maybe I am putting it in the wrong place?
    Just to reiterate, I am quite clueless (sorry!) but I’d like the archive page to be set up so that each category is displayed on the page, ALONG WITH every post (title & image) in that category. In short, multiple categories displayed, showing all posts in those categories, all on 1 handy-dandy page. I hope I explained that properly? Any advice would be much appreciated (or feel free to let me know if I really am being a glutton for punishment trying to do this!)
    Thanks so much.

    • Bill Erickson says

      Your description sounds like you want to loop through all categories and display all of their posts, but the code you provided looks like you want to query a specific category.

      1. If you’re trying to display only the posts from a single category, and specifying that category in the backend using a custom field, your template would look like this: https://gist.github.com/4572940

      2. If you want a template file that loops through all categories, and for each category shows all their posts, your template file would look like this: https://gist.github.com/4572998

  23. Patrick says

    Hi BIll,

    I used the pre_get_posts method to change the post_type used on my home page to a custom post type I have. This works great and avoids having to perform more queries on this page, however on other pages I use template files and would love to be able to target these pages in a similar way via functions.php and alter the main query as I have done with the home page.

    However, I tried simply changing if ( $query->is_main_query() && $query->is_home() ) { to if ( $query->is_main_query() && $query->is_page(ID) ) { but this doesn’t work. I’m sure there must be a way of using pre_get_posts on template pages, by altering the main query but I can’t work out how to do it.

    Any help would be appreciated as I really notice the difference in speed, thanks.

    • Bill Erickson says

      I’ve never used it on template pages because in my experience, template pages are typically secondary queries, so I use WP_Query().

      In your template, put this:

      global $wp_query; print_r( $wp_query );

      That will tell you everything that’s currently in the query. You can then use the pre_get_posts method to remove everything you don’t want in there.

  24. says

    Hi Bill and thanks for a great post! I was trying to change the number of posts shown in a particular category, but because of the posts_per_page pagination bug I simply couldn’t get it done, no matter what other possible solutions I’ve tried. Yours did the trick! Have a great one and thanks again!

  25. says

    Hi Bill,
    Thank you for taking the time to write these posts — very helpfu!

    I am new to WP & Genesis, have some coding experience with minor PERL programming 10 years ago. I am having a challenge with how posts are handled at the index (home) versus the Archives. At the index I want one post, the most recent, to show in full. On the archives, I want multiple posts to show as an excerpt. The problem is WP & Genesis settings gives me either excerpts globally and/or 1 post globally (both home and archives). At WordCamp Atlanta, Travis Smith helped me with part of the problem (removed pagination on Archives — only one post listed at a time) but I later learned it didn’t address the entire problem. It is here:
    https://gist.github.com/wpsmith/5177901
    I know I’m on the right track and I need some direction, even how to go about it. Any assistance would be most appreciated! Also, starting PHP tutorials this afternoon!
    Thanks,
    Smythe

  26. kate says

    Hello Bill,
    I was using fine the new WP_Query all the time, but then was told that it causes performance issues on the big project. Was suggested to use ‘pre_get_posts’ action. I browsed the web for quite long time, tried all the possible ways to use it in my case, but no success.
    In my case, every post has one of the categories (berries, veggies, fruit). Some of the posts have custom field ‘rating’ that contains some integer. I need to display the posts making sure that posts with higher rating are displayed first, and the are followed by all the rest (involves infinite scroll during it too).
    Here is what I tried: https://gist.github.com/emelyanenko/ff0f74c4293d58d43bd4
    Printed request looks like that:
    SELECT wp_posts.* FROM wp_posts WHERE 1=1 AND wp_posts.ID = 227 AND wp_posts.post_type IN (‘post’, ‘page’) ORDER BY wp_posts.post_date DESC

    And it only returns me the content of that page with id = 227.

    How can I make it return me the other posts, not only the containing page? Ideally, I would need this action to be applied only to that particular query where I’m displaying the posts of the 3 mentioned above types.

    Thank you in advance

    • Bill Erickson says

      You should only use pre_get_posts if you’re customizing the current page’s query. In your case, I think a custom query will be best. You’re trying to load these results on a page, so you need the page’s query to run first and then do your custom query inside of it.

      WP_Query actually does 4 queries to the database:

      1. Get the actual results of your query
      2. Figure out how many other posts match that query, for pagination purposes
      3. Get taxonomy terms for the current results
      4. Get metadata for the current results

      The first one is the important one. If you don’t need the other three, you can turn off the ones you don’t need like this: http://www.billerickson.net/code/improve-performance-of-wp_query/

      It looks like you’ll need all of them. You need the # of posts for pagination / infinite scroll, you need the categories, and you care about post meta (rating).

      Don’t worry so much about the performance of this query. Just make sure you have a caching plugin installed and then the query performance won’t matter much.

  27. Brian Novak says

    Bill,
    Is there any way write the function to simply include one category and exclude all others? I have many categories that and will be adding new ones that I do not want to appear on the homepage feed.

    Thanks,
    Brian

  28. Nathaniel Ryan says

    I’ve gone through these steps, but must be overlooking something very obvious, and hope you can assist. I want my category page(s) to sort A-Z.

    So using your example of Events:
    When I were to go to /events/ I would like the posts to be listed A-Z, as well if I have a child inside events called Sports or Free, then the A-Z sorting would still work on
    /events/sports/
    /events/free/

    I’m using Genesis, and feel like I should be able to go into my Child theme functions.php or using the Simple hooks plugin, I should be able to say orderby ‘title’ and order ‘ASC’ but I’m not having luck so far.

    Thanks so much-
    Nate

  29. Antonio says

    Hello Bill,

    imagine you have two kind of “events” and want to use the same pre_get_post hook for both.
    But then, in one event custom post type you have defined a custom taxonomy “country” so you can query by $query->set( “country”, “uk”) but then, in the other event custom post type you havent defined any country to uk but still want to have a valid query.
    How would you do that?

    Since we are in the main_query we still dont know if “that query” will be empty so we cannot choose when to query by country uk or query by every country.

    I am doing it so far with another function.
    https://gist.github.com/antorome/b76cbdae202a4d22efe3#file-gistfile1-php

    In the example the tax is “seccion” and “mejores-ofertas”.

    so then back in the pre_get_posts I do
    if (cdb_productos_afiliacion(“event”))
    $query->set(“seccion”, “mejores-ofertas”)

    Do you know if there is anyway to add query->set(“country”, “uk”) directly in the main query so if the given custom post doesnt have any post with a country tax set to uk, it will query the default one???

    Thanks

    • Bill Erickson says

      I don’t understand the question. Are you saying you have two different ‘event’ post types? Why?

      Why don’t you use the taxonomy archive for your country query? Go to /country/uk and you’ll see all events in that taxonomy term. Go to /events to see all events, regardless of country.

  30. says

    Bill-

    This is so close to what I need to do on my custom blog page. Right now, I created a bare bones page_blog.php and added that to my child theme. Right now genesis is only pulling in the ‘post’ type. I’d like to to query ‘post’ and ‘podcast’.

    What should I add to my blog template to accomplish this? Thanks again for your such good resources.

    -Dan

    • Bill Erickson says

      I do not recommend you use a custom page template like page_blog.php. You should be modifying the main WordPress query as described above.

      1. Delete your page_blog.php file
      2. Go to Settings > Reading and set your blog page as the one that displays your posts
      3. In functions.php, add this: https://gist.github.com/billerickson/9056496

  31. says

    That would be okay, except in this theme, the front page- is a rather nice page (http://www.appendipity.com/pintercast/) that I don’t want to lose to a standard blog page.

    What this theme offers is a nice dynamic front page- then a blog page which pulls in podcasts and posts. Except that I can’t get the blog page to pull in CPT: ‘podcast’
    Make sense?

    -Dan

  32. Gena says

    Quick question.

    How do I exclude Genesis blog template from this grid loop.

    I can see this if( ! ( $query->is_home() || $query->is_archive() ) ) and from codex it says the “pre_get_posts action doesn’t work for Page requests.”

  33. Aaron says

    Hi Bill,

    Thanks for the great tutorial.

    I’d like to sort a CPT Archive dynamically and wonder if pre_get_posts can be used in conjunction with a query string to accomplish this.

    Here’s a quick example, but I haven’t been able to get it to work: https://gist.github.com/aaroncolon/9244757

    Guessing it’s not possible or I’m missing something very obvious. Any help is greatly appreciated.

    Thanks,
    Aaron

  34. says

    Is it possible to query posts based on the current date and then randomise the results using ‘&orderby=rand’.
    First I need to query the posts based on the current date and then apply ‘&orderby=rand’ to it? How do I do it?

  35. Hung Pham says

    Hi,

    I want to combine the filters for all posts having publish status and all owned private posts. I am thinking to use
    – $query->set (‘post_status’, ‘publish’): for all published posts
    – $query->set(‘author’, $user_ID): for all owned posts

    But don’t know how to combine them together in one filter. Please advice.

    Thanks.

  36. says

    Hi Bill
    I want to order posts from a specific categorie. So i used your last code snippet and changed
    is_post_type_archive( event’ )
    to
    is_post_type_archive( ‘wordpress-website-maken )

    But after the change my website breaks.
    I remove the code and its all good again.
    What did i do wrong?
    What can i do to sort those posts by number?

    thanx in advance.
    andy

    • Bill Erickson says

      Two things: First, you forgot the closing ‘ at the end of maken. That’s probably what broke your site.

      But more importantly, is_post_type_archive() only applies to post type archives. So if you created a custom post type called “WordPress Website Maken” then yeah, that would be the right one to use. But it sounds like it’s a category of posts. So you’ll want to use is_category()

  37. Ross says

    Hey Bill, great post! Even though it’s 3 years old, it still helped me figure out the damn query issue I was having.

    But now I’m stuck on a new one – I’ve installed the WTI like plugin so that users can rate posts and want to sort grid items on my front page by date/score. My php looks like this:

    $query->set(“orderby”, ‘date meta_value_num’);
    $query->set(“order”,”DESC”);

    As per the instructions at http://codex.wordpress.org/Class_Reference/WP_Query and http://www.webtechideas.com/sorting-posts-by-meta-key-and-value/

    Date seems to be sorting properly, but score doesn’t. My guess is that perhaps the orderby isn’t parsing properly and so it’s just defaulting the default date sort? If I just sort by score, that works fine… if I just sort by date that obviously works fine… it’s combining the two that doesn’t work. Even tried upgrading to WP 4 beta 3 to try the new multi-order des/asc feature, but no joy.

    Any thoughts? Website I’m having issues with is at http://goo.gl/TfPIzR

    • Bill Erickson says

      I don’t think the sort by date will work for you. It is actually sorting by the UNIX datetime. So the only way the second orderby parameter will be used is if two posts were published the exact same second. If they have different datetimes, then it doesn’t matter what the score is since the one with an earlier post date will show up first.

      If you sorted by ‘meta_value_num date’ it would show the highest ranked first, and if two items have the same score it would then sort by date.

      • Ross says

        Damn. Unix date makes it pretty impossible to sort by ‘date’ rather than ‘time’ unless i hack the SQL calls from WordPress.

        What a pain.

        Ah well, thanks for the quick response Bill.

        • Bill Erickson says

          You could add a meta_key that stores just the date. Ymd, like 20140521. Then you could sort based on that and your other meta field.

          • Ross says

            Probably a better idea – I had considered that in the wee hours last night but lack of sleep made me forget. If I can automate the population of that field, even better.

            Is it possible to sort on two meta fields?

            I’ll jump in tomorrow and give it another shot, cheers Bill

  38. Ross says

    Yeah, I actually gave the array a shot yesterday. Didn’t help with the date problem obviously.

    With the meta key, the issue is that you ‘orderby’ meta_value_num and then specify the meta_key in a subsequent setting, so I’m not sure if I can specify multiple attributes in meta_key.

    $query->set(“orderby”, ‘date meta_value_num’);
    $query->set(“meta_key”, ‘_wti_total_count’);

    Guess there’s only one way to find out :)

    • Ross says

      Interesting addendum, I changed some of my post dates to 00:01 and ran the same sort… STILL didn’t work.

      Looked in the database and of course the date shows up as 00:00:01:22 or 00:00:01:12 and the order by still uses

      I’ve ended up hacking query.php, which I don’t really like… but it works:
      case ‘post_date':
      $orderby = “CAST($wpdb->posts.{$orderby} as date)”;
      break;

      I’ll have to make sure I turn off auto-updates and re-hack the query file every time I upgrade in future… I’ll see if I can make a suggestion to modify the core

      • Bill Erickson says

        Why not make this change in pre_get_posts, as described above? Then the code can be in a theme or plugin.

        • Ross says

          You can specify what attribute to sort on with pre_get_posts, but I don’t believe you can specify a cast… if the orderby attribute you set in the query doesn’t match the expected values, query.php (core) will ignore it

  39. Easy Mark says

    Hi there, Bill:

    I know this is an older post, but I just want to double check on one thing.

    In line 13 of the LAST example posted, you have an if statement that starts out like this:

    if( $query->is_main_query() & !$query->is_feed()…

    Just wasn’t sure if this was a typo and that there are supposed to be two && after the is_main_query() or whether there is only supposed to be one.

    If it is just a type, and there were supposed to be two &&, then I understand that line.

    If there is SUPPOSED TO BE only one, then I am going to need some help figuring out what the difference in php is between one & and two &&…

  40. Ian says

    Hey Bill!

    Still can’t believe how much love this post gets all these years later :)

    I’ve been trying to figure out how to change the loop on category pages so that I can display them in A-Z order, but also so that I can separate posts by letter (i.e. All posts starting with A, then B and so one, with jump links to each.

    I’ve got the A-Z working but can’t figure out how to customize the loop to show what I’m trying to achieve. Any pointers you have on this would be great. I’ve search, but it’s not something that comes up too often…

    Cheers!

    • Bill Erickson says

      There’s no easy way to do that. One approach is to create a new taxonomy called “Post Letters” or whatever you want to call it, then put posts that start with “A” in the “A” category. Then to see all the posts that start with A, you go to that taxonomy term archive.

      A more automated approach is to write a function that runs on ‘save_post’ to update a meta key that stores the first letter of the post. Then when you want to view posts that start with a certain letter, you do a meta_query for posts with that value as the meta_key

  41. says

    Hey Bill,

    I am trying to exclude a category from my custom post type archive but am not having any luck. I am using the genesis framework, executive pro theme and set up custom post types and archives in it.

    I also tried excluding categories form the whole site with no luck as well.

    Below is the code I put in the functions file but not sure if there is an easier code I can put in the custom post archive file.

    https://gist.github.com/billerickson/284f5b4135c67c66b3e0

    ANy help would be appreciated.

  42. says

    I tried the code you mentioned. archive name is business listings that I swapped out for events. The categories are still showing however on the business listings archive.

    /**
    * Exclude Category from Blog
    *
    * @author Bill Erickson
    * @link http://www.billerickson.net/customize-the-wordpress-query/
    * @param object $query data
    *
    */
    function be_exclude_category_from_blog( $query ) {
    if( $query->is_main_query() && $query->is_post_type_archive( ‘business-listings’ ) && !is_admin() ) {
    $query->set( ‘category__not_in’, array( 12, 34, 47 ) );
    }
    }
    add_action( ‘pre_get_posts’, ‘be_exclude_category_from_blog’ );

    • Bill Erickson says

      Are you sure you have the ‘category’ taxonomy being used on this post type, and not a custom taxonomy like ‘business-category’ ? the ‘category__not_in’ only works with the taxonomy called “Category” which is applied to the “Post” post type. I’m 99% sure you’re using a custom taxonomy since that’s a more appropriate way to do i t.

      In that case, you’ll need to use a taxonomy query. Actually writing the code for you is outside the scope of free support I offer in my blog comments, but this can get you started: http://codex.wordpress.org/Class_Reference/WP_Query#Taxonomy_Parameters

Leave a Reply

If you'd like to include code in your post, please post it to http://gist.github.com and include a link.