The WordPress Loop & How To Customize The Loop

WordPress makes it extremely easy to create content on your website and blog. All you have to do is log into your WordPress Powered Website, head over to the Add New Post and start blogging away.

The Actual Steps of crafting your blog post are as follows: (I know, you already know this part).

  1. Choose your Title
  2. Add your Content
  3. Images
  4. Choose a category
  5. List of tags
  6. and of course making sure you set the proper metadata in your favorite SEO Plugin Metaboxes
  7. You then hit publish
  8. Share your content with the world via social media and your email subscriber list

People then visit your website and checkout your latest blog post which is typically featured at the top of your blog page that’s designated to showcase your latest posts.

Some See this as Magical and Others Take this Process for Granted

Have you ever wondered how WordPress knows which post should be displayed first and then which post comes next? How does WordPress know which post to display and how to format that post? This magic is handled by a combination of the template file used and the WordPress Loop.

In this article, I will share with you what might seem like a lot of information but in actuality, a whole book can be written on this topic.

How To Easily Copy The Code in This Post?

If you hover over the code snippets in this article, in the upper right corner, there will appear a clipboard image. Click that and all the code will be copied to your clipboard. Then just paste the code in your favorite text editor or IDE.

I also recommend using a local web server on your computer to test all code. Never test any code on a live, production website.

Let’s take a look at a very basic index.php file which is the fallback template file chosen if other more specific template files aren’t available.


<?php
get_header();
if (have_posts()) :
   while (have_posts()) :
      the_post();
         the_content();
   endwhile;
endif;
get_sidebar();
get_footer(); 
?>

Don’t worry, in this post I will go over the code for you.

What Is The WordPress Loop?

The Loop, is PHP Code used by WordPress to display posts, pages, custom Post types, comments and more. It’s a PHP While Loop along with an if conditional statement. This is the gist of the if conditional with the loop.


<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>

Notice the closing PHP tag ?>. After that is where HTML is placed in conjunction with Template Tags which I’ll go more in depth in another article.

The Loop ends with this snippet of code.


<?php endwhile; else : ?>
	<p><?php esc_html_e( 'Sorry, no posts matched your criteria.' ); ?></p>
<?php endif; ?>

You might also find the loop coded slightly different but still correct. Developers have different coding styles.


<?php 
if ( have_posts() ) {
	while ( have_posts() ) {
		the_post(); 
		//
		// Post Content here
		//
	} // end while
} // end if
?>

This Is How It Actually Works

  • It starts off with an if conditional statement which checks if there are any posts to loop over. It’s using the have_posts() function.
  • If there are posts to display, then we start the PHP While Loop. The While Loop will keep on working as long as the condition in between the parenthesis remains true. The condition it’s checking is have_posts() function which is continuously looking if there’s another post to display. It’s keeping count of all posts saved and published in the database.
  • Then there’s the_post() function which iterates the post index in the loop to be viewed.
  • Then there might be some template tags used to generate additional information along with HTML.
  • Then the while loop will come to an end
    <?php endwhile; else : ?>

    and you can have an else conditional to display a statement that no posts were found. It can be as simple as this

    <p><?php esc_html_e( 'Sorry, no posts matched your criteria.' ); ?></p>
  • Finally you have the
    <?php endif; ?>

    which ends the if conditional.

Here’s a list of just a few of the many Template Tags You Can Use inside the loop

  • the_time() – the time or date for the post or page. This can be customized using standard php date function formatting.
  • the_title() – the title of the post or page
  • next_post_link() – a link to the post published chronologically after the current post
  • previous_post_link() – a link to the post published chronologically before the current post
  • the_category() – the category or categories associated with the post or page being viewed
  • the_content() – the main content for a post or page
  • the_excerpt() – the first 55 words of a post’s main content followed by an ellipsis (…) or read more link that goes to the full post. You may also use the “Excerpt” field of a post to customize the length of a particular excerpt.
  • the_ID() – the ID for the post or page
  • the_meta() – the custom fields associated with the post or page
  • the_author() – Displays the author of the post or page

