WordPress get_posts is a powerful function allowing developers to retrieve pieces of content from the WordPress database. You can specify in the finest detail which posts, pages, and custom post types you’re looking for, get your custom result set, then filter and order the items like a PHP/MySQL ninja.

But don’t be scared if you’re not a PHP professional, there are countless PHP tutorials you can watch or read and learn the language. You just need a little knowledge of PHP to create custom lists of posts to display on your website as the get_posts function keeps an array of parameters allowing to build simple or advanced queries.

Using WordPress get_posts is a two-step process:

  • First, you have to build your custom query. Actually, it won’t look like a MySQL query, and you won’t write any SELECT statement. You just need to define an array of parameters and pass it to the get_posts function. WordPress converts that array into a real and secure MySQL query, runs it against the database, and returns an array of posts.
  • Second, you have to traverse the result set returned by get_posts with a foreach cycle.

That being said, in this post, we will first dive into the key concepts mentioned above, specifically how get_posts works, how to build a custom query, and how to show data on the front site.
Then, I will provide a real-world example with a snippet of code you can grab, edit and use on your staging environment for your tests and development.

Note: We usually differentiate between posts, pages, and custom post types. In this article, we use the term ‘posts’ for regular blog posts as well as for pages and custom post types. All these post types are stored in the ‘wp_posts’ table of the database. The main difference between post types is in the value of the ‘post_type’ field. From a developer’s perspective, posts, pages, and custom post types are all posts.

Introduction to the WordPress get_posts Function

The Codex describes the get_posts function as follows:

Retrieves an array of the latest posts, or posts matching the given criteria.

We can use get_posts this way:

$args = array(
	'numberposts'	=> 20,
	'category'		=> 4
);
$my_posts = get_posts( $args );

if( ! empty( $my_posts ) ){
	$output = '<ul>';
	foreach ( $my_posts as $p ){
		$output .= '<li><a href="' . get_permalink( $p->ID ) . '">' 
		. $p->post_title . '</a></li>';
	}
	$output .= '</ul>';
}

The function above retrieves the latest 20 blog posts in the specified category (by default the 'post_type' is 'post') and returns an array of $post objects. You can iterate over the array to display the posts on the screen. It’s pretty easy, right?

get_posts uses WP_Query to retrieve post items, and it keeps an array of the same parameters available in WP_Query (with few exceptions). So we have a huge list of variables we can use to build our custom queries. These parameters are grouped in the following 15 categories:

  • Author Parameters
  • Category Parameters
  • Tag Parameters
  • Taxonomy Parameters
  • Search Parameters
  • Post & Page Parameters
  • Password Parameters
  • Post Type Parameters
  • Order & Orderby Parameters
  • Date Parameters
  • Custom Field (post meta) Parameters
  • Permission Parameters
  • Mime Type Parameters
  • Caching Parameters
  • Return Fields Parameter

A quick look at the list above can give you an idea of the variety of the custom queries you can build and run against the WordPress database. So, let’s dive deeper into query parameters and start to build our lists of posts.

How to Build Queries With WordPress get_posts

Each category of parameters relates to the same piece of information. For example, we can build a query to retrieve posts from the specified author(s) or excluding the specified author(s), defining the author by ID or nicename. The same way, we can build queries fetching posts by category, tag, taxonomy, date, custom fields and even more.

How To Use Parameters to Build Simple Queries

Many parameters can be used in a quite similar way, regardless of the category they belong to. For example, the following parameters allow to query the database by post author(s):

  • author (int) – author ID
  • author_name (string) – author’s user_nicename
  • author__in (array) – an array of multiple authors’ IDs
  • author__not_in (array) – an array of multiple authors’ IDs to be excluded from the result set

How can we use these parameters?

In the following example, the parameter 'author' specifies that we want the most recent blog posts written by the author with ID = 1:

$my_posts = get_posts( array( 'author' => 1 ) );

The same ‘author’ parameter allows to query the database in different ways:

// return an array of posts from specific authors
$my_posts = get_posts( array( 'author' => '1,5,12' ) );
// return an array of posts excluding the specified author
$my_posts = get_posts( array( 'author' => -1 ) );

So, depending on the parameter’s value, you’ll have a result set with posts from a single author (integer), from multiple authors (a list of comma-separated values) or excluding an author (negative values).

Other parameters provide additional flexibility. For example, the following call to get_posts returns an array of the latest blog posts from multiple authors:

// return an array of posts from multiple authors
$my_posts = get_posts( array( 'author__in' => array( 1, 5, 12 ) ) );

And we can also exclude multiple authors:

// return an array of posts from multiple authors
$my_posts = get_posts( array( 'author__not_in' => array( 1, 5, 12 ) ) );

