October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run ScanOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
cookies

How to Show a Visitor’s Last Visited Post in WordPress

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

To show a returning visitor the post they most recently read, save the current post ID in a site-specific cookie, then query that ID and render its title and link later. The example below keeps a short, ordered history, works for anonymous visitors, and exposes the display through a [last_visited_posts] shortcode.

What this implementation does

  • Records published single-post views in a cookie named freedom251_post_history.
  • Moves a revisited post to the newest position and keeps at most five IDs.
  • Reads, validates and deduplicates the IDs before querying WordPress.
  • Displays only currently published posts, so private or unavailable content is not exposed.
  • Lets an editor place the result with a shortcode.

This is a visitor-specific browsing-history cookie. It is separate from the cookies WordPress uses for login sessions or commenter convenience, as described in the WordPress Cookies documentation.

Add the history code

Put the following in a small site-specific plugin or in a code-snippet tool such as WPCode. A site-specific plugin is usually easier to keep when changing themes.

<?php
/**
 * Save the current published post in a visitor's short history.
 */
function freedom251_record_post_history() {
    if ( ! is_singular( 'post' ) || ! is_user_logged_in() && headers_sent() ) {
        return;
    }

    $post_id = get_queried_object_id();
    if ( ! $post_id || 'publish' !== get_post_status( $post_id ) ) {
        return;
    }

    $cookie_name = 'freedom251_post_history';
    $limit       = 5;
    $old_value   = isset( $_COOKIE[ $cookie_name ] ) ? wp_unslash( $_COOKIE[ $cookie_name ] ) : '';
    $old_ids     = array_filter( array_map( 'absint', explode( ',', $old_value ) ) );

    // Remove this post wherever it already appears, then add it as newest.
    $old_ids = array_values( array_diff( $old_ids, array( $post_id ) ) );
    array_unshift( $old_ids, $post_id );
    $old_ids = array_slice( $old_ids, 0, $limit );

    setcookie(
        $cookie_name,
        implode( ',', $old_ids ),
        array(
            'expires'  => time() + ( 30 * DAY_IN_SECONDS ),
            'path'     => COOKIEPATH ? COOKIEPATH : '/',
            'secure'   => is_ssl(),
            'httponly' => true,
            'samesite' => 'Lax',
        )
    );

    // Make the new value available during this request as well.
    $_COOKIE[ $cookie_name ] = implode( ',', $old_ids );
}
add_action( 'template_redirect', 'freedom251_record_post_history' );

/**
 * Render the saved posts with [last_visited_posts].
 */
function freedom251_last_visited_posts_shortcode() {
    $cookie_name = 'freedom251_post_history';
    $raw_value   = isset( $_COOKIE[ $cookie_name ] ) ? wp_unslash( $_COOKIE[ $cookie_name ] ) : '';
    $ids         = array_filter( array_map( 'absint', explode( ',', $raw_value ) ) );
    $ids         = array_values( array_unique( $ids ) );

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

    $posts = get_posts(
        array(
            'post_type'           => 'post',
            'post_status'         => 'publish',
            'post__in'            => $ids,
            'orderby'             => 'post__in',
            'posts_per_page'      => count( $ids ),
            'ignore_sticky_posts' => true,
            'no_found_rows'       => true,
        )
    );

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

    $output = '<ul class="last-visited-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 ) )
        );
    }
    $output .= '</ul>';

    return $output;
}
add_shortcode( 'last_visited_posts', 'freedom251_last_visited_posts_shortcode' );

The template_redirect hook runs early enough for the response cookie to be sent before page output. The condition intentionally records only single posts with the public publish status. If your site uses a custom post type, change is_singular( 'post' ), post_type, and the status check together.

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

Place the list on a page

  1. Create or edit the page, widget, block, or template area where the list should appear.
  2. Insert [last_visited_posts] in a Shortcode block or the editor location that supports shortcodes.
  3. Visit several published posts in a private browser window, then return to that page. The newest post should appear first.