Want to see a complete list of All WordPress Template Tags?

Here’s Some Conditional Tags You Can Use

  • is_home() – Returns true if the current page is the homepage
  • is_single() – Returns true if the page is currently displaying a single post
  • is_page() – Returns true if the page is currently displaying a single page
  • is_category() – Returns true if page or post has the specified category, for example: is_category(‘youtube’)
  • is_tag() – Returns true if a page or post has the specified tag
  • is_author() – Returns true if a specific author is logged in and visiting the site
  • is_search() – Returns true if page is a search results page

There are more conditionals you can use and in another article I will go over more of them. I’m also working on a WordPress Glossary so make sure to Bookmark this website.

More Examples of the WordPress Loop

Sounds easy right? Well there are many ways to customize the WordPress loop to output your content. You can create a variety of template files with specific formatting. You can also use the loop in various locations on your website like the sidebars, footer areas or even have multiple loops in the main section of your content area.

This is a more complex example of an index.php file (albeit not overly complex).
This Code is from DevWP which is my WordPress Theme Development Tutorial Series on YouTube.

DevWP WordPress Training Theme


<?php
/**
 * The main template file
 *
 * This is the most generic template file in a WordPress theme
 * and one of the two required files for a theme (the other being style.css).
 * It is used to display a page when nothing more specific matches a query.
 * E.g., it puts together the home page when no home.php file exists.
 *
 * @link https://developer.wordpress.org/themes/basics/template-hierarchy/
 *
 * @package DevWP
 */

get_header();
?>

<div id="primary" class="content-area col-sm-8">
    <main id="main" class="site-main">

    <?php
if (have_posts()):
    if (is_home() && !is_front_page()):
?>
               <header>
                    <h1 class="page-title screen-reader-text">
                        <?php
        single_post_title();
?>
                   </h1>
                </header>

            <?php
    endif;
    
    /* Start the Loop */
    while (have_posts()):
        the_post();
        
        /*
         * Include the Post-Format-specific template for the content.
         * If you want to override this in a child theme, then include a file
         * called content-___.php (where ___ is the Post Format name) and that will be used instead.
         */
        get_template_part('template-parts/content', get_post_format());
    endwhile;
    
    devwp_post_nav();
else:
    get_template_part('template-parts/content', 'none');
endif;
?>

    </main>
    <!-- #main -->
</div> <!-- #primary -->

    <div class="col-sm-4">
      <?php
get_sidebar();
?>
   </div>
<?php
get_footer();

Let’s Go Over Some of the Code

As you can see, we start off at the top of the file with our opening php tag followed by a comment section and then we use the get_header(); function and close off the php tag.

Then we jump into some HTML and then back into PHP. You will find that most developers will jump in and out of PHP and HTML versus just echoing out the html. Especially for longer segments of code.

Now for the WordPress Loop In Action

  • As mentioned before, we start off with our if conditional to check if there are any posts.
    if ( have_posts() ):
  • But in this example, we then check if the query is for the blog homepage and also check the front_page setting set in your Settings->Reading section of your website. This is done so it can properly handle the output of the page title. That’s this line of code
    if ( is_home() && ! is_front_page() ): ?>

    If that condition is true, then we output this section

    <header>
       <h1 class="page-title screen-reader-text">
    	<?php single_post_title(); ?>
       </h1>
    </header>

    followed by the endif for the nested if conditional

    <?php endif;
  • Then we actually start the loop with this line of code
     while ( have_posts() ): the_post();
  • and we use the get_template_part() function to include another template which is good practice to keep your code modular.
    get_template_part( 'template-parts/content', get_post_format() );

    and then we end the while loop with

    endwhile;
  • This next line of code is for our custom navigation function
    devwp_post_nav();
  • Next we put in our else condition like this
    else : get_template_part( 'template-parts/content', 'none' );
  • We then end the first if conditional statement like this
     endif; ?> 
  • The rest of the code completes the page layout and calls in the sidebar.