Similarly, we can use category params, tag params, post type params, with some specific differences. See, as an example, category params:

  • cat (int)
  • category_name (string)
  • category__and (array)
  • category__in (array)
  • category__not_in (array)

Anyway, not all parameters are as easy to use as these parameters. Additionally, we can use category params, post type params, mime type params, etc. all in a single query. This means that we have granular control over the items in the result set, and we can build more advanced queries based on post types, custom taxonomies and custom fields altogether.

So, let’s dive deeper!

How To Build Advanced Queries in WordPress

Let’s move a step forward with a more advanced query based on custom post types and custom taxonomies. Say you have the following post type:

name: book
taxonomy name: book_category, book_author
support for: title, editor, thumbnail, excerpt, custom-fields

Custom Post Types and Custom Taxonomies

Suppose you want a list of the most recent books in the specified book_category custom taxonomy. Here is the array of arguments:

$args = array(
	'post_type'		=> 'book',
	'tax_query'		=> array(
		array(
			'taxonomy'	=> 'book_category',
			'field'		=> 'slug',
			'terms'		=> 'sci-fi'
		)
	),
);

The arguments above simply tell WordPress to retrieve all books in 'sci-fi' 'book_category'.

The 'tax_query' parameter takes an array of argument arrays (i.e. an array of arrays). These nested arrays allow to build very complex queries based on multiple taxonomies, as shown in the example below:

$args = array(
	'numberposts'	=> 10,
	'post_type'		=> 'book',
	'relation'		=> 'AND',
	'tax_query'		=> array(
		array(
			'taxonomy'	=> 'book_category',
			'field'		=> 'slug',
			'terms'		=> 'sci-fi'
		),
		array(
			'taxonomy'	=> 'book_author',
			'field'		=> 'term_id',
			'terms'		=> 22
		)
	)
);

These parameters allow us to retrieve a list of the latest 10 'book' post types in the 'sci-fi' 'book_category', written by the 'book_author' with ID #22. The 'relation' parameter sets the logical relationship between each taxonomy listed in 'tax_query'. Above we set its value to AND because we need to retrieve all books belonging to the 'sci-fi' category AND written by author #22.

How to Build Meta Queries Using Custom Field Parameters

Occasionally, you may need to build lists of posts based on a specific custom field key and/or value.

$args = array(
	'meta_key'		=> 'cover',
	'meta_value'	=> 'paperback',
	'meta_compare'	=> '='
);

These parameters allow us to retrieve all posts by custom field key and value. 'meta_compare' sets the operator required to test the value of the 'meta_value' parameter. Here 'meta_value' is '=', which is also the default value.

Available values are '=', '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'NOT EXISTS', 'REGEXP', 'NOT REGEXP' or 'RLIKE'.

This is a pretty easy example, but we can build more advanced queries. In the next example, we query the database for fantasy books published after 2010:

$args = array(
	'post_type'		=> 'book',
	'meta_key'		=> 'year_published',
	'meta_value_num'	=> 2010,
	'meta_compare'	=> '>',
	'tax_query'		=> array(
		array(
			'taxonomy'	=> 'book_category',
			'field'		=> 'slug'
			'terms'		=> 'fantasy'
		)
	)
);

And we can go even further. In the next example we are mixing up a post type with a custom taxonomy and two custom fields:

$args = array(
	'post_type'		=> 'book',
	'tax_query'		=> array(
		array(
			'taxonomy'	=> 'book_category',
			'field'		=> 'slug'
			'terms'		=> array( 'fantasy' )
		)
	),
	'meta_query'	=> array(
		'relation'		=> 'AND',
		array(
			'key'		=> 'year_published',
			'value'		=> 2010,
			'type'		=> 'numeric',
			'compare'	=> '>',
		),
		array(
			'key'		=> 'price',
			'value'		=> array( 10, 25 ),
			'type'		=> 'numeric',
			'compare'	=> 'BETWEEN',
		)
	)
);

Here we set an array of parameters to retrieve a list of fantasy books published after 2010 which cost BETWEEN $10 and $25.

You can see that the 'meta_query' parameter works much like the 'tax_query' parameter. It keeps an array of arrays, allowing us to buind advanced queries based on multiple meta key/value pairs. For a comprehensive list of query parameters and a good number of examples, see the WP_Query documentation.

Why get_posts is Limited to 5 WordPress Posts?

The get_posts function takes the same arguments as WP_Query::parse_query() (see the Codex), but some specific parameters make it work slightly differently from a WP_Query object.

Maybe you didn’t use the 'numberposts' parameter in your queries and you’re wondering why you see just 5 items in your list.

