As I outlined in the introduction to this series, the WP_Query
class has four main elements:
- the arguments for the query, using parameters which will be covered in detail in this series
- the query itself
- the loop, which will output post content, titles or whatever you want to display
- finishing off: closing if and while tags and resetting post data
In this tutorial I'll show you how to use the loop with WP_Query
, including the two main ways to structure your loop and how to use multiple loops.
Where the Loop Fits
Without a loop, nothing will be displayed on your page. After WordPress has run the query, using the arguments you've defined, it then needs to be told what to output from the data that it's fetched. This is where the loop comes in.
So the loop comes after your query, and it uses three tags:
-
if( $query->have_posts() )
checks if there are any posts. -
while( $query->have_posts() )
repeats the loop for each post as long as there are posts to retrieve.
-
$query->the_post()
accesses that specific post.
So this is where the loop fits in the WP_Query
class:
<?php $args = array( // Arguments for your query. ); // Custom query. $query = new WP_Query( $args ); // Check that we have query results. if ( $query->have_posts() ) { // Start looping over the query results. while ( $query->have_posts() ) { $query->the_post(); // Contents of the queried post results go here. } } // Restore original post data. wp_reset_postdata(); ?>
After running the loop, all that's left to do is tidy things up using wp_reset_postdata()
.
Structure of the Loop
The way your loop is structured will depend on what data you want to display from your post. Here's an example loop which outputs the post title, featured image and an excerpt. You'd use a loop like this on an archive page.
<?php $args = array( // Arguments for your query. ); // Custom query. $query = new WP_Query( $args ); // Check that we have query results. if ( $query->have_posts() ) { // Start looping over the query results. while ( $query->have_posts() ) { $query->the_post(); ?> <article id="post-<?php the_ID(); ?>" <?php post_class( 'left' ); ?>> <a href="<?php the_permalink(); ?>" title="<?php the_title_attribute(); ?>"> <?php post_thumbnail( 'thumbnail' );?> </a> <a href="<?php the_permalink(); ?>" title="<?php the_title_attribute(); ?>"> <?php the_title(); ?> </a> <?php the_excerpt(); ?> </article> <?php } } // Restore original post data. wp_reset_postdata(); ?>
This loop displays exactly what I've described above: the featured image, title and excerpt.
Taking the Loop Further: Checking For Content
But sometimes you might want to add a heading before your list of posts, or you might want to enclose them all in a containing element. If you simply added this before your loop, it would be output regardless of whether the query actually returned any data, meaning you could have a heading with nothing beneath it, or some unnecessary markup.
This is very easy to get around by putting the enclosing element or the heading inside your if
tag:
<?php $args = array( // Arguments for your query. ); // Custom query. $query = new WP_Query( $args ); // Check that we have query results. if ( $query->have_posts() ) { echo '<section class="clear">'; echo '<h2>' . __( 'Heading', 'tutsplus' ) . '</h2>'; // Start looping over the query results. while ( $query->have_posts() ) { $query->the_post(); ?> <article id="post-<?php the_ID(); ?>" <?php post_class( 'left' ); ?>> <a href="<?php the_permalink(); ?>" title="<?php the_title_attribute(); ?>"> <?php post_thumbnail( 'thumbnail' );?> </a> <a href="<?php the_permalink(); ?>" title="<?php the_title_attribute(); ?>"> <?php the_title(); ?> </a> <?php the_excerpt(); ?> </article> <?php } echo '</section>'; } // Restore original post data. wp_reset_postdata(); ?>
Here you can see I've checked if there are any posts retrieved by my query, and if there are I've opened a containing element and added a heading.
This is also useful if you want to output the results of your query as a list. Let's say I want to create a list of all posts in a given category. The ul
element isn't inside my loop as it doesn't relate to one specific post, but I only want to output it if there are posts. So I use this:
<?php $args = array( 'category_name' => 'category-slug', 'post_type' => 'post' ); // Custom query. $query = new WP_Query( $args ); // Check that we have query results. if ( $query->have_posts() ) { echo '<ul class="category posts">'; // Start looping over the query results. while ( $query->have_posts() ) { $query->the_post(); ?> <li <?php post_class( 'left' ); ?>> <a href="<?php the_permalink(); ?>" title="<?php the_title_attribute(); ?>"> <?php the_title(); ?> </a> </li> <?php } echo '</ul>'; } // Restore original post data. wp_reset_postdata(); ?>
This checks if the query has fetched any posts, and if so, it opens the ul
element and then runs the loop.
Running Extra Loops
It's important to be aware that while you can use WP_Query
to run more than one loop, you have to reset the post data and start a second instance of WP_Query
to do this. This is because each of your loops will be outputting data based on different arguments.
This example displays the excerpt and featured image for the first post and then just the title of each subsequent post:
<?php // First query arguments. $args1 = array( 'post_type' => 'post', 'posts_per_page' => '1' ); // First custom query. $query1 = new WP_Query( $args1 ); // Check that we have query results. if ( $query1->have_posts() ) { // Start looping over the query results. while ( $query1->have_posts() ) { $query1->the_post(); ?> <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>> <a href="<?php the_permalink(); ?>" title="<?php the_title_attribute(); ?>"> <?php post_thumbnail( 'thumbnail' );?> </a> <a href="<?php the_permalink(); ?>" title="<?php the_title_attribute(); ?>"> <?php the_title(); ?> </a> <?php the_excerpt(); ?> </article> <?php } } // Restore original post data. wp_reset_postdata(); // Second query arguments. $args2 = array( 'offset' => '1', 'post_type' => 'post' ); // Second custom query. $query2 = new WP_Query( $args2 ); // Check that we have query results. if ( $query2->have_posts() ) { echo '<ul class="more-posts">'; // Start looping over the query results. while ( $query2->have_posts() ) { $query2->the_post(); ?> <li <?php post_class(); ?>> <a href="<?php the_permalink(); ?>" title="<?php the_title_attribute(); ?>"> <?php the_title(); ?> </a> </li> <?php } echo '</ul>'; } // Restore original post data. wp_reset_postdata(); ?>
I've used two key arguments here:
-
'posts_per_page' => '1'
, used with the first query, outputs just the most recent post. -
'offset' = '1'
, used with the second query, skips the first post, ensuring it's not repeated in the list below. - As you can see from the code above, the loop is slightly different for each query. The first one outputs the featured image, title and excerpt, while the second checks if the query has posts and if so, opens a
ul
element and encloses each post title in ali
element and a link to its page.
You'll also notice that I used wp_reset_postdata()
after both loops. If I hadn't done this, the second loop would still output data from the first.
Summary
Without a loop, WP_Query
doesn't really do very much. The loop is the code you use to display the data that WordPress has fetched from the database based on your query arguments.
As I've demonstrated, there are a few variations on the loop. A simple loop will just output all posts in the order you've specified in your query arguments (or by date in descending order by default). If you separate if( $query->have_posts() )
and while( $query->have_posts() )
, you can insert additional markup outside your loop, but only if your query has returned data. And finally, by specifying alternative arguments and using wp_reset_postdata()
after each loop, you can use WP_Query
more than once to create multiple loops on your page.
Comments