A beginner’s guide to the WordPress loop

Everyone is familiar with the WordPress loop, even if they don’t know it. It is a method WordPress uses to display posts on any given page. In addition to being performed on most pages you see it offers tons of flexibility by allowing itself to be modified, and that quite extensively, if needed.

The end result of this is the ability to list posts by comment count, show only those with featured images, display only password-protected ones and so on. In this article we’ll take a look at how the default WordPress Loop works, what you can do to modify it and how you can create your very own custom loops.

The Loop

When talking about The Loop (capitalized) we usually mean the main loop on a page. For example, let’s say your front page lists your 10 most recent posts, then shows 3 random posts. Your 10 recent posts are displayed using the main loop — The Loop — your 3 random posts use a custom loop.

The Query

Loops go hand in hand with queries. Loops allow you to display posts by iterating through a list of them, but how does it know which ones to show you from all the hundreds in your database? The answer is The Query.

Similarly to loops, there is a main query which can be called The Query (capitalized) and there may be other custom queries on the page. In our previous example, the 10 recent posts are pulled using the main query while the random posts are retrieved using a custom query. The main query is always generated by WordPress. WordPress looks at the URL you are on, figures out what type of page you are looking at and what posts belong there. Let’s assume you visit the “Design” category on WDD, the URL is http://www.webdesignerdepot.com/category/design/.

WordPress figures out that you are on a category archive, namely the design archive. Instead of retrieving your latest 10 posts, it looks for the latest 10 posts within this category. This information can then be used by the theme to create a loop to display the posts with. As a result, each page you are on will use a different main query. Even if the code of the loop is the same, you will see different content since the query retrieves different posts.

An example loop

Below is an example of The Loop. It can be used on any page such as your home page, archive pages, search pages, etc.

<?php if ( have_posts() ) : ?>
    <?php while ( have_posts() ) : the_post() ?>
    // Post display here
    <?php endwhile ?>
<?php else : ?>
    // Content if there are no posts to show
<?php endif ?>

The two key functions are have_posts() and the_post(). The have_posts() function determines if there are any posts to show. The the_post() function sets up some post data and increments internal pointers which help the have_posts() function determine if there is anything to show.

If there are no posts, we show a sad message informing users that there are no posts here. If there are posts however, we loop through them one by one and display them. Here's a complete block of code that will actually display posts.

<?php if ( have_posts() ) : ?>
    <?php while ( have_posts() ) : the_post() ?>
    <div <?php post_class() ?>>
        <h2><a href='<?php the_permalink() ?>'><?php the_title() ?></a></h2>
        <div class='post-excerpt'>
            <?php the_excerpt() ?>
        </div>
        <div class='post-meta'>
            <time><?php the_time( 'Y-m-d' ) ?></time>
            <?php if ( has_category() ) : ?>  
                <span class='post-categories'><?php the_category( ', ' ) ?></span>
            <?php endif ?>
        </div>
    </div>
    <?php endwhile ?>
<?php else : ?>
    <h2>Sorry, there are no posts here</h2>
    <p>Perhaps you would like to go back to the <a href='<?php echo site_url() ?>'>home page</a>?</p>
<?php endif ?>

A quick reminder: the content is determined by the query. How the content is shown is determined by the loop. This is what gives WordPress (and other server-side language based system) such awesome templating power.

You can use the very same code above to display any post list. On the author page the most recent articles from the author will be in the query. On a search page the most recent results matching the query are retrieved. The loop just goes through the items WordPress retrieves for us.

Modifying the Query

While it is possible to modify the query, it is not recommended for various reasons which I will get into later. That being said, it is a good step on the path to learning more about loops and queries so let’s take a look.

Let’s say you want to exclude a particular category from your home page. If you know the ID of this category you can do so by placing the following code in your index.php, before the loop.

<?php
if ( is_home() ) {
    query_posts( 'cat=-92' );
}
?>

You can use a fair number of parameters which would allow you to modify how many posts are shown, include/exclude tags, categories, show posts from a particular set of authors and so on.

<?php
if ( is_home() ) {
    query_posts( 'cat=92&author=4,11,392,2' );
}
?>

The code above shows only those posts that belong to category 92 (this may be a featured category) and were written by one of four authors, perhaps your top ranking ones. So why is this not recommended, despite being quite easy to pull off? The main reason is that it is inefficient and it breaks paging (unless you fix it by adding the paged parameter). While it seems like you are modifying the original “The Query”, that is not what’s going on.