By default, the number of posts you set in Settings → Reading admin page determines the number of posts to be retrieved by a WordPress query. Anyway, if you don’t specify a custom value for 'numberposts' or 'posts_per_page', get_posts returns a different number of posts.

  • 'numberposts' is the total number of posts to retrieve. It is an alias of 'posts_per_page' in WP_Query, but there’s a difference between the two: by default, the number of posts to retrieve when using get_posts is 5, while 'posts_per_page' in WP_Query defaults to the number of posts per page of your WordPress blog. You can override the default value by setting a custom value for 'numberposts' or 'posts_per_page' in the array of arguments.

In addition to 'numberposts', the following parameters are specific of get_posts:

  • 'category' is a comma-separated list of category IDs. It is an alias of the 'cat' parameter in WP_Query.
  • 'include' is a comma-separated list of post IDs. This is an alias of the 'post__in' parameter in WP_Query.
  • 'exclude' is a comma-separated list of post IDs.
  • 'suppress_filters' specifies whether to suppress filters. This parameter defaults to true in get_posts, while it defaults to false in WP_Query (see it on Track).

The get_posts function is defined in wp-includes/post.php. You can deep dive into how get_posts works by checking the source code either on Track (WordPress 5.2) or in your local WordPress installation.

Ordering Items

'orderby' and 'order' sort the items in the result set. You car sort posts by 'ID', 'author', 'title', 'name', 'type', 'date', 'modified', 'parent', 'rand', 'comment_count' and in many other ways, in ascending or descending order.

If you have a simple query, you just need to set a value for 'order' and 'orderby'. In the following example, posts are sorted by post name in ascending order:

$args = array(
	'author'	=> '1,5,12',
	'orderby'	=> 'name',
	'order'		=> 'ASC'
);

That’s pretty straightforward. But what if you had an advanced query? I.e: Can we sort items by one or more custom field values in an advanced meta query?

WordPress 4.0 and WordPress 4.2 brought important improvements to 'orderby' and 'meta_query' params. We now have a new syntax for ordering by specific clauses of a meta query. Thanks to the new syntax, we can use indexes to create references to the specific clauses of the meta query from the 'orderby' parameter.

Thanks to these improvements, the meta query in the example above can be written as follows:

$args = array(
	'meta_query'	=> array(
		'relation'		=> 'AND',
		'year_clause' => array(
			'key'		=> 'year_published',
			'value'		=> 2010,
			'type'		=> 'numeric',
			'compare'	=> '>',
		),
		'price_clause' => array(
			'key'		=> 'price',
			'value'		=> array( 10, 25 ),
			'type'		=> 'numeric',
			'compare'	=> 'BETWEEN',
		)
	),
	'orderby' => 'price_clause',
);

In the example above we ordered elements by 'price_clause'.

And we can do even more. As of WordPress 4.0, we can pass to get_posts an array of meta query indexes instead of a single index, as seen in the example below:

$args = array(
	'meta_query'	=> array(
		'relation'		=> 'AND',
		'year_clause' => array(
			'key'		=> 'year_published',
			'value'		=> 2010,
			'type'		=> 'numeric',
			'compare'	=> '>',
		),
		'price_clause' => array(
			'key'		=> 'price',
			'value'		=> array( 10, 25 ),
			'type'		=> 'numeric',
			'compare'	=> 'BETWEEN',
		)
	),
	'orderby' => array( 'price_clause' => 'ASC', 'year_clause' => 'DESC' ),
);

Congratulations, you’ve built an advanced meta query, and sorted results first by 'price_clause' in ascending order, then by 'year_clause' in descending order.

See the full list of sorting options in the Codex.

It’s time for us to display data on the front page.

Suggested reading: How to Easily Create and Use a phpinfo Page.

How to Display get_posts Returned Data

WordPressget_posts returns an array of WP_Post objects giving us access to a number of variables for each selected post stored in wp_posts database table:

  • ID
  • post_author
  • post_name
  • post_type
  • post_title
  • post_date
  • post_date_gmt
  • post_content
  • post_excerpt
  • post_status
  • comment_status
  • ping_status
  • post_password
  • post_parent
  • post_modified
  • post_modified_gmt
  • comment_count
  • menu_order
WordPress get_posts: wp_posts table
wp_posts table structure in phpMyAdmin

You can easily access these data with a foreach cycle like the following:

$custom_posts = get_posts( $args );

if( ! empty( $custom_posts ) ){
	$output = '<ul>';
	foreach ( $custom_posts as $p ){

		$output .= '<li><a href="' 
		. get_permalink( $p->ID ) . '">' 
		. $p->post_title . '</a></li>';
	}

	$output .= '</ul>';
}

return $output ?? '<strong>Sorry. No posts matching your criteria!</strong>';

If get_posts found at least one post, it returns an array of items we can traverse to show the post title and a link to the original post. We used the get_permalink function to retrieve the post permalink, as we don’t have a corresponding WP_Post variable.

That’s pretty easy, but how can we implement that code and build our custom lists of posts using WordPress get_posts?

You can show lists of posts on your pages in several ways.