As you can see, the loop can get a little more complicated. But we aren’t done yet. Now let’s bend the WordPress loop to do as we wish.

Customizing the WordPress Loop

We already covered the default loop so now we will go over more advanced ways of working with the loop.

There are 4 ways to work with the loop.

  • query_posts()
  • WP_Query()
  • get_posts()
  • pre_get_post

Using query_posts() To Loop

query_posts uses WP_Query and is not recommended because it directly alters the main loop by changing the variables of the global variable $wp_query.

With that out of the way, here’s a very general example.


<?php query_posts( 'showposts=3&post_type=post' ); ?>

 <?php 
   if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
	<h2><?php the_title(); ?></h2>
	<?php the_post_thumbnail(); ?>
	<?php the_excerpt(); ?>
	<?php endwhile; else: ?>
	<p>Sorry, there are no posts to display</p>
	<?php endif; ?>
<hr>
	<?php wp_reset_query(); ?>

	<?php query_posts( 'showposts=1&offset=3&post_type=post&ignore_sticky_posts=1' ); ?>

	<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>

	<h2><?php the_title(); ?></h2>
        <?php the_post_thumbnail(); ?>
	<?php the_excerpt(); ?>
	  <?php endwhile; else: ?>
	<p>Sorry, there are no posts to display</p>

	  <?php endif; ?>
<?php wp_reset_query(); ?>

Let’s Go Over The Code
The first thing we do is use the query_post() function like this:

<?php query_posts( 'showposts=3&post_type=post' ); ?>

Inside the function, we are saying we want to show 3 posts from the post post-type.

Then we go into our loop

 if ( have_posts() ) : while ( have_posts() ) : the_post(); ?> 

and use a few template tags to retrieve some information about each post.


<h2><?php the_title(); ?></h2>
<?php the_post_thumbnail(); ?>
<?php the_excerpt(); ?>

We then use endwhile followed by an else to display a statement that no there are no posts and then endif like so:


<?php endwhile; else: ?>
	<p>Sorry, there are no posts to display</p>
<?php endif; ?>

Then we reset the query so we can reuse it again like so:


<?php wp_reset_query(); ?>

Once that part is done, we jump into another loop with some more requests being made:


<?php query_posts( 'showposts=1&offset=3&post_type=post&ignore_sticky_posts=1' ); ?>

In the code above we are offsetting by 1, requesting 3 posts from the post post-type and ignoring the sticky post.

WP_Query() Function – The Preferred Way To Customize The Loop

This is the one to use that will cover the vast majority of your use cases.

Note: There are two sections to Copy to your text editor

With it you would first start off creating an $args variable, then another variable which I’ll call $the_query for the actual query. Here’s how that looks.

Note: this snippet goes before the loop.


<?php 

	$args = array( 
                'post_type'     => 'post',
		'category_name' => 'markup',
                'posts_per_page'  => 3,
                'post__not_in'  => get_option('sticky_posts')
	);
	$the_query = new WP_Query( $args );

?>

The $args variable is assigned the value of an associative array. The array has key value pairs which are arguments for the query to process.

Then you would start the process of checking for posts and the loop like this. What you’ll notice is that now we are using the $the_query with arrow notation -> before the have_posts and the_post functions.


<?php
if ($the_query->have_posts()):
    while ($the_query->have_posts()):
        $the_query->the_post();
?>

<h2><?php
        the_title();
?></h2>
<?php
        the_post_thumbnail();
?>            
<?php
        the_excerpt();
?>

<?php
    endwhile;
    wp_reset_postdata();
else:
?>

<p><?php
    esc_html_e('Sorry, no posts matched your criteria.');
?></p>

<?php
endif;
?>

Instead of breaking down the code, take a good look at it and see if you can figure it out. If you’ve been following along, you should be able to see what the code is doing. Is the Challenge Accepted?

Multiple Loops with WP_Query

