3 Ways to Create Loop in WordPress – WP_Query{} get_posts() query_posts()

An article for beginners and those who are already a little familiar with WordPress, which should debunk all the myths about the different types of loops in WordPress.

I've written about the WordPress loop and mentioned in passing the different loop variants in the wp_query{} descriptions. In this article I will take the next step and tell you about 3 variants of loops for outputting posts and about pros and cons of each of them.

Proper usage of several loops on the page will give you an opportunity to output blocks with needed posts, sort them in necessary order and not to worry about breaking logical structure of the page and "catching" various bugs.

Possible variants of loops:

  1. Standard loop and loop based on query_posts().
  2. Additional loop based on WP_Query().
  3. Additional loop based on get_posts().

Each of these options is convenient to use in different situations. You don't need to study a different manual to use each variant, because they all work with the same parameters, you just need to understand how and where to use them.

For a better understanding and visual perception of how the query functions work, study this diagram:

How query functions work in WordPress

1) Standard Loop and loop based on query_posts()

I've combined the 2 kinds of loops (with query_posts() and starting with if( have_posts() ), because technically they are exactly the same.

Let's remember what a standard WordPress Loop looks like:

// check if there are posts in the global query - $wp_query variable
if( have_posts() ){
	// go through all available posts and display them
	while( have_posts() ){

		<div <?php post_class(); ?> id="post-<?php the_ID(); ?>">
			<h1><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h1>
			<?php the_content(); ?>


	<div class="navigation">
		<div class="next-posts"><?php next_posts_link(); ?></div>
		<div class="prev-posts"><?php previous_posts_link(); ?></div>

// no posts
else {
	echo "<h2>No entries.</h2>";

This code can be found in the files index.php, category.php etc. These files are responsible for displaying a list of posts on the page. This loop goes through the posts that appear on the page one by one and during the loop, using Template Tags (intended to be used inside the loop), we can display various post data (title, text, metadata, etc.).

Note: in the standard Loop, we don't specify any arguments to retrieve posts, but immediately start the loop with if( have_posts() ){ while( have_posts() ){... This tells us that the data already exists and just needs to be processed and displayed.

'Already existing' data is located in the global $wp_query variable and for each page WordPress is determined automatically, that is, WordPress makes a query in the database in advance, based on what page is currently displayed (category, tag, article, static page, etc.) and the result of the query is written to $wp_query and then this data uses to create a loop. Interestingly, such a query is done by the query_posts() function, which we'll break down below.

The normal WordPress loop is used for basic WP pages (categories, tags, archives by dateб etx.).

A loop based on query_posts()

query_posts() allows you to modify the basic query and output posts we want.

Option 1

We can change the basic query (make another query and overwrite the data of the previous query) and, for example, cut out unnecessary categories from the output or change the number of output posts, sort order, etc.

global $query_string; // basic query parameters

// basic query + your parameters
query_posts( $query_string .'&cat=-6,-9&order=ASC&posts_per_page=20' );


wp_reset_query(); // reset current page query data to default

In this example we created a new database query using the parameters of the basic query + our own parameters: we excluded categories 6 and 9 (cat=-6, -9) and sorted posts (order=ASC) and output 20 posts per page instead of the preset 10 (posts_per_page=20).

See the function query_posts() for a complete list of parameters that can be used to generate the output we need.

The advantage of this change is that if we, for example, change the number of output posts on the page from 10 (the default) to 20, then pagination on the page will automatically adjust to this change because query_posts() changes data in the global $wp_query variable and pagination is built based on this data. This is just one example, showing that query_posts() and the behavior of other functions on the page are interrelated.

It is recommended to modify WP's basic query through the pre_get_posts filter rather than through query_posts().

Option 2

You can not use the parameters of the base query (query_string), but completely rewrite the base query:

query_posts( 'cat=-6,-9&order=ASC' );

However, this approach will essentially erase the base query and create a new one, which may not be composed correctly, so it is not recommended to completely rewrite the base query. It is better to try to solve the problem in some other way.

Why we need wp_reset_query()

You need to reset the modified query when using query_posts(), because query_posts() rewrites the global $wp_query variable, which is responsible for some page properties. Let's look at an example.

Suppose we need to display only post 9 (post ID) on category page 6 (category ID):

query_posts( 'p=9' );

if( have_posts() ){
	while( have_posts() ){

else {
	echo 'No entries.';


In this example, we did not reset the query and the query_posts() function rewrote the global variable $wp_query. Now, when we check which page it is (which is the category page: is_category() == true), we see that it is no longer a category page at all, but a post page: is_single() == true. So the next code will return "This is the post page," when in fact it is the category page:

if( is_category() ) echo 'This is the category page';    // will not work
if( is_single() )   echo 'This is the page of the post'; // it will work

A mistake that can create a lot of headaches later on.

When to use query_posts()?

When you need to slightly modify the basic WordPress query. Ideally:

  • to exclude a category/tag (e.g. on the home page).
  • change the sorting direction
  • limit the number of posts you can display.
  • exclude certain posts from a category/tag.
  • etc.

And again, for such tasks it is better to use pre_get_posts filter.

Never use query_posts() to create multiple loops on the same page, to output a list of posts to the sidebar; to create additional output for posts; etc.. Use the get_posts() based loops for these purposes. They use the same parameters.

2) A loop based on WP_Query()

To display posts unrelated to the current page or to create multiple (additional) loops, you can use loops based on the WP_Query class. It looks similar to query_posts() loop. For WP_Query the same parameters are used as for query_posts().

Interestingly, WP_Query class is the core of query_posts() and get_posts(), which means that both of these functions are based on this class.

Example loop: let's output all posts from category 9:

// specify category 9 and turn off pagination
$query = new WP_Query( 'cat=9&nopaging=1' );
if( $query->have_posts() ){
	while( $query->have_posts() ){
		<h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
		<?php the_content(); ?>
	wp_reset_postdata(); // reset the $post variable
	echo 'There are no entries.';

An example of creating multiple loops based on WP_Query():

// Loop 1
$query1 = new WP_Query('cat=-1&nopaging=1'); // all posts except category 1
while( $query1->have_posts() ){

	// posts output

// Loop 2
$query2 = new WP_Query('cat=-2&nopaging=1'); // all posts except category 2
while( $query2->have_posts() ){

	// posts output

// Loop 3
$query3 = new WP_Query('cat=-3&nopaging=1'); // all posts except category 3
while( $query3->have_posts() ){

	// posts output

The feature of WP_Query{} loops is that we create a new $query object which is in no way related to the global $wp_query object and so we do not break the structure of the current page in any way.

Also, we can use the new object for other purposes, not only to output posts, but also for various kinds of checks: for example:

  • posts of what page type are used in this new object.
  • we can find out the total number of posts that satisfy the query ($query->found_posts)
  • etc.

This data can be useful if you create additional pagination queries or something else.

Why use wp_reset_postdata()?

The global variable $post stores the data of the current post (if current page is a post page, then the data of current page post). When the $query->the_post() part of the code is triggered, the data of the current post in the loop is written to the $post variable and the data of the last post from that loop remain in that variable at the end of the loop, but we need the global $post always contain the data of the current page post. It means that before loop's execution $post->ID = current page post ID, say it's 10, but after loop execution the same variable $post->ID = 56, say 56 it's last post ID in the loop, but we need it to be equal to 10 as before.

wp_reset_postdata() is used just to return the correct data into the global $post variable.

When to use WP_Query()?

If you need to display entries without affecting the main loop (let's say sidebar entries); if you need to create multiple queries. In general, I don't see any advantage of WP_Query() loops over loops using get_posts() and so I recommend using get_posts() instead of WP_Query().

3) A loops based on get_posts()

The most convenient way to output the needed posts in the right order is to output them with get_posts(). get_posts() is most often better suited to your task, for example:

  • you need to display the 10 most recent posts in the sidebar.
  • You need to output 10 random posts in the footer.
  • you need to output all pictures attached to the post.
  • you need to output posts with a certain meta-field.

The get_posts() as well as query_posts() work based on the WP_Query{} class, so the parameters passed are the same.

#1. An example loop based on get_posts().

Let's output 5 posts from category 9:

global $post; // not necessary

// 5 posts from category 9
$myposts = get_posts( array(
	'category' => 9
) );

foreach( $myposts as $post ){
	setup_postdata( $post );

	// standard post output

wp_reset_postdata(); // reset the $post variable

The code will print exactly 5 posts, even though we only specified the category number in the arguments. This is because get_posts() has pre-defined parameters that we have to keep in mind. For example, if we want to output all entries from category 9, we should add another parameter 'nopaging' => 1 or 'posts_per_page' => -1 (makes no difference).

When to use get_posts()

Always when you just want to get posts anywhere in the template. When you want to create multiple loops. Since get_posts() takes the same parameters as query_posts(), it is very convenient to use it to output posts according to a variety of criteria.


Where and which of the 3 loop variant to use:

  • get_posts() - if you want to output posts from the Database. Can be used as many times as you want on a page.

  • query_posts() - if you need to change/correct the standard output of posts on WordPress pages. Can be used once per page.

  • WP_Query() - in all other cases when query_posts() and get_posts() fail. The WP_Query() class is the core of query_posts() and get_posts() and can be used for any complex output cases.

Remember that the parameters are the same for any type of loop variant and described in wp_query{} class.