Customizing the WordPress Query with pre_get_posts

One of the most powerful features of WordPress is the WordPress 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.

For example, you might want to:

  1. Exclude posts from a certain category on the homepage
  2. Increase or decrease the number of posts displayed per page for a specific post type
  3. Sort posts in your category archive by comment count rather than post date
  4. Exclude posts marked “no-index” in Yoast SEO from your on-site search results page
  5. List related articles at the end of a post.

If you’re interested in looking “under the hood” at how queries in WordPress work, here’s the slides from a presentation by WordPress Lead Developer Andrew Nacin. I’m going to focus this tutorial on common uses.

First, what not to do.

Don’t use query_posts()

The query_posts() function completely overrides the main query on the page and can cause lots of issues. There is no instance where query_posts() is recommended.

Depending upon your specific requirements, you should create a new WP_Query or customize the main query.

If you need a simple way to list dynamic content on your site, try my Display Posts Shortcode plugin. It works just like the custom query described below but is built using a shortcode so you don’t have to write any code or edit your theme files.

Customize the Main Query

The “main query” is whatever WordPress uses to build the content on the current page. For instance, on my Genesis category archive it’s the 10 most recent posts in that category. The first four examples above all require altering the main query.

We’ll use the WordPress hook pre_get_posts to modify the query settings before the main query runs. You must your function in your theme’s functions.php or a core functionality 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 every query from nav menus to recent comments widgets. We’ll do this by checking $query->is_main_query().

We’ll also this code isn’t running on admin queries. You might want to exclude a category from the blog for your visitors, but you still want to access those posts in the Posts section of the backend. To do this, we’ll add ! is_admin().

Then we’ll check to make sure the conditions are right for our modification. If you only want it on your blog’s homepage, we’ll make sure the query is for home ( $query->is_home() ). Here’s a list of available conditional tags.

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 my WP_Query arguments guide or the WP_Query Codex page.

Exclude Category from Blog

 * Exclude Category from Blog
 * @author Bill Erickson
 * @link
 * @param object $query data