Now you can really do some cool things with the loop via the WP_Query() function. You can have different parts of the same page display different posts based on categories, tags, ID’s, author, post types and more. It’s made for you to decide how you want the data to be displayed. I should also mention that you will need to code the HTML & CSS used for the various sections as well. Also, if you want to use different widgetized areas, then you will have to code that up as well.

Take a look at how we use two different queries to present different posts based on category name.


<?php
$query1 = new WP_Query('posts_per_page=5&cat=29');

if ($query1->have_posts()) {
    while ($query1->have_posts()) {
        $query1->the_post();
?>
                   <h2><?php
        the_title();
?>
                   </h2>
                    <?php
        the_post_thumbnail();
?>
                   <?php
        the_excerpt();
?>
                  <?php
        the_shortlink();
?>
                   <?php
        the_category();
?>
                   <?php
        the_tags();
?>
      <p>The ID is: <?php
        the_ID();
?> </p>
        <hr>
                    <?php
    }
    wp_reset_postdata();
}

$query2 = new WP_Query(array(
    'tag_id' => 134
));

if ($query2->have_posts()) {
    while ($query2->have_posts()) {
        $query2->the_post();
?>
     <h2><?php
        the_title();
?></h2>
                    <?php
        the_post_thumbnail();
?>
                   <?php
        the_excerpt();
?>
<?php
        the_shortlink();
?>
                   <?php
        the_category();
?>
                   <?php
        the_tags();
?>
       <p>The ID is: <?php
        the_ID();
       ?> </p>
     <hr>
                    <?php
    }
    
    wp_reset_postdata();
}
?>

Notice that we are coding this a little differently. Instead of creating an $args variable, we are passing the arguments directly inside the WP_Query like so.


$query1 = new WP_Query('posts_per_page=5&cat=29');

and for the second query further down the code snippet we code it this way.


$query2 = new WP_Query(array(
    'tag_id' => 134
));

You can code it either way. You will find for just a few arguments, you would use this approach versus if you have multiple arguments you want to query for, then you should use the $args version in the first section above.

Query based on Post Type

Let’s say you have various custom post types or just want to display any of the general post types in specified areas. You would use code like this.


$query = new WP_Query( array( 'post_type' => 'page' ) );

In the code above, we create a new variable called $query and assign it the value of new WP_Query with an associative array with the key => value pair of post_type => page. If you wanted to display post or any other post type, including custom post types, you would change out page for that other post type.

You can also query for multiple categories or tags at the same time. This is useful if you have a lot of categories on your website but need a specific query just for say a few categories. Look at the example below.


$args = array(
	'cat' => array( '30,19' )
);
$the_query = new WP_Query( $args );

Using get_posts() To Loop

So far we’ve covered query_posts() and WP_Query. Now it’s time to try out get_posts(). While WP_Query is the preferred way to query for data, get_posts() is ideal for smaller queries. Try this out in your sidebar.php file. Let’s look at an example.


<?php
global $post;
$args         = array(
    'category' => -29,
    'numberposts' => 10
);
$latest_posts = get_posts($args);
foreach ($latest_posts as $post):
    setup_postdata($post);
    the_title();
    the_post_thumbnail();
    the_excerpt();
endforeach;
wp_reset_postdata();
?>

The first thing we do is call the global $post variable, then we set up our parameters via the $args variable. In this example, I’m choosing to retrieve 10 posts but I’m also choosing to exclude category 29.

Then I create the $latest_posts variable and assign the value of the get_posts function with the $args. We then use a foreach loop to loop through the $latest_posts as individual $post.

Some post-related data is not available to get_posts by default, such as post content through the_content(), or the numeric ID. This is resolved by calling an internal function setup_postdata(), with the $post array as its argument:

Then we use some template tags to pull in the title, the thumbnail and the excerpt. Followed by ending the foreach and reset the postdata.

Using pre_get_post to Customize the WordPress Loop

The pre_get_posts is a hook that enables you to customize the loop. This will make changes directly to the query object so there’s no need for global variables or to return any values. This is not an ideal solution for individual pages, archives and it might cause issues with some template tags like the is_front_page, is_page, is_singular and others.