WordPress describes the Shortcode API as a set of functions for creating shortcodes used in posts and pages. A shortcode is therefore appropriate when an editor controls placement. For a fixed sidebar or header, call the same rendering function from a theme template or wrap it in a custom block instead.

How the cookie and query are kept safe

Validate every stored value

Cookies are visitor input. The code converts each entry to an integer, removes empty values and duplicates, and returns nothing for a missing or unusable cookie. Do not use a cookie value directly in SQL or print it as HTML.

Preserve the visitor’s order

post__in limits the query to saved IDs, while orderby => post__in keeps the newest-to-oldest order established by the cookie. A normal date sort could show a different order from the visitor’s actual reading history.

Keep restricted content out

The query requests only post_status => publish. That prevents this public widget from intentionally listing drafts, private posts, or other non-public statuses. If you add a custom visibility rule, apply it before output as well.

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

Shortcode, template, block or REST API?

Approach Best use Visitor state Important consideration
Shortcode An editor chooses the page or content location Cookie-based history for anonymous visitors Easy placement; output still varies by visitor and must be handled by caching correctly
Theme template A fixed area such as a sidebar or account layout Cookie-based history, or another server-side design Requires theme or plugin ownership and PHP changes
Custom block A block-editor component with controlled markup and styling Usually the same cookie logic More development work, but better editor controls
REST-powered component A JavaScript interface that fetches public post details Client-held IDs or a cookie sent to your own endpoint Expose only public data; WordPress cookie authentication and nonces are for authenticated requests, not anonymous identification
Account history A signed-in user should see history across devices Server-side records tied to an account This is a different design from the anonymous cookie pattern and needs its own storage, retention and privacy decisions

The WordPress Posts REST API documents public and restricted post data boundaries. If you build a REST variation, do not return private content merely because an ID was supplied by a browser.

Caching, consent and privacy checks

Page caching

A full-page cache can serve one visitor’s rendered list to another, or serve a page generated before the cookie existed. Configure the cache to vary this component by cookie, bypass caching for the personalized response, or render the list client-side. The cited implementation example does not establish compatibility with particular themes or caching plugins, so verify it on your stack.

Cookie policy

Decide how the 30-day feature cookie fits your privacy notice, consent banner and retention policy. Requirements differ by jurisdiction and site configuration; the implementation guidance does not establish a universal legal exemption. If consent is required for this cookie on your site, set it only after the relevant consent signal and provide a way to clear it.

Logged-in and anonymous testing

Test in separate browser profiles. A logged-in WordPress session cookie does not automatically provide anonymous visitor history, and the history cookie in this example does not authenticate a user.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting

  • No list appears: confirm the shortcode is exactly [last_visited_posts], browse at least one published post, and inspect whether the browser accepted the cookie.
  • The cookie never appears: check for output sent before template_redirect, confirm the site uses HTTPS when expected, and verify the cookie path and domain settings.
  • Posts appear in the wrong order: keep orderby => post__in; removing it allows the database’s normal ordering to take over.
  • A cached list is shared between visitors: exclude the personalized fragment from full-page caching or move rendering to a request made after page load.
  • A deleted or private post is missing: that is expected; the query deliberately limits results to currently published posts.
  • Your site uses a custom post type: update the singular check, query’s post_type, and status rules consistently, then test permissions and public visibility.

Before putting it live

  • Confirm the cookie name does not collide with another plugin.
  • Set the history limit and expiration to match your privacy notice.
  • Test first visit, repeat visit, revisiting an older post, a malformed cookie, and an empty history.
  • Test logged-in and logged-out browsers, mobile and desktop layouts, and every page-cache layer.
  • Check that the output contains no drafts, private posts, or posts the visitor cannot publicly view.
  • Document the cookie in your site’s privacy and cookie controls where required.

The Bottom Line

Record a small, validated list of post IDs in a feature-specific cookie, retrieve only public posts in that saved order, and render the result with a shortcode or fixed layout component. Treat caching and cookie-consent behavior as part of the implementation, not as an afterthought.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Read next

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.