function be_exclude_category_from_blog( $query ) {
	if( $query->is_main_query() && ! is_admin() && $query->is_home() ) {
		$query->set( 'cat', '-4' );
add_action( 'pre_get_posts', 'be_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 the category with an ID of 4.

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
 * Change Posts Per Page for Event Archive
 * @author Bill Erickson
 * @link
 * @param object $query data
function be_change_event_posts_per_page( $query ) {
	if( $query->is_main_query() && !is_admin() && is_post_type_archive( 'event' ) ) {
		$query->set( 'posts_per_page', '18' );

add_action( 'pre_get_posts', 'be_change_event_posts_per_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, 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.

Here’s more information on building Custom Metaboxes. My BE Events Calendar plugin is a good example of this query in practice.

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
 * Customize Event Query using Post Meta
 * @author Bill Erickson
 * @link
 * @param object $query data
function be_event_query( $query ) {
	if( $query->is_main_query() && !$query->is_feed() && !is_admin() && $query->is_post_type_archive( 'event' ) ) {
		$meta_query = array(
				'key' => 'be_events_manager_end_date',
				'value' => time(),
				'compare' => '>'
		$query->set( 'meta_query', $meta_query );
		$query->set( 'orderby', 'meta_value_num' );
		$query->set( 'meta_key', 'be_events_manager_start_date' );
		$query->set( 'order', 'ASC' );
		$query->set( 'posts_per_page', '4' );

add_action( 'pre_get_posts', 'be_event_query' );

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

All of the above examples showed you how to modify the main query. In many instances, you’ll want to run a separate query to load different information, and leave the main query unchanged. This is where Custom WordPress Queries are useful.

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, you could do something similar to this:

 * Display posts about spain
function be_display_spain_posts() {

  $loop = new WP_Query( array(
    'posts_per_page' => 5,
    'category_name' => 'spain',
  ) );
  if( $loop->have_posts() ): 
    echo '<h3>Recent posts about Spain</h3>';
    echo '<ul>';
    while( $loop->have_posts() ): $loop->the_post();
      echo '<li><a href="' . get_permalink() . '">' . get_the_title() . '</a></li>';
    echo '</ul>';
add_action( 'genesis_after_entry', 'be_display_spain_posts' );

For this you’ll use the WP_Query class. For more information, see my post on Custom WordPress Queries. I also have a WP_Query Arguments reference guide.

Bill Erickson

Bill Erickson is a freelance WordPress developer and a contributing developer to the Genesis framework. For the past 14 years he has worked with attorneys, publishers, corporations, and non-profits, building custom websites tailored to their needs and goals.

Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions


  1. Dan Tudor 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. Stephen Harris says

    Thanks Bill! I’ve recently written a post on my blog ( 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. Paul says

    thanks for the tutorial. I used it to filter posts by a custom field value with BETWEEN to allow searching within a numeric range.

  5. cori 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:
      3. In functions.php, exclude featured posts from the main query on category pages. Something like this:

      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.

      • cori 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.

          • cori 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!

  6. cori 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:

      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:

        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 🙂

  7. Wayne Burke 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.

  8. 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. 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

  9. 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

  10. Tyy 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 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 ( ). I have a taxonomy called ‘code tags’, and here’s a term inside that taxonomy:

      • Tyy 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.

          • Tyy 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!

  11. monti 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. 🙁

  12. monti 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-2883980

    The address of my site is: 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:

  13. Julio Alarcon 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.

    • 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.

      • julio alarcon 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.

          • julio alarcon 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

  14. Bill Erickson 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.

    • julio alarcon 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:

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

  15. 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?

  16. 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:

    • 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.

  17. 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 . 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?

  18. 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
        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.

  19. Brian McFarlin says

    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’ ) )

      • Brian McFarlin says

        Yes…that is what I’m trying to do. I’ll try the code you provided.

        One more question:
        On the corresponding add_action, could I use pre-get_posts?

          • Lucas 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:

          • Lucas Hall 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:

            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.

        • Lucas Hall 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!

  20. Jason Judge 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

  21. 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.

  22. 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.

      • 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

  23. Mary 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:
    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:

      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:

  24. 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.

  25. Razvan 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!

  26. Smythe 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:
    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!

  27. 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:
    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:

      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.

  28. Brian Novak says

    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.


  29. 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

    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-

  30. 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.

    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???


    • 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.

  31. Dan R. says


    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.


    • 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:

  32. Dan R. says

    That would be okay, except in this theme, the front page- is a rather nice page ( 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?


  33. 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.”

  34. 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:

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


  35. Kevin 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?

  36. Hung Pham says


    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.


  37. Andy Jobben 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’ )
    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.

    • 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()

  38. 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’);

    As per the instructions at and

    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

    • 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

  39. 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)”;

      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

  40. 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 &&…

  41. 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…


    • 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

  42. Steve P 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.

    ANy help would be appreciated.

  43. Steve P 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
    * @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:

  44. Steve P says

    Thank you for your response.


    I will try the taxonomy queries and see how that goes.

  45. Chandra says

    Is this possible to display slider from single post uploaded images? Or else is there any plugin to upload images in post and display it as slider on single post..

    • Bill Erickson says

      Try Soliloquy. Or download my Gallery Rotator plugin for free at GitHub, upload the images, click “Create Gallery”, add them to a gallery, and before inserting the gallery into your post click “Display as rotator”.

  46. Kevin says

    Hey Bill I am trying to do the following to a blog and am having a heck of a time:

    • Customize the first post to be full width, make it more of a featured/hero
    • After the new featured/full-width page I want to just list the posts per usual (using Genesis theme). And have a sidebar. All my other blogs I do this easily, I let Genesis list out the posts and I create a custom sidebar.
    • Any archive pages would just be the default list of posts no featured post
    • Preferably compatible with Infinite Scroll via Jetpack

    Any insight at all would be great. Loving the new site fonts btw! Nice and clean.

    Thanks as always,


    • Bill Erickson says

      If you just wanted to change the style or output of the first post, that would be easy. For instance, use the ‘pre_get_option_image_size’ filter to modify the image size for the first post. The issue is that the first post needs to occur outside the content area.

      I’d try something like this in home.php:

      That’s untested, but it should get the first post set up and displayed after the header. Then when you get to the actual loop it will start with the second post, since you didn’t rewind_posts().

  47. ChrisScottUK says

    Hi Bill,
    Thanks so much for this great site, and resource!
    1 question I have – Is it possible to use a custom field on a page editor to determine which category of posts would be displayed on that page.
    I’m trying to figure it out, but am getting myself in knots. I need to allow someone editing the page to make quick changes to which categories would be displayed without amended the code each time

    • Bill Erickson says

      Yes. You could use the Custom Fields metabox (click “Screen Options” in top right corner of Edit Page screen to turn it on if it isn’t already active), or you could create a custom metabox using CMB2 or ACF.

      Give them a field where they can specify the category slug. Then in your theme file, when you’re putting together your query do something like this, where ‘be_category’ is the name of the meta field that has the category slug.

  48. Kan says

    Thank you Bill.

    After countless hours of trying to get pagination to show the correct number of pages on a new WP_Query within an custom post type archive template, using your solution to customize the main query and using a normal loop on the custom template page did the trick.

    And going through the slides for Andrew Nacin’s talk also gave me some good insight. I seriously cannot thank you enough. How can I buy you a beer?

    • Bill Erickson says

      Glad to help! A great way to “buy me a beer” virtually is to share this post on Facebook (button is below the article). The more people see it, the more likely it will send a new client my way 🙂

  49. Mehdi Madhkhan says

    Hi bill,

    Thanks a lot for this impressive post. I wouldn’t really like to bother you or anyone who would take the time and trouble to post an answer to my question, but, it seems after I’ve modified the main query this way, the codes on my
    file, which was provided by the theme developer won’t work anymore and nothing appears on the category (archive) page. I’ve tried hard to find the answer on WordPress Codex but most of what I see there is simply too hard for me to understand. Would you kindly let me know how to show results after the query’s been implemented?

    So sorry if this looks like a rookie’s question as I’m both new to object-based PHP coding and to WordPress coding.

    Any help would be truly appreciated.

    • Bill Erickson says

      Can you post the code you’ve used to customize your query? Go here and paste the code (so it’s formatted correctly), then grab the link and post it in a comment.

  50. Mehdi Madhkhan says

    hey Bill,

    Sorry if I’m disorganizing comments on your page but seems my previous comment was not published so I can follow up and I have to write another one.

    Earlier, I posted this comment on your website:
    Hi bill, Thanks a lot for this impressive post. I wouldn’t really like to bother you or anyone who would take the time and trouble to post an answer to my question, but, it seems after I’ve modified the main query this way, the codes on my archive.php file, which was provided by the theme […]

    And received this reply via email:
    Can you post the code you’ve used to customize your query? Go here and paste the code (so it’s formatted correctly), then grab the link and post it in a comment.

    Here’s what I’ve done so far:
    First, I add the following function from your website to my functions.php file:

    Up to this point, posts from category with ID 64 have disappeared from the archive.php (category page) which means the function has been successfully applied to the category but I don’t know what to do next to show posts from that category ordered by their title ascending. When I try to gettype($query) on my archive.php file, it claims that $query is “Null” and I don’t know how to use something Null or to rebuild the query object. And here’s the code on my archive.php which was provided by my theme developer:

    As you can see, I’m quite new to OOP and as such, I’d really appreciate any help (even easy to understand tutorials) so I can fix the code. Thanks a lot for your kind cooperation in this matter.

    • Bill Erickson says

      What specifically are you trying to do? Why have you excluded that category if you’re trying to list its posts ordered by title?

      • Mehdi Madhkhan says

        Thanks for your reply. Seems I’ve really F*ed up the code! As I told you, I’m quite new to WordPress programming and I’ve only worked on writing simple CMSs of my own using basic SQL queries.

        I thought first I should exclude the category I’m trying to fetch posts for and then send another query to fetch the posts ordered by title to form a list of posts. If I’m wrong (which is quite likely) would you kindly tell me what’s the practical way to do that? If I’ve failed to convey what I mean by now I’d like to say “I want a list of posts in that particular category ordered by title and with a certain number of posts on a page. Would you kindly provide the code snippets I should use on functions.php file and on archive.php file? I think it would benefit every novice user like me”. Thanks in advance.

          • Mehdi Madhkhan says

            Thank you but still WordPress outputs its default results on category 64 results page. I tried adding the line

            echo “Hi there!!!”;

            to the function to see whether it’s being called at all on category 64 page but the phrase “Hi there” appeared before any other source code on the page. Maybe that’s because it’s not in objective style. I don’t know why the default results are not being overwritten by the function. It seems I’ll have to use a specialized file called category-64.php and change the default code on it (which I don’t know how). Any updates on that?

          • Bill Erickson says

            The code I linked to above will make the category with an ID of 64 display its posts ordered by title, A-Z.

            If you need more help, I recommend hiring someone on Codeable.

          • Mehdi Madhkhan says

            Thanks a lot. You’ve been very helping. Yes I think I’ll go for that. I don’t know why I can’t see your last reply here but I hope you receive my message. Have a great time!

  51. Laurent says

    Thank you Bill for your blog and your work 🙂 and thanks in advance for your help.

    So I have a problem because my “primary-menu” disappears since I have pasted “pre_get_posts” in my Functions.php file.

    I need this “pre_get_posts” to sort my post by Deadline’s ACF fields.

    So I created others menus (header and extra menu), so It works with these menu If I remove my pre_get_post functions.

    Perhaps (probably) I have made a mistake…. Well, Bill or someone else if you could explain it to me, thanks a lot !

    see code here :

    • Bill Erickson says

      Your code is backwards. You’re modifying every query EXCEPT the main query, when you should be modifying ONLY the main query. Here’s an updated version:

      The conditional is saying “Only modify the main query, and don’t do it if we’re in the backend, and only modify it for the blog home or other archive pages.

      You can change the is_home() || is_archive() to apply to whatever context you want, but that should cover the post listings. You could also add is_search() but if you don’t have the ‘deadline’ field on pages then none of them will appear in search results.

      • Laurent says

        Yep ! Thank you very much Bill, You’re great !!! It works : my pre_get _post functions runs and my menu appears….
        Me, I’m so stupid because the github code I paste for you yesterday was wrong ! !
        See here the real code I had before…
        With this code I had, in all cases (home, archive, search, catégory) my posts sort by deadline.
        If you wanna tell mel why this code was wronng…..,
        Nevertheless as you told me , I have added “is_search()” Everything is allright !!
        A very very big thanks from France !!! 🙂

        • Bill Erickson says

          The issue with that code is you’re modifying every query EXCEPT those in the admin area. You still need to check $query->is_main_query() to make sure this is the main query on the page, and not a different query like the query for menu items.

  52. Alistair says

    Hi I am doing this tutorial which is great and works fine except I would like it to show all the pages in a category before it moves on to the next page so Portfolio1 might have 7 posts Portfolio2 3 and Portfolio3 6pages how would i alter the below to achieve this ( if possible)

    function sk_change_portfolio_posts_per_page( $query ) {

    if( $query->is_main_query() && !is_admin() && is_post_type_archive( ‘portfolio’ ) ) {
    $query->set( ‘posts_per_page’, ‘5’);

    • Bill Erickson says

      You won’t be able to do that with the post type archive because the posts won’t be in the correct order.

      What you should do is:
      1) Use WP Term Order to specify an order for your portfolio categories.
      2) Set the portfolio category archive query to display all posts (something high like 999).
      3) On the portfolio category archive, use get_terms( 'portfolio-category' ); (change that to your actual taxonomy name) to get a list of all the categories in order. Loop through them to find which one matches the current archive page. Use the previous and next categories as you pagination links.

      That way you start with /portfolio-category/portfolio1, then click “Next” to get to /portfolio-category/portfolio2. All the posts in that category display because you have posts_per_page set to 999.

    • Alistair says

      Hi Bill me again, I have tried a few modifications as I didn’t like the product-category being in the slug which the portfolio plugin added so i created my own CPT call the same but changed the slug to products instead. but now I have broken it as its not calling the portfolio-archive.php so I dont have individual groups anymore ie if i go to it shows the full post but if i go to it shows all the posts below is my code can you please tell me what I have done wrong:

      • Bill Erickson says

        Please post code as a gist since it usually gets messed up in the comment area (I updated your comment for you).

        The slug used for the taxonomy term archive is specified when you register_taxonomy(), and none of the code you provided registered the taxonomy. You’re actually adding the default WP taxonomies (category and post_tag) to your portfolio rather than the custom taxonomies referenced later (portfolio_category and portfolio_tag). You should either:
        a) Register the portfolio_category and portfolio_tag taxonomies
        b) Use the default category/tag taxonomies on your portfolio by changing the code in be_portfolio_template() to apply if is_category() || is_tag(). Although if you are using posts then this will mess up those archives as well, which is why we used custom taxonomies.

  53. Antuan says

    there any way to get exclude post dated the previous day where they exit the home chronologically? What I want is for the current day publications appear every day, and those who have passed the previous day are not even remain on file. Many thanks.

  54. Jamie Grove says

    Excellent post Bill, is it possible to use pre get posts to exclude duplicates, what I am needing to is on a taxonomy page (woo product tag) I need to exclude posts /products with the same post meta, I know how to do it via a foreach and while loop but not sure if is possible with pre_get_posts, any insight would be most appreciated 🙂

    • Bill Erickson says

      You can customize the query so that only posts with or without a certain meta value are included, but I don’t believe you can exclude posts that have duplicate metadata.

      • Jamie Grove says

        Ok thanks for getting back to me, that is what I was thinking but wanted to check that I wasn’t missing anything obvious.
        Thanks again

  55. Mazepress says

    Is there a way of creating a shortcode that can run wp reset query before your new shortcode runs? To avoid the loop affecting the query within another shortcode.

    Example: Trying to add shortcode to display 5 products of a woocommerce sub category on the parent category intro but finding the parent category loop injects within the shortcodes. Maybe off topic but can’t find anything on the subject.

    • Bill Erickson says

      You shouldn’t need a separate shortcode. The shortcode that does the query should use `wp_reset_postdata()` when the loop is complete.

      Instead of creating your own shortcode, you might try using Display Posts Shortcode. Or if you do build your own shortcode, you can use that plugin as a guide (see here for how the post data is reset).

      • Mazepress says

        Thanks for your insight Bill.

        I was using the WooCommerce provided shortcodes to display the products. These are hooked in where the product description would normally be. The full product category loop then runs underneath but for some reason all of the woocommerce shortcodes to display 5 products from specific sub categories all end up getting overwritten by the loop/query running on the product category archive itself.

        I also tried a custom module approach to display these same items but the same result happened where the query or loop for the specific product archive overwrites the intro sub category lists of products. I can’t share the link to demonstrate here as it is sensitive and while I don’t want to take up your precious time if you have a moment I could email you the link so you can see what I mean.

        Using your display posts shortcode might be the only option but wanted to see if I could get the standard built in tools to work before exploring additional tools to make it work. I was wondering if I could set a function which applies a wp_reset_postdata() every time it sees a specific shortcode in the content of a page as another workaround.

        I am using Genesis but I am also using Beaver Builder to leverage end user ease of access etc. I have them working in complete harmony though so don’t see why it would affect this.

        Greatly appreciate your input, Bill.


        • Bill Erickson says

          I don’t use WooCommerce (I don’t build ecommerce sites), so I’m not much help there, but I wouldn’t think the core WC shortcodes would cause query issues. I recommend you reach out to a developer that specializes in WooCommerce like Daniel Espinoza.

          • Mazepress says

            Thank for the recommendation. I will do that. Wow if you worked on eCommerce you would be lethal! I mean, you are lethal anyway Bill, everyone knows that but I am maybe a little surprised you haven’t ventured into online retail.

            Thanks again, have a nice day.

  56. Jamie Mitchell Design says

    Hi Bill

    using the pre_get_posts

    would I be able to set a taxonomy of ‘region’ to only show parent posts and not child posts?

    I’m using Hierarchical CPT’s and ALL posts show when viewing a taxonomy term

    for the life of me can’t work out how to only show the parent posts

    any advice appreciated, thanks in advance

  57. Alexander says

    Thanks for this amazing post. You can add a rewrite rule for custom post type by adding this code

    function prefix_news_rewrite_rule() {
    add_rewrite_rule( ‘news/([^/]+)/photos’, ‘index.php?news=$matches[1]&photos=yes’, ‘top’ );
    add_rewrite_rule( ‘movie/([^/]+)/videos’, ‘index.php?news=$matches[1]&videos=yes’, ‘top’ );

    add_action( ‘init’, ‘prefix_news_rewrite_rule’ );


  58. MrO says

    Hi Bill,

    Just wanted to post a thank you here for your ‘Order results by multiple meta keys’ code snippet, it was the exact solution to my problem as well as help me understand why it works, which cannot be said of the other solutions/hacks I found on the interweb.

    FYI, the link on that page to this page is broken.

    Thanks again and happy new year!

  59. Dave says

    My site is using the latest version of WP and a Genesis theme. I tried to use your code for “Exclude Category from Blog” in the functions.php file and got this error:

    Your PHP code changes were rolled back due to an error on line 525 of file wp-content/themes/showcase-pro/functions.php. Please fix and try saving again.

    syntax error, unexpected ‘’’ (T_STRING), expecting ‘,’ or ‘)’

    FYI: Line 525 was this $query->set( ‘cat’, ‘-11’ );

    New to coding so any help is appreciated!

    • Bill Erickson says

      Everything looks right to me. Make sure you are using single quotes ' and not backticks `

  60. Stacy says

    Hi Bill & Congrats for the excellent blog.

    It helped me a lot, as i’m taking my first steps on genesis and the way you describe things is really simple, comprehensible and to the point!

    I would like to ask for some help, as iterating through all of these comments didn’t provide me with a solution.

    I have implemented two loops in the home page of my website. The first one is a custom loop, showing the 4 latest posts of the same post category, for example “X”. The second one is implemented with genesis standar loop, showing the latest post οf all categories, including “X”. This loop is using pagination, so users can turn to page 2, 3 etc.

    Is there a way to exclude only the 4 posts of “X” category appearing to the first loop, from the second one? Probably via a function combining category and offset?

    Thanks in advance!

    • Bill Erickson says

      If they were two custom queries, then you could exclude the first four posts by adding 'post__not_in' => $posts in your second query, where $posts is an array of Post IDs.

      Doing this with the main query is more difficult. You’ll need to hook into pre_get_posts to exclude those four posts from every homepage query so the pagination works.

      I haven’t tried it, but something like this in functions.php could do it:

      What we typically do though is make the homepage a static front page with a bunch of custom queries, then have “More Posts” buttons that link to the blog page or category archives. See Lil Luna as an example.

      This way we can feature whatever content makes the most sense (and change it seasonally) without affecting which posts appear in the main blog.

  61. Jairam Swami says


    I need to customize the product category archive template. I want to show parent category products in a child category archive template.

    For details lets assume these categories:-

    business (child of All)
    Work from home (child of business)
    online business (child of business)

    Current if we open the archive for “All” then all post form child category (business, Work from home, online business ) will show and if we open the archive for “Business” then all post form child category (Work from home, online business) will show. And this default functionality of wordpress or woocommerce. But I need that if archive for “online business” (child category) opened

    then show products from this selected category (online business).
    also show post from all parent categories (All,Business) of this category.

  62. Stephen Tapp says

    Hi Bill,

    I’m using a query that filters posts containing featured images. (I have no problem doing that with: ‘key’ => ‘_thumbnail_id’, ‘compare’ => ‘EXISTS’) But I would also like to filter those posts with featured images > 600px wide. I can’t find any reference to such a key. Do you know if one exists for featured image width and/or height? LMK if you need more info. Thanks!

    • Bill Erickson says

      Unfortunately no, WordPress doesn’t store the dimensions of the featured image as metadata. You would need to start storing that information yourself.

      It’s a two-step process:
      1. Start saving the data for new/updated posts. Hook into save_post, then use $image = wp_get_attachment_image_src( get_post_thumbnail_id(), 'full' ); to get an array of image dimensions (width = $image[1]).
      2. Bulk update existing content. This would probably be best done with a custom wp cli script. If you don’t have too many posts, you could run the query directly in the page and hope it doesn’t time out (example). If you have lots of posts, I would paginate it, doing 100 at a time until there are no remaining (example).

      I hope that helps.

Leave A Reply