WordPress related posts from the same category are the simplest “read next” list: other posts that share a category with the one being read. This snippet prints three of them under a single post as a plain list of links, without a related posts plugin.
It never lists the current post, skips sticky posts and prints nothing on content without categories, instead of falling back to random posts from the whole site.
The snippet
<?php
/**
* Related posts: other posts from the current post's categories.
* Scripts Organizer: Trigger location Conditions, Script location Shortcode.
*/
$dpc_count = 3; // How many posts.
$dpc_orderby = 'rand'; // 'rand' for a random pick, 'date' for the newest.
$dpc_current_id = is_singular() ? get_queried_object_id() : get_the_ID();
$dpc_cat_ids = $dpc_current_id ? wp_get_post_categories( $dpc_current_id ) : array();
if ( $dpc_cat_ids ) {
$dpc_related = new WP_Query(
array(
'post_type' => 'post',
'post_status' => 'publish',
'category__in' => $dpc_cat_ids,
'post__not_in' => array( $dpc_current_id ),
'posts_per_page' => $dpc_count,
'orderby' => $dpc_orderby,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
)
);
if ( $dpc_related->posts ) {
echo '<ul class="dpc-related">';
foreach ( $dpc_related->posts as $dpc_related_post ) {
printf(
'<li class="dpc-related__item"><a href="%1$s">%2$s</a></li>',
esc_url( get_permalink( $dpc_related_post ) ),
esc_html( get_the_title( $dpc_related_post ) )
);
}
echo '</ul>';
}
}
Add it with Scripts Organizer
In Scripts Organizer go to Code Blocks → Add New and give the block a name. Set Trigger location to Conditions, tick Shortcode under Script location (leave PHP unticked), paste the code into the PHP editor, keep Template on All and publish.
The block now shows its shortcode, for example [scorg_shortcode id="123"]. Paste it where the output should appear: a Shortcode block in the post or in a block theme template, or the shortcode element of your page builder’s single or archive template.
Put the shortcode in your single post template or at the end of the post. Setting Template to Post Type → Posts keeps it from printing on pages that happen to use the same template. Scripts Organizer includes this file every time the shortcode renders, inside its own function. That is why the code has no function declarations (a second shortcode on the same page would fatal with “Cannot redeclare”), uses get_the_ID() or get_queried_object_id() instead of a bare $post, and prefixes its variables with dpc_.
How it works
- On a single post
get_queried_object_id()is the post being viewed, even if your template ran another loop before the shortcode. Elsewhere it falls back to the post of the current loop. wp_get_post_categories()returns the post’s category IDs. When there are none the query is skipped, becausecategory__inwith an empty list would match every post.- The
WP_Queryexcludes the current post withpost__not_in, ignores sticky posts and setsno_found_rows, since no pagination is needed. - The loop reads
$dpc_related->postsdirectly instead of callingthe_post(), so the global post of your template stays untouched and nowp_reset_postdata()is needed. Titles and links are escaped.
Customise it
- More or fewer posts: change
$dpc_count. - Newest first instead of random: set
$dpc_orderbyto'date'. - Same tags instead of categories: replace
wp_get_post_categories()withwp_get_post_tags( $dpc_current_id, array( 'fields' => 'ids' ) )andcategory__inwithtag__in. - Thumbnails: add
get_the_post_thumbnail( $dpc_related_post, 'thumbnail' )inside the link, like in Previous and next post links with thumbnails.
Gotchas
'rand'makes the database sort every matching post on every page view. That is fine for a few thousand posts; on large sites use'date'or cache the result in a transient.- Page caching freezes a random pick until the cache is cleared, which is usually fine for related posts.
In the Scripts Organizer library Import it with one click from Scripts Organizer.