Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

WordPress has no core function literally named recent_posts_function. For a simple list of recent published posts, use get_posts(); use WP_Query when you need pagination or more advanced query behavior. These functions retrieve posts, but you still need to render and safely escape the HTML. This guide focuses on WordPress—the phrase can also refer to APIs on other platforms.

Choose the right way to get recent posts

What you need Use
A small, non-paginated list get_posts()
Associative-array results for legacy code wp_get_recent_posts()
Pagination or complex filtering WP_Query
Changes to the main archive or home query pre_get_posts()
A no-code recent-content list The Latest Posts block or a Recent Posts widget, if available in your editor and theme
Reusable output editors can insert A shortcode or custom block

For a basic secondary list, get_posts() is usually the clearest starting point. It returns WP_Post objects by default and uses WordPress query arguments. Its defaults include five posts in descending date order, but explicit arguments make the intended content and ordering clear. WordPress also makes get_posts() ignore sticky-post promotion and skip the found-rows calculation used for pagination. WordPress: get_posts()

A safe, reusable recent-posts function

Add this to a child theme’s functions.php file or to a small site-specific plugin. The function prints a semantic list, limits the requested count, returns nothing when there are no matches, and escapes the title and URL when rendering them.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
function freedom251_recent_posts( $number = 5 ) {
	$number = absint( $number );

	if ( 0 === $number ) {
		return;
	}

	$posts = get_posts(
		array(
			'post_type'      => 'post',
			'post_status'    => 'publish',
			'numberposts'    => $number,
			'orderby'        => 'date',
			'order'          => 'DESC',
		)
	);

	if ( empty( $posts ) ) {
		return;
	}

	echo '<ul class="recent-posts">';

	foreach ( $posts as $post ) {
		printf(
			'<li><a href="%1$s">%2$s</a></li>',
			esc_url( get_permalink( $post ) ),
			esc_html( get_the_title( $post ) )
		);
	}

	echo '</ul>';
}

Call it from a theme template where the list should appear:

<?php freedom251_recent_posts( 5 ); ?>

The number is normalized with absint(). Setting post_status to publish ensures the component is intended to show public posts, not drafts or other statuses. esc_url() is for the link destination; esc_html() is for visible title text. Escaping belongs at output time and should match the output context.

Make the function a shortcode

Shortcodes should return their HTML rather than echoing it. This version supports only two deliberate attributes: a count and a category slug. It sanitizes both instead of accepting arbitrary query arguments from the editor.

function freedom251_recent_posts_shortcode( $atts ) {
	$atts = shortcode_atts(
		array(
			'number'   => 5,
			'category' => '',
		),
		$atts,
		'recent_posts'
	);

	$number = absint( $atts['number'] );

	if ( 0 === $number ) {
		return '';
	}

	$args = array(
		'post_type'      => 'post',
		'post_status'    => 'publish',
		'numberposts'    => $number,
		'orderby'        => 'date',
		'order'          => 'DESC',
	);

	if ( '' !== $atts['category'] ) {
		$args['category_name'] = sanitize_title( $atts['category'] );
	}

	$posts = get_posts( $args );

	if ( empty( $posts ) ) {
		return '';
	}

	$output = '<ul class="recent-posts">';

	foreach ( $posts as $post ) {
		$output .= sprintf(
			'<li><a href="%1$s">%2$s</a></li>',
			esc_url( get_permalink( $post ) ),
			esc_html( get_the_title( $post ) )
		);
	}

	return $output . '</ul>';
}
add_shortcode( 'recent_posts', 'freedom251_recent_posts_shortcode' );

Insert it into content with a shortcode such as [recent_posts number="5" category="news"]. The category value must be the category slug. A shortcode is not secure automatically: sanitize accepted attributes, escape rendered values, and keep the supported options narrow.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Add a category, post type, or taxonomy filter

get_posts() accepts most WP_Query arguments, so a list can be narrowed without changing the rendering approach. For a category slug:

$posts = get_posts(
	array(
		'post_type'     => 'post',
		'post_status'   => 'publish',
		'numberposts'   => 5,
		'category_name' => 'news',
	)
);

For a registered custom post type, change post_type to its slug:

$books = get_posts(
	array(
		'post_type'   => 'book',
		'post_status' => 'publish',
		'numberposts' => 6,
	)
);

For a custom taxonomy, use tax_query with the taxonomy name and the term field you are matching:

$posts = get_posts(
	array(
		'post_type'      => 'post',
		'post_status'    => 'publish',
		'numberposts'    => 5,
		'tax_query'      => array(
			array(
				'taxonomy' => 'topic',
				'field'    => 'slug',
				'terms'    => array( 'wordpress' ),
			),
		),
	)
);

Use the actual registered post-type and taxonomy slugs on your site. A correctly structured query returns nothing if the slug does not exist or has no matching published content.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Exclude the current post

For a related or sidebar list on a single post, exclude the page currently being viewed with post__not_in:

$current_id = get_the_ID();

$posts = get_posts(
	array(
		'post_type'    => 'post',
		'post_status'  => 'publish',
		'numberposts'  => 5,
		'post__not_in' => array( $current_id ),
		'orderby'      => 'date',
		'order'        => 'DESC',
	)
);

This prevents a list from linking to the current article; it does not prevent two separate recent-post components on the same page from repeating the same entries. To avoid that, keep track of IDs already displayed and pass them to later queries for exclusion.

Use cards with thumbnails, dates, or excerpts

For a richer list, keep the same query but render each result as an article. Check for a featured image so posts without one do not leave empty image boxes. Use a heading level that fits the surrounding page structure rather than choosing one solely for visual size.

foreach ( $posts as $post ) {
	$title = get_the_title( $post );
	$url   = get_permalink( $post );

	echo '<article class="recent-post-card">';

	if ( has_post_thumbnail( $post ) ) {
		echo '<a href="' . esc_url( $url ) . '">';
		echo get_the_post_thumbnail(
			$post,
			'medium',
			array( 'loading' => 'lazy' )
		);
		echo '</a>';
	}

	echo '<h3><a href="' . esc_url( $url ) . '">';
	echo esc_html( $title );
	echo '</a></h3>';

	echo '<time datetime="' . esc_attr( get_the_date( DATE_W3C, $post ) ) . '">';
	echo esc_html( get_the_date( '', $post ) );
	echo '</time>';

	$excerpt = get_the_excerpt( $post );
	if ( $excerpt ) {
		echo '<p>' . esc_html( wp_strip_all_tags( $excerpt ) ) . '</p>';
	}

	echo '</article>';
}

get_the_post_thumbnail() outputs image markup, while get_the_excerpt() and get_the_date() retrieve display values. The example uses lazy loading for thumbnails; avoid applying lazy loading blindly to an image that is prominent above the fold. Keep link text meaningful, and avoid redundant metadata or a second identical link around the same card content.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When wp_get_recent_posts() makes sense

wp_get_recent_posts() is a long-standing convenience wrapper around get_posts(). It returns associative arrays by default, so fields are accessed with keys such as $recent['ID'] and $recent['post_title']. The documented default is 10 posts, and the documented status default is broader than a public-only list. Specify post_status explicitly when the intended output is published content.

$recent_posts = wp_get_recent_posts(
	array(
		'numberposts' => 5,
		'post_status' => 'publish',
		'post_type'   => 'post',
	)
);

foreach ( $recent_posts as $recent ) {
	printf(
		'<a href="%1$s">%2$s</a>',
		esc_url( get_permalink( $recent['ID'] ) ),
		esc_html( $recent['post_title'] )
	);
}

Pass OBJECT as the second argument if you want post objects rather than the default associative arrays. Passing an integer directly as the first argument is deprecated; pass an argument array instead. For new code, get_posts() is usually more readable when you want WP_Post objects. WordPress: wp_get_recent_posts()

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Use WP_Query for pagination or advanced control

Choose WP_Query when the list behaves more like an archive: it needs pagination, complex filters, or other query controls. A custom loop changes the global post context when it calls the_post(), so restore that context afterward.

$query = new WP_Query(
	array(
		'post_type'      => 'post',
		'post_status'    => 'publish',
		'posts_per_page' => 10,
		'paged'          => max( 1, get_query_var( 'paged' ) ),
		'orderby'        => 'date',
		'order'          => 'DESC',
	)
);

if ( $query->have_posts() ) {
	echo '<ul class="recent-posts">';

	while ( $query->have_posts() ) {
		$query->the_post();
		printf(
			'<li><a href="%1$s">%2$s</a></li>',
			esc_url( get_permalink() ),
			esc_html( get_the_title() )
		);
	}

	echo '</ul>';
}

wp_reset_postdata();

For a real paginated view, add pagination links appropriate to the site’s template and URL context; posts_per_page and paged set the query, but do not render pagination controls by themselves. Do not use get_posts() as an archive-pagination shortcut: its convenience behavior disables the found-rows calculation typically needed for pagination. WordPress: WP_Query

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why not use query_posts()?

A secondary recent-posts list should not replace the page’s main query. Avoid legacy patterns such as query_posts( 'posts_per_page=5' ): WordPress documents risks to performance and pagination and recommends alternatives such as WP_Query, get_posts(), or pre_get_posts(), depending on the goal. Use pre_get_posts() when the requirement is to alter the main archive query before it runs. WordPress: query_posts()

When PHP is unnecessary

If you only need a basic list in a page or template, the built-in Latest Posts block or a Recent Posts widget may be enough. The exact label and editor location depend on the WordPress version, theme, and editor context. These tools handle presentation without requiring you to add a PHP function. Use custom code when the built-in display cannot meet your filtering, markup, or integration needs.

Troubleshooting common problems

  • Drafts or unexpected posts appear: set 'post_status' => 'publish', particularly when using wp_get_recent_posts().
  • Sticky posts are not promoted: get_posts() ignores sticky-post behavior. If sticky ordering matters, configure a WP_Query deliberately rather than assuming the convenience function promotes them.
  • The list is empty: confirm that published posts exist and that category, taxonomy, post-type, date, and exclusion arguments match content on the site.
  • The current article appears: exclude its ID with post__not_in.
  • Later template content shows the wrong post: call wp_reset_postdata() after a WP_Query loop that used the_post().
  • A shortcode displays nothing: verify that it is registered, that the shortcode tag matches, that its filters return posts, and that the callback returns markup rather than echoing it.
  • Images are missing: check that those posts have featured images and that the theme supports thumbnails. Only render the image link when has_post_thumbnail() is true.

Keep the requested count small, avoid retrieving every post without a clear reason, and avoid repeating nearly identical queries on one page. Query cost depends on the site’s content and setup, so there is no universal performance figure to promise.

Quick decision

Use get_posts() for a simple list, wp_get_recent_posts() when legacy array-shaped output is useful, and WP_Query for pagination or complex query behavior. Modify the main query with pre_get_posts(), not query_posts(). If you do not need custom PHP, start with the Latest Posts block or Recent Posts widget.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.