The original query runs just the same, pulling your latest 10 posts. WordPress then sees your query_posts() function and re-runs the query with your category constraint in place, rendering the initial query wasteful.

Creating our own queries

Creating our own queries won’t solve the problem above, but it’s the next step in learning to do so. Creating queries is useful when you need something extremely specific, or if you want a secondary loop, like our random 3 posts example. A custom query with a custom loop looks something like this:

<?php
    $args = array(
        'post_type' => 'post',
        'post_status' => 'publish',
        'orderby' => 'rand',
        'posts_per_page' => 15
    );

    $random_posts = new WP_Query( $args );
    if( $random_posts->have_posts() ) : 
        while( $random_posts->have_posts() ) : 
?>
    Post Display Here
<?php else : ?>
    You have no posts to show
<?php endif ?>

The first bit down until the if statement defines the query. Using the $args array I specified that I’d like to retrieve 15 random posts which have the “post” post type (regular blog posts) and have been published.

I then used this array to create a new query object. If you don’t know about object oriented PHP just yet, no sweat, just remember the notation!

The rest is a slightly modified version of our initial loop. Instead of using have_posts() and the_post() on their own, we prefix it with the variable we used to store our query in: $random_posts.

There are lots and lots of parameters you can use as arguments. You can pull posts from specific dates or date ranges, you can grab posts based on complex category/tag/taxonomy rules, you can even use data in the metadata table to narrow down the list. Take a look at the WP_Query Class Reference for more information.

Creating our own query is great, but it still doesn’t solve our original problem. If we wanted to exclude categories on the home page we could build our own query to do so, but this wouldn't prevent the main query from being run. The final piece of the puzzle is the pre_get_posts hook which allows us to modify the main query.

Modifying the main query

A note of caution before we continue: modifying the main query can have serious unintended consequences if not done correctly. Experimenting is safe as long as you do it in a well-defined location so you can remove your changes if you see something odd.

In WordPress, hooks are a mechanism that allows us to modify core functionality. They enable us to change the excerpt length, modify the login screen, re-phrase error messages, create custom post types and more. By using the pre_get_posts hook, we can modify the parameters of the main query before it is performed. You’ll need to add the following into your theme’s function file, or even better: into the functions file of a child theme.

add_action( 'pre_get_posts', 'my_exclude_category' );

function my_exclude_category( $query ) {
    if ( $query->is_home() && $query->is_main_query() ) {
        $query->set( 'cat', '-92' );
    }
}

This bit of code achieves what we were trying to do previously — exclude category 92 from our home page — but without the overhead. The main query itself is modified. So, why is this dangerous? Did you notice the

is_home()

and

is_main_query()

functions? These make sure that the query is only modified on the home page and if it is the main query.

Let’s look at an example without the proper safeguards. Perhaps you noticed that an author may have plagarized and you want to remove all traces of his/her posts until the issue is resolved. You do something like this:

add_action( 'pre_get_posts', 'my_exclude_author' );

function my_exclude_author( $query ) {
    $query->set( 'author', '-23' );
}

This does actually exclude the author’s posts from everywhere, but you'll also be astonished to see that they disappear from the back end as well. This is why conditional functions are so important. Take a look at all of them in the WordPress Codex. Here’s what this code should look like in the end:

add_action( 'pre_get_posts', 'my_exclude_author' );

function my_exclude_author( $query ) {
    if( !is_admin() ) {
        $query->set( 'author', '-23' );
    }
}

Conclusion

As you can see, you aren’t constrained at all by WordPress defaults, but you can easily fall into the trap of inefficient code if you are not familiar with how WordPress works.

I don't recommend using query_posts(), except for quick testing and verification. If you must modify the original query, use the pre_get_posts hook. If you need a completely custom solution you should create a custom loop using the WP_Query object.

Don't forget that the brain of your loops is always a query, the loop is just a means to go through the items returned by the query!

Daniel builds plugins, themes and apps – then proceeds to write or talk about them. He's the editor for the WordPress section at Smashing Magazine and he writes for a bunch of other online magazines. When not coding or writing you'll find him playing board games or running with his dog. Drop him a line on Twitter or visit his personal website. More articles by Daniel Pataki