⏲️ Estimated reading time: 9 min
📈 Trending Posts Widget for WordPress – Show Popular Posts by Views. Add a powerful “Trending Posts” widget to your WordPress site using this custom PHP code. Learn how to display your most viewed articles based on post meta values like post_views_count.
📌 Display Your Most Popular Posts With a Custom Trending Widget
Displaying trending or most-viewed posts is a smart way to keep your visitors engaged. It encourages exploration, increases page views, and promotes your top-performing content. In this tutorial, we’ll show you how to add a custom “Trending Posts” widget to your WordPress site using a simple PHP code snippet.
This widget will dynamically pull the top posts based on their view count using a custom field (post_views_count). It also includes support for caching using transient to ensure fast load times and less server stress.
🧩 Step-by-Step: How the Trending Posts Widget Works
✅ 1. Widget Registration
The widget is registered using register_widget() inside a widgets_init hook.
add_action('widgets_init', 'register_trending_posts_widget');
function register_trending_posts_widget() {
register_widget('Trending_Posts_Widget');
}
✅ 2. Widget Class Structure
The widget is built by extending the WP_Widget class and defining key functions:
__construct()– Widget name and description.widget()– Front-end output of trending posts.form()– Admin form in Widgets panel.update()– Save form settings and clear the cache.
🔧 PHP Code: Trending Posts Widget
Below is the complete code to add to your theme’s functions.php or inside a custom plugin:
<?php
/**
* Trending Posts Widget – PRO
* Optimized, cache-versioned, production-ready
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Trending_Posts_Widget extends WP_Widget {
private $defaults = [
'title' => '',
'num_posts' => 5,
'show_date' => false,
'period' => 'all_time', // all_time | monthly | weekly
];
public function __construct() {
parent::__construct(
'trending_posts_widget',
esc_html__( 'Trending Posts', 'your-textdomain' ),
[
'classname' => 'widget-trending-posts',
'description' => esc_html__( 'Display trending posts by view count.', 'your-textdomain' ),
'customize_selective_refresh' => true,
]
);
// Invalidate cache version when views are updated
add_action( 'updated_post_meta', [ $this, 'maybe_bump_cache_version' ], 10, 4 );
}
/**
* Increment cache version when post views change
* (cheap, no DB deletes)
*/
public function maybe_bump_cache_version( $meta_id, $post_id, $meta_key, $meta_value ) {
if ( $meta_key !== 'post_views_count' ) {
return;
}
$ver = (int) get_transient( 'trending_posts_cache_ver' );
set_transient( 'trending_posts_cache_ver', $ver + 1, DAY_IN_SECONDS );
}
/**
* Widget output
*/
public function widget( $args, $instance ) {
$instance = wp_parse_args( (array) $instance, $this->defaults );
$cache_ver = (int) get_transient( 'trending_posts_cache_ver' );
$cache_key = sprintf(
'trending_posts_%d_%s_%s_%d_%d',
$cache_ver,
$this->id,
$instance['period'],
absint( $instance['num_posts'] ),
$instance['show_date'] ? 1 : 0
);
$output = get_transient( $cache_key );
if ( false === $output ) {
$query_args = [
'posts_per_page' => absint( $instance['num_posts'] ),
'meta_key' => 'post_views_count',
'orderby' => 'meta_value_num',
'order' => 'DESC',
'post_status' => 'publish',
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
];
if ( $instance['period'] !== 'all_time' ) {
$query_args['date_query'] = $this->get_date_query( $instance['period'] );
}
$query = new WP_Query( $query_args );
ob_start();
if ( $query->have_posts() ) {
echo '<ul class="trending-posts-list">';
while ( $query->have_posts() ) {
$query->the_post();
$views = (int) get_post_meta( get_the_ID(), 'post_views_count', true );
?>
<li class="trending-post-item">
<?php if ( has_post_thumbnail() ) : ?>
<a href="<?php echo esc_url( get_permalink() ); ?>" class="trending-thumb">
<?php the_post_thumbnail( 'thumbnail', [ 'loading' => 'lazy' ] ); ?>
</a>
<?php endif; ?>
<div class="trending-post-info">
<a class="trending-post-title" href="<?php echo esc_url( get_permalink() ); ?>">
<?php echo esc_html( get_the_title() ); ?>
</a>
<div class="trending-post-meta">
<span class="views">
<?php
printf(
esc_html__( '%s views', 'your-textdomain' ),
number_format_i18n( $views )
);
?>
</span>
<?php if ( $instance['show_date'] ) : ?>
<time datetime="<?php echo esc_attr( get_the_date( DATE_W3C ) ); ?>">
<?php echo esc_html( get_the_date() ); ?>
</time>
<?php endif; ?>
</div>
</div>
</li>
<?php
}
echo '</ul>';
} else {
echo '<p class="no-posts">' . esc_html__( 'No trending posts found.', 'your-textdomain' ) . '</p>';
}
wp_reset_postdata();
$output = ob_get_clean();
// cache 1 hour (safe + efficient)
set_transient( $cache_key, $output, HOUR_IN_SECONDS );
}
if ( ! empty( $output ) ) {
echo $args['before_widget'];
$title = apply_filters(
'widget_title',
$instance['title'] ?: esc_html__( 'Trending Posts', 'your-textdomain' ),
$instance,
$this->id_base
);
if ( $title ) {
echo $args['before_title'] . esc_html( $title ) . $args['after_title'];
}
echo $output;
echo $args['after_widget'];
}
}
/**
* Date query helper
*/
private function get_date_query( $period ) {
switch ( $period ) {
case 'weekly':
return [ [ 'after' => '1 week ago' ] ];
case 'monthly':
return [ [ 'after' => '1 month ago' ] ];
default:
return [];
}
}
/**
* Admin form
*/
public function form( $instance ) {
$instance = wp_parse_args( (array) $instance, $this->defaults );
?>
<p>
<label><?php esc_html_e( 'Title:', 'your-textdomain' ); ?></label>
<input class="widefat" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text"
value="<?php echo esc_attr( $instance['title'] ); ?>">
</p>
<p>
<label><?php esc_html_e( 'Number of posts:', 'your-textdomain' ); ?></label>
<input class="tiny-text" type="number" min="1" max="20"
name="<?php echo esc_attr( $this->get_field_name( 'num_posts' ) ); ?>"
value="<?php echo esc_attr( $instance['num_posts'] ); ?>">
</p>
<p>
<label><?php esc_html_e( 'Period:', 'your-textdomain' ); ?></label>
<select class="widefat" name="<?php echo esc_attr( $this->get_field_name( 'period' ) ); ?>">
<option value="all_time" <?php selected( $instance['period'], 'all_time' ); ?>>All time</option>
<option value="monthly" <?php selected( $instance['period'], 'monthly' ); ?>>Last 30 days</option>
<option value="weekly" <?php selected( $instance['period'], 'weekly' ); ?>>Last 7 days</option>
</select>
</p>
<p>
<label>
<input type="checkbox"
name="<?php echo esc_attr( $this->get_field_name( 'show_date' ) ); ?>"
<?php checked( $instance['show_date'] ); ?>>
<?php esc_html_e( 'Show date', 'your-textdomain' ); ?>
</label>
</p>
<?php
}
public function update( $new, $old ) {
return [
'title' => sanitize_text_field( $new['title'] ?? '' ),
'num_posts' => absint( $new['num_posts'] ?? 5 ),
'show_date' => ! empty( $new['show_date'] ),
'period' => sanitize_key( $new['period'] ?? 'all_time' ),
];
}
}
/**
* Register widget
*/
add_action( 'widgets_init', function () {
register_widget( 'Trending_Posts_Widget' );
});
CSS Styling Suggestions
.widget-trending-posts .trending-posts-list {
list-style: none;
padding: 0;
margin: 0;
}
.trending-post-item {
display: flex;
gap: 12px;
margin-bottom: 16px;
align-items: flex-start;
}
.trending-post-thumbnail-link img {
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 4px;
}
.trending-post-meta {
font-size: 0.85em;
color: #666;
margin-top: 4px;
}
.view-count::before {
content: '👁 ';
opacity: 0.7;
}
Note: Replace 'your-textdomain' with your theme/plugin’s actual text domain (e.g., 'mytheme' or 'trending-widget').
🔄 Notes on Usage
- The widget assumes you already have a custom field
post_views_countupdated manually or via a separate plugin. - You can use any plugin like Post Views Counter or your own script to increment this meta key.
- The widget caches results for 1 hour using WordPress
transientsfor improved performance. - Works seamlessly with any modern WordPress theme that supports widgets.

