The WP_Post Object

WP_Post mirrors one wp_posts row; get_post() fetches it and setup_postdata() makes the template tags work.

Syntaxget_post( int|object|null $post = null, string $output = OBJECT, string $filter = 'raw' ): WP_Post|array|null

WP_Post is the object form of a single row in wp_posts. Its public properties map one-to-one onto the table's columns, so the schema is the API. Core hands you these objects everywhere — get_post(), WP_Query::$posts, the $post global — and they are cached, so fetching the same ID twice does not hit the database twice.

The fields

ID, post_author, post_date, post_date_gmt, post_content, post_title, post_excerpt, post_status, comment_status, ping_status, post_password, post_name (the slug), to_ping, pinged, post_modified, post_modified_gmt, post_content_filtered, post_parent, guid, menu_order, post_type, post_mime_type, comment_count, plus a filter property recording which sanitisation context the object was loaded in.

Fetching one

get_post( $post = null, $output = OBJECT, $filter = 'raw' ) returns WP_Post|array|null. With no argument it returns the global $post, so inside the loop get_post() and get_post( $GLOBALS['post'] ) are the same call. Pass ARRAY_A to get an associative array keyed by the same column names.

Shorthand and longhand

$post->post_title reads the raw column. get_post_field( 'post_title', $post ) reads the same column but defaults to the 'display' context, running it through sanitize_post_field(); get_post_field( 'post_title', $post, 'raw' ) is the exact equivalent of the property.

Custom loops

setup_postdata( $post ) fills $authordata, $pages, $numpages, $multipage, $more and the date globals that template tags read. $query->the_post() is the shorthand that assigns the global and calls it for you.

Example

Example · php
<?php
// With no argument, get_post() returns the global $post. These are identical
// inside the loop:
$current = get_post();
$current = get_post( $GLOBALS['post'] );

// By ID. Returns null if the post does not exist, so always guard.
$book = get_post( 42 );

if ( ! $book instanceof WP_Post ) {
    return;
}

// Properties are the wp_posts columns, raw and unfiltered.
echo $book->ID;          // int
echo $book->post_name;   // the slug
echo $book->post_parent; // 0 when there is no parent
echo $book->post_status; // 'publish', 'draft', ...

// Shorthand and longhand for the same column:
echo $book->post_title;                              // raw
echo get_post_field( 'post_title', $book );          // context 'display' by default
echo get_post_field( 'post_title', $book, 'raw' );   // exactly the line above the last

// Second argument changes the return shape. ARRAY_A gives an associative
// array with the same keys; ARRAY_N gives a numerically indexed one.
$row = get_post( 42, ARRAY_A );
echo $row['post_title'];

// post_content is the raw database text: blocks and shortcodes unparsed,
// no paragraphs, not escaped. Run the filter before output.
echo apply_filters( 'the_content', $book->post_content );

When to use it

  • Rendering a post's content outside the loop without losing shortcode and block processing
  • Writing a helper that accepts either a post ID or a WP_Post object, as core functions do
  • Looping over posts returned by get_posts() while still using the_title() and the_excerpt()
  • Debugging a template that prints the wrong author because $post was never assigned

More examples

A secondary loop, shorthand and longhand

The two forms produce identical output. Reach for the longhand whenever you have WP_Post objects from somewhere other than a WP_Query — a get_posts() call, a cached array, a REST payload you rehydrated — and still want to use template tags.

Example · php
// SHORTHAND: WP_Query::the_post() sets the global $post *and* calls
// setup_postdata() for you.
$related = new WP_Query( array(
    'post_type'      => 'post',
    'posts_per_page' => 3,
    'post__not_in'   => array( get_the_ID() ),
) );

if ( $related->have_posts() ) {
    while ( $related->have_posts() ) {
        $related->the_post();
        the_title( '<h3>', '</h3>' );
        the_excerpt();
    }
    wp_reset_postdata();
}

// LONGHAND: the same result when you hold plain WP_Post objects and no query.
// Note the `global $post` and that the foreach is what assigns it.
global $post;
$items = get_posts( array( 'post_type' => 'post', 'numberposts' => 3 ) );

foreach ( $items as $post ) {
    setup_postdata( $post ); // populates $authordata, $pages, $more, ...
    the_title( '<h3>', '</h3>' );
    the_excerpt();
}
wp_reset_postdata(); // restores the global $post from the main query

Modifying a post: the object is a copy

A frequent source of silent bugs: assigning to a WP_Post property looks like it worked because the rest of the request sees the new value. Use this pattern in any importer, cron job, or admin action that edits posts.

Example · php
$post_object = get_post( 42 );

// This changes nothing in the database. WP_Post is a cached read model.
$post_object->post_title = 'New title';

// Write with wp_update_post(). It needs the ID and only the fields you change.
$result = wp_update_post(
    array(
        'ID'         => 42,
        'post_title' => 'New title',
        'post_status' => 'publish',
    ),
    true // $wp_error: return WP_Error instead of 0 on failure
);

if ( is_wp_error( $result ) ) {
    error_log( $result->get_error_message() );
}

// After a write, the object you fetched earlier is stale. Fetch it again;
// the cache was invalidated by the update.
$post_object = get_post( 42 );

Reading fields safely in a helper

The get_post( $post ) normalisation on the first line is the convention core uses everywhere, and it makes your helper callable with an ID, an object, or nothing at all inside the loop. Note the cast: every wp_posts column comes back as a string.

Example · php
/**
 * Accepts an ID or a WP_Post, like most core functions do.
 *
 * @param int|WP_Post|null $post Post to describe.
 * @return string
 */
function acme_post_byline( $post = null ) {
    $post = get_post( $post ); // normalises int|WP_Post|null to WP_Post|null

    if ( ! $post instanceof WP_Post ) {
        return '';
    }

    // post_author is stored as a string; cast before using it as an ID.
    $author_id = (int) $post->post_author;

    return sprintf(
        /* translators: 1: author name, 2: publication date */
        __( 'By %1$s on %2$s', 'acme' ),
        esc_html( get_the_author_meta( 'display_name', $author_id ) ),
        esc_html( get_the_date( '', $post ) ) // get_the_date() accepts a post
    );
}

Discussion

  • Be the first to comment on this lesson.