How to Show Posts Without Using setup_postdata()

Quick Summary

  • Loop over get_posts() results and read fields straight from each WP_Post object, such as post_title, without calling setup_postdata().
  • The example lists hand-picked IDs with post__in, keeps their order with orderby post__in and shows one per page via paged.
  • Functions relying on the global post still refer to the page running the template, so pass the post object to has_post_thumbnail() and get_the_post_thumbnail().

get_posts() returns an array of WP_Post objects. The usual pattern, used for showing recent posts with get_posts(), is to call setup_postdata() on each one so template tags work, but you can also skip it and read the data straight from the object. This snippet does that to show a few hand-picked posts, one per page, in a WordPress template.

PHP
<?php 
$paged = get_query_var( "paged" )?get_query_var( "paged" ):1;
$post_per_page = 1;
$post_ids = array( 12,9,18 );
$_p = get_posts(array(
	'posts_per_page'	=> $post_per_page,
	'post__in'		=> $post_ids,
	'orderby'		=>'post__in',
	'paged'			=> $paged	
));

foreach ($_p as $p) {
//setup_postdata( $post );
?>

<div class="post" <?php post_class( ); ?> >
	<div class="container">
		                    
		 <div class="row">
		      <div class="col-md-12">
                      <h3><?php echo apply_filters( "the_title",$p->post_title ); ?></h3>
									
		</div>

		                        <div class="col-md-12">
		                            <p>
		                               <?php

		                                if(has_post_thumbnail( )){
		                                    the_post_thumbnail( 'large', array( "class"=>"img-fluid single-post-highlight-image" ) );
		                                }

		                               ?>
		                            </p>



		                            
		                     		


		                        </div>

		                    </div>

		                </div>
		            </div>
		        <?php }

		        	wp_reset_postdata();

		        ?>

How it works

  • $paged is the current page number from the URL, or 1 on the first page.
  • post__in limits the query to the IDs in $post_ids, and 'orderby' => 'post__in' keeps them in the order you listed: 12, 9, 18. Replace the IDs with your own.
  • $post_per_page is 1, so together with paged each page shows one of the three posts. get_posts() returns a plain array without a page count, and no page links are printed, so add your own navigation.
  • The title comes from $p->post_title. Passing it through apply_filters( "the_title", ... ) applies the filters the_title() would. Other fields work the same way, such as $p->ID or $p->post_content.

Watch out for tags that use the global post

Without setup_postdata(), the global $post is still the page running the template, and any function that relies on it ignores $p:

  • has_post_thumbnail() and the_post_thumbnail() check and show the featured image of that page, not of the listed post. Pass the post instead: has_post_thumbnail( $p ) and get_the_post_thumbnail( $p, 'large', ... ).
  • post_class() prints the page’s classes too, and because the div already has class="post", the tag ends up with two class attributes, of which browsers use only the first. post_class( 'post', $p ) on its own covers both.
  • The closing wp_reset_postdata() isn’t strictly needed when nothing changed the global post, but it does no harm.
Share on X (opens in a new tab) Share on LinkedIn (opens in a new tab) Share on Facebook (opens in a new tab)

ChatGPT (opens in a new tab) Claude (opens in a new tab) Gemini (opens in a new tab) Perplexity (opens in a new tab) Grok (opens in a new tab)

More Insights

See More