🚀 Tips to Improve the Widget
- Add fallback thumbnail image using a conditional
elseinhas_post_thumbnail(). - Use CSS to customize the output style of
.trending-thumb. - Allow filtering by category or post type in future enhancements.
- Make it Gutenberg block-ready for block themes.
🧠 Final Thoughts
Using this widget, you give your site visitors access to the most engaging content on your blog. It’s great for blogs, magazines, news sites, or any content-heavy platform that thrives on post visibility.
⚠️ Disclaimer and Source Hygiene
This content is provided for informational and educational purposes only. While every effort has been made to ensure accuracy, completeness, and relevance at the time of publication, the information presented should not be interpreted as professional, legal, medical, or financial advice.
The author and publisher assume no responsibility or liability for any errors, omissions, or outcomes resulting from the use of this information. Readers are encouraged to independently verify facts and consult qualified professionals where appropriate.
Source Hygiene & Editorial Standards
- Information is compiled from publicly available sources, reputable publications, official statements, historical records, and/or commonly accepted data at the time of writing.
- When specific data, statistics, or quotations are used, they are based on sources believed to be reliable, but absolute accuracy cannot be guaranteed due to the evolving nature of information.
- Some content may include editorial analysis, interpretation, or opinion, clearly separated from factual reporting where applicable.
- Images, examples, and scenarios may be illustrative or symbolic and are not always direct representations of real events or individuals, unless explicitly stated.
Updates & Corrections
Content may be updated, expanded, or corrected without prior notice to reflect new information, clarifications, or improved accuracy.
If you believe any information is inaccurate, outdated, or improperly sourced, please contact the site administrator so it can be reviewed and corrected promptly.
🔔 For more tutorials like this, consider subscribing to our blog.
📩 Do you have questions or suggestions? Leave a comment or contact us!
🏷️ Tags: WordPress widget, trending posts, post views, custom widget, PHP snippet, WP_Query, WordPress development, cache with transients, post meta, WP_Widget
📢 Hashtags: #WordPress, #TrendingPosts, #WidgetDev, #WPCustomCode, #PHPTutorial, #PostViews, #WPQuery, #Caching, #WPWidgets, #WebDevelopment
💡 Developer’s Wrap-Up
The “Trending Posts Widget” is a must-have feature for any WordPress site looking to increase user engagement. With just a bit of code, you can create a powerful tool that highlights your site’s best content based on real user behavior. Add it today and watch your page views climb!