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.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsfunction 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.
#1 Best Overall
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.
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:
Rank #2
$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.
Rank #3
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.
Rank #4
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.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →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()
Best Value
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.
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 usingwp_get_recent_posts(). - Sticky posts are not promoted:
get_posts()ignores sticky-post behavior. If sticky ordering matters, configure aWP_Querydeliberately 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 aWP_Queryloop that usedthe_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.
Quick Recap
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.