It’s also recommended to use conditional tags along with this so you can ensure you get the results you want, where you want. The code snippet below would be placed in your functions.php file.


function custom_home_query( $query ) {
    if ( $query->is_home() && $query->is_main_query() ) {
        $query->set( 'cat', '29' );
    }
}
add_action( 'pre_get_posts', 'custom_home_query' );

Let’s Go Over The Code
First thing we do is create a custom function called custom_home_query and pass it the $query parameter. Then we create an if conditional checking if the $query is_home and is_main_query. If this is true, then we are choosing to only show posts from the category 29. After the double closing curly braces, we use the add_action with the pre_get_posts hook and the name of our custom function.

You can also exclude a category by instead of the positive ID of 29 for my particular category, you can just say -29 and that category will be excluded. You can also exclude or include multiple categories by using a comma to separate each one.

If you want to include any custom post types you might have in the search results, you would use code like this.


function cpt_search_filter($query) {
  if ( !is_admin() && $query->is_main_query() ) {
    if ($query->is_search) {
      $query->set('post_type', array( 'post', 'listing' ) );
    }
  }
}

add_action('pre_get_posts','cpt_search_filter');

In the code above, we are creating our custom function called cpt_search_filter and then we have an if conditional checking to make sure we aren’t in the admin section aka back-end of our website and that it’s the main query. If that is true, then we check if the query is for search and then we set the query to include our custom post type which in this case is listing. We then finish off with our add_action.

Single and Page Template loops

Over the course of this tutorial, I’ve shown you how to work with the loop on a Blog Page that displays multiple posts. Now let’s take a quick look at a single post or page.

single.php with content.php

This next code snippet is from the DevWP Training Theme and it’s two snippets to look at. The single.php file and the content.php file.


<?php
	/**
	 * The template for displaying all single posts
	 *
	 * @link https://developer.wordpress.org/themes/basics/template-hierarchy/#single-post
	 *
	 * @package DevWP
	 */

	get_header();
?>

	<div id="primary" class="content-area col-sm-8">
		<main id="main" class="site-main">

		<?php
			while ( have_posts() ): the_post();

				get_template_part( 'template-parts/content', get_post_type() );

				devwp_post_nav();

				// If comments are open or we have at least one comment, load up the comment template.
				if ( comments_open() || get_comments_number() ):
					comments_template();
				endif;

			endwhile; // End of the loop.
		?>

		</main>
		<!-- #main -->
	</div> <!-- #primary -->

	<div class="col-sm-4">
	  <?php get_sidebar(); ?>
	</div>
<?php
	get_footer();

Then there’s the code in the content.php file.


<?php
	/**
	 * Template part for displaying posts
	 *
	 * @link https://developer.wordpress.org/themes/basics/template-hierarchy/
	 *
	 * @package DevWP
	 */

?>

<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
	<header class="entry-header">
	  <?php devwp_post_thumbnail(); ?>
	  <?php
		  if ( is_singular() ):
			  the_title( '<h1 class="entry-title">', '</h1>' );
		  else :
			  the_title( '<h2 class="entry-title"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></h2>' );
		  endif;

		  if ( 'post' === get_post_type() ): ?>
						<div class="entry-meta">
				<?php
					devwp_posted_on();
					devwp_posted_by();
				?>
						</div> <!-- .entry-meta -->
		  <?php
		  endif;
	  ?>
	</header>
	<!-- .entry-header -->

	<div class="entry-content">
	  <?php

		  if ( is_single() ) {
			  the_content( sprintf(
				  wp_kses(
				  /* translators: %s: Name of current post. Only visible to screen readers */
					  __( 'Continue reading<span class="screen-reader-text"> "%s"</span>', 'devwp' ),
					  array(
						  'span' => array(
							  'class' => array(),
						  ),
					  )
				  ),
				  get_the_title()
			  ) );
		  } else {
			  the_excerpt();
		  }

		  wp_link_pages( array(
			  'before'           => '<div class="page-links">' . esc_html__( 'Pages:', 'devwp' ),
			  'after'            => '</div>',
		  ) );
	  ?>
	</div>
	<!-- .entry-content -->

	<footer class="entry-footer">
	  <?php devwp_entry_footer(); ?>
	</footer>
	<!-- .entry-footer -->
</article> <!-- #post-<?php the_ID(); ?> -->
<hr>

page.php with content-page.php

This next code also comes from the DevWP WordPress Training Theme


<?php
	/**
	 * The template for displaying all pages
	 *
	 * This is the template that displays all pages by default.
	 * Please note that this is the WordPress construct of pages
	 * and that other 'pages' on your WordPress site may use a
	 * different template.
	 *
	 * @link https://developer.wordpress.org/themes/basics/template-hierarchy/
	 *
	 * @package DevWP
	 */

	get_header();
?>

	<div id="primary" class="content-area col-sm-8">
		<main id="main" class="site-main">

		<?php
			while ( have_posts() ): the_post();

				get_template_part( 'template-parts/content', 'page' );

				// If comments are open or we have at least one comment, load up the comment template.
				if ( comments_open() || get_comments_number() ):
					comments_template();
				endif;

			endwhile; // End of the loop.
		?>

		</main>
		<!-- #main -->
	</div> <!-- #primary -->

	<div class="col-sm-4">
	  <?php get_sidebar(); ?>
	</div>
<?php
	get_footer();

and here’s the content-page.php code


<?php
	/**
	 * Template part for displaying page content in page.php
	 *
	 * @link https://developer.wordpress.org/themes/basics/template-hierarchy/
	 *
	 * @package DevWP
	 */

?>

<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
	<header class="entry-header">
	  <?php devwp_post_thumbnail(); ?>

	  <?php the_title( '<h1 class="entry-title">', '</h1>' ); ?>
	</header>
	<!-- .entry-header -->

	<div class="entry-content">
	  <?php
		  the_content();

		  wp_link_pages( array(
			  'before' => '<div class="page-links">' . esc_html__( 'Pages:', 'devwp' ),
			  'after'  => '</div>',
		  ) );
	  ?>
	</div>
	<!-- .entry-content -->

	<?php if ( get_edit_post_link() ) : ?>
			<footer class="entry-footer">
		  <?php
			  edit_post_link(
				  sprintf(
					  wp_kses(
					  /* translators: %s: Name of current post. Only visible to screen readers */
						  __( 'Edit <span class="screen-reader-text">%s</span>', 'devwp' ),
						  array(
							  'span' => array(
								  'class' => array(),
							  ),
						  )
					  ),
					  get_the_title()
				  ),
				  '<span class="edit-link">',
				  '</span>'
			  );
		  ?>
			</footer>
			<!-- .entry-footer -->
	<?php endif; ?>
</article> <!-- #post-<?php the_ID(); ?> -->

Besides using two files for each which is part of keeping your code modular, the main difference is this line which can be found in both which is used to display the actual content.


the_content();

the_content() function is handled a little differently in each file but ultimately produce the full content versus just an excerpt.

Resetting The WordPress Loop

Throughout this tutorial, I demonstrated a few ways of resetting the loop. This is a necessary step so we can reuse the loop. There are 3 ways to reset the loop and they include:

  • wp_reset_postdata() Use this function to restore the global $post variable of the main query loop after a secondary query loop using new WP_Query. It restores the $post variable to the current post in the main query.
  • wp_reset_query() restores the $wp_query and global post data to the original main query. This function should be called after query_posts().
  • rewind_posts() Rewinds the loop posts in order to re-use the same query in different locations on a page.

In Conclusion

Learning How to work with the WordPress Loop is extremely important for theme and plugin developers. You can really customize the loop to suit your needs and get the results you want.

Hopefully you found this tutorial helpful, if you did, feel free to share it with others and also make sure to watch the video for a more visual overview of the WordPress Loop.

Get DevWP - WordPress Development Training Theme

Find Me On