Real-World Example: How to Display a Custom List of Items With a Shortcode

I will show you how to build a quick and easy shortcode you can include in your content. Anyway, I won’t dive deep into shortcodes, as we already covered that topic in a previous blog post.

First off, create a new directory in the wp-content/plugins folder of your local WordPress install or in a staging environment. In this example, I named the directory kinsta-shortcodes.

In wp-content/plugins/kinsta-shortcodes/ create a .php file with the same name as the new directory: kinsta-shortcodes.php.

Open the new file in your favorite text editor and include the following heading:

<?php
/**
 * @package Kinsta_shortcodes
 * @version 1.0
 */
/*
Plugin Name: Kinsta shortcodes
Plugin URI: http://wordpress.org/extend/plugins/#
Description: This is an example plugin 
Author: Your Name
Version: 1.0
Author URI: https://yourwebsite.com/
*/

Now we have a brand new plugin, but it’s still doing nothing. Browse to Plugins admin screen in your WordPress dashboard and activate the new plugin making sure you’ve WP_DEBUG set to true in your wp-config.php file.

Your sandbox is now ready for your hacks. The next step is to register a hook for a custom shortcode:

/**
 * Add a hook for a shortcode tag
 */
function kinsta_shortcodes_init(){
	add_shortcode( 'kinsta_get_posts', 'kinsta_get_posts_cb' );
}
add_action('init', 'kinsta_shortcodes_init');

kinsta_get_posts is the shortcode name and kinsta_get_posts_cb is the callback defined below:

/**
 * Register a shortcode
 *
 * @param array $atts Array of shortcode attributes
 */
function kinsta_get_posts_cb( $atts ){

	// safely extract custom arguments and set default values
	extract( shortcode_atts(
			array(
				'numberposts'		=> 3,
				'post_type'			=> 'post',
				'book_category'		=> 'fantasy',
				'year_published'	=> 1900,
				'price_min'			=> 0,
				'price_max'			=> 50
			),
			$atts,
			'kinsta_get_posts'
		) );

	// define the array of query arguments
	$args = array(
		'numberposts'	=> $numberposts,
		'post_type'		=> $post_type,
		'tax_query'		=> array(
			array(
				'taxonomy'	=> 'book_category',
				'field'		=> 'slug',
				'terms'		=> $book_category,
			)
		),
		'meta_query'	=> array(
			'relation'		=> 'AND',
			'year_clause'	=> array(
				'key'		=> 'year_published',
				'value'		=> $year_published,
				'type'		=> 'numeric',
				'compare'	=> '>',
			),
			'price_clause'	=> array(
				'key'		=> 'price',
				'value'		=> array( $price_min, $price_max ),
				'type'		=> 'numeric',
				'compare'	=> 'BETWEEN',
			)
		),
		'orderby' => array( 'price_clause' => 'ASC' )
	);

	$custom_posts = get_posts( $args );

	if( ! empty( $custom_posts ) ){
		$output = '<ul>';
		foreach ( $custom_posts as $p ){

			$output .= '<li><a href="' 
			. get_permalink( $p->ID ) . '">' 
			. $p->post_title . '</a> (' 
			. get_post_meta( $p->ID, 'year_published', true ) 
			. ') - Price: ' . get_post_meta( $p->ID, 'price', true ) . '</li>';
		}

		$output .= '</ul>';
	}

return $output ?? '<strong>Sorry. No posts matching your criteria!</strong>';

We set six shortcode attributes we use to define an array of parameters, which is finally passed to the WordPressget_posts function. If $custom_posts is not empty, then a foreach cycle generates the HTML of an unordered list of items.

Now you and the authors of your blog can include lists of posts using a shortcode like the following:

[kinsta_get_posts post_type="book" book_category="sci-fi" numberposts="4" price_min=1 price_max=250]

Of course, you can change the array of arguments as you like and run your tests in any post or page of your development website.

WordPress get_posts example
An advanced list of posts built with the get_posts function

Summary

WordPress get_posts is a powerful function that allows developers to include lists of posts anywhere on the frontend of your WordPress website. It uses WP_Query but it’s easier to use and is preferable to WP_Query when you just need lists of posts. Anyway, a direct reference to WP_Query is recommended when you need to display posts in a Loop.

So, build your lists, test your code, and when you’re sure it works fine, then (and only then) push it to your live website (but run a backup first).

Now we would like to hear from you. What is your experience with the WordPress get_postsfunction? Do you have any use cases to share with us? Do so in the comments below!

Carlo Daniele Kinsta

Carlo is a passionate lover of webdesign and front-end development. He has been playing with WordPress for more than 20 years, also in collaboration with Italian and European universities and educational institutions. He has written hundreds of articles and guides about WordPress, published both on Italian and international websites, as well as on printed magazines. You can find him on LinkedIn.