Including Custom Post Types in “Recent Posts” Widget

The question:

I’m able to easily include my custom post types into my main loop by making small adjustments with query_posts(), but I’m not sure how I would go about including custom post types in the “Recent Posts” sidebar widget (or any of the other widgets, for that matter).

How should I go about expanding “Recent Posts” scope to include more than just the native post type?

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

You’ll have to edit the code for the Recent Posts widget or create your own version based on the default. The code is in the wp-includes/default-widgets.php file around line 513. But since you should never make modifications to core, my recommendation would be to copy the code to create your own My Custom Recent Posts widget and use that on your site. Just drop the new widget class into your theme’s functions.php file or use it in a plugin.

The only real modification you need to make are to the widget’s class name and encapuslated functions and options (so that there aren’t any naming conflicts with the original Recent Posts widget. After that, you’ll need to edit the call to WP_Query in the widget() constructor so that it includes your custom post type.

For this example, I’ve set post_type equal to array('post, 'page', 'custom-post-type') … you’ll need to modify that to fit your specific use case.

Here’s the widget’s full code for reference:

/**
  * My_Custom_Recent_Posts widget class
  *
  */
class WP_Widget_My_Custom_Recent_Posts extends WP_Widget {

    function __construct() {
        $widget_ops = array('classname' => 'widget_my_custom_recent_entries', 'description' => __( "The most recent posts on your site") );
        $this->WP_Widget('my-custom-recent-posts', __('My Custom Recent Posts'), $widget_ops);
        $this->alt_option_name = 'widget_my_custom_recent_entries';

        add_action( 'save_post', array(&$this, 'flush_widget_cache') );
        add_action( 'deleted_post', array(&$this, 'flush_widget_cache') );
        add_action( 'switch_theme', array(&$this, 'flush_widget_cache') );
    }

    function widget($args, $instance) {
        $cache = wp_cache_get('widget_my_custom_recent_posts', 'widget');

        if ( !is_array($cache) )
            $cache = array();

        if ( isset($cache[$args['widget_id']]) ) {
            echo $cache[$args['widget_id']];
            return;
        }

        ob_start();
        extract($args);

        $title = apply_filters('widget_title', empty($instance['title']) ? __('My Custom Recent Posts') : $instance['title'], $instance, $this->id_base);
        if ( !$number = (int) $instance['number'] )
            $number = 10;
        else if ( $number < 1 )
            $number = 1;
        else if ( $number > 15 )
            $number = 15;

        $r = new WP_Query(array('showposts' => $number, 'nopaging' => 0, 'post_status' => 'publish', 'ignore_sticky_posts' => true, 'post_type' => array('post', 'page', 'custom-post-type')));
        if ($r->have_posts()) :
?>
        <?php echo $before_widget; ?>
        <?php if ( $title ) echo $before_title . $title . $after_title; ?>
        <ul>
        <?php  while ($r->have_posts()) : $r->the_post(); ?>
        <li><a href="<?php the_permalink() ?>" rel="nofollow noreferrer noopener" title="<?php echo esc_attr(get_the_title() ? get_the_title() : get_the_ID()); ?>"><?php if ( get_the_title() ) the_title(); else the_ID(); ?></a></li>
        <?php endwhile; ?>
        </ul>
        <?php echo $after_widget; ?>
<?php
        // Reset the global $the_post as this query will have stomped on it
        wp_reset_postdata();

        endif;

        $cache[$args['widget_id']] = ob_get_flush();
        wp_cache_set('widget_my_custom_recent_posts', $cache, 'widget');
    }

    function update( $new_instance, $old_instance ) {
        $instance = $old_instance;
        $instance['title'] = strip_tags($new_instance['title']);
        $instance['number'] = (int) $new_instance['number'];
        $this->flush_widget_cache();

        $alloptions = wp_cache_get( 'alloptions', 'options' );
        if ( isset($alloptions['widget_my_custom_recent_entries']) )
            delete_option('widget_my_custom_recent_entries');

        return $instance;
    }

    function flush_widget_cache() {
        wp_cache_delete('widget_my_custom_recent_posts', 'widget');
    }

    function form( $instance ) {
        $title = isset($instance['title']) ? esc_attr($instance['title']) : '';
        if ( !isset($instance['number']) || !$number = (int) $instance['number'] )
            $number = 5;
?>
        <p><label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:'); ?></label>
        <input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo $title; ?>" /></p>

        <p><label for="<?php echo $this->get_field_id('number'); ?>"><?php _e('Number of posts to show:'); ?></label>
        <input id="<?php echo $this->get_field_id('number'); ?>" name="<?php echo $this->get_field_name('number'); ?>" type="text" value="<?php echo $number; ?>" size="3" /></p>
<?php
    }
}

Method 2

As of at least 3.6, you can use the following code to modify the query used:

add_filter('widget_posts_args', 'widget_posts_args_add_custom_type'); 
function widget_posts_args_add_custom_type($params) {
   $params['post_type'] = array('post','custom_type');
   return $params;
}

Just add the types you want in the array for post_type and they should appear.

Update: According to http://core.trac.wordpress.org/ticket/16159, this has been available since 3.4

Method 3

I just came across a great plugin where the heavy lifting is already done, and it has great documentation and author support. I’ve really been impressed.

It allows WP_Query overrides (allowing you to filter by custom post types and anything else you would want) and some clear instructions on how to use it.

Documentation
http://www.pjgalbraith.com/2011/08/recent-posts-plus/

WordPress Plugin URL
http://wordpress.org/extend/plugins/recent-posts-plus/

Made my work just that much shorter!

Method 4

You can copy the widget code (see /wp-includes/default-widgets.php) and modify the query line.

Method 5

I have also create a widget plugin for this that’s more customizable than the Recent Posts widget. If interested you can download it here http://new2wp.com/pro/latest-custom-post-type-posts-sidebar-widget/

Method 6

This Code Creates a New Recent Posts Widget Which Includes Your CPT’s

There’s 2 steps involved when extending the native recent posts widget:

i. Create a new class for your custom recent posts widget which you can do by copying and renaming the recent posts widget code from the defaults-widgets.php in wp-includes folder.

ii. Then you will need to register the new widget as well and you may choose to de-register the native recent posts widget or use both.

All the code can simply be copied into your functions file using a child theme or create another file and include it in your child themes functions file.

<?php

class WPSites_Recent_Posts extends WP_Widget {

    public function __construct() {
        $widget_ops = array('classname' => 'wpsites_recent_posts', 'description' => __( "Latest CPT's & Posts.") );
        parent::__construct('wpsites-recent-posts', __('WP Sites Recent Posts'), $widget_ops);
        $this->alt_option_name = 'wpsites_recent_posts';

        add_action( 'save_post', array($this, 'flush_widget_cache') );
        add_action( 'deleted_post', array($this, 'flush_widget_cache') );
        add_action( 'switch_theme', array($this, 'flush_widget_cache') );
    }

    public function widget($args, $instance) {
        $cache = array();
        if ( ! $this->is_preview() ) {
            $cache = wp_cache_get( 'wpsites_widget_recent_posts', 'widget' );
        }

        if ( ! is_array( $cache ) ) {
            $cache = array();
        }

        if ( ! isset( $args['widget_id'] ) ) {
            $args['widget_id'] = $this->id;
        }

        if ( isset( $cache[ $args['widget_id'] ] ) ) {
            echo $cache[ $args['widget_id'] ];
            return;
        }

        ob_start();

        $title = ( ! empty( $instance['title'] ) ) ? $instance['title'] : __( 'Recent Posts' );

        /** This filter is documented in wp-includes/default-widgets.php */
        $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );

        $number = ( ! empty( $instance['number'] ) ) ? absint( $instance['number'] ) : 5;
        if ( ! $number )
            $number = 5;
        $show_date = isset( $instance['show_date'] ) ? $instance['show_date'] : false;


        $r = new WP_Query( apply_filters( 'widget_posts_args', array(
            'posts_per_page'      => $number,
            'no_found_rows'       => true,
            'post_status'         => 'publish',
            'post_type'           => array('post', 'portfolio',
            'ignore_sticky_posts' => true
        ) ) ) );

        if ($r->have_posts()) :
?>
        <?php echo $args['before_widget']; ?>
        <?php if ( $title ) {
            echo $args['before_title'] . $title . $args['after_title'];
        } ?>
        <ul>
        <?php while ( $r->have_posts() ) : $r->the_post(); ?>
            <li>
                <a href="<?php the_permalink(); ?>" rel="nofollow noreferrer noopener"><?php get_the_title() ? the_title() : the_ID(); ?></a>
            <?php if ( $show_date ) : ?>
                <span class="post-date"><?php echo get_the_date(); ?></span>
            <?php endif; ?>
            </li>
        <?php endwhile; ?>
        </ul>
        <?php echo $args['after_widget']; ?>
<?php

        wp_reset_postdata();

        endif;

        if ( ! $this->is_preview() ) {
            $cache[ $args['widget_id'] ] = ob_get_flush();
            wp_cache_set( 'wpsites_widget_recent_posts', $cache, 'widget' );
        } else {
            ob_end_flush();
        }
    }

    public function update( $new_instance, $old_instance ) {
        $instance = $old_instance;
        $instance['title'] = strip_tags($new_instance['title']);
        $instance['number'] = (int) $new_instance['number'];
        $instance['show_date'] = isset( $new_instance['show_date'] ) ? (bool) $new_instance['show_date'] : false;
        $this->flush_widget_cache();

        $alloptions = wp_cache_get( 'alloptions', 'options' );
        if ( isset($alloptions['wpsites_recent_posts']) )
            delete_option('wpsites_recent_posts');

        return $instance;
    }

    public function flush_widget_cache() {
        wp_cache_delete('wpsites_widget_recent_posts', 'widget');
    }

    public function form( $instance ) {
        $title     = isset( $instance['title'] ) ? esc_attr( $instance['title'] ) : '';
        $number    = isset( $instance['number'] ) ? absint( $instance['number'] ) : 5;
        $show_date = isset( $instance['show_date'] ) ? (bool) $instance['show_date'] : false;
?>
        <p><label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label>
        <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo $title; ?>" /></p>

        <p><label for="<?php echo $this->get_field_id( 'number' ); ?>"><?php _e( 'Number of posts to show:' ); ?></label>
        <input id="<?php echo $this->get_field_id( 'number' ); ?>" name="<?php echo $this->get_field_name( 'number' ); ?>" type="text" value="<?php echo $number; ?>" size="3" /></p>

        <p><input class="checkbox" type="checkbox" <?php checked( $show_date ); ?> id="<?php echo $this->get_field_id( 'show_date' ); ?>" name="<?php echo $this->get_field_name( 'show_date' ); ?>" />
        <label for="<?php echo $this->get_field_id( 'show_date' ); ?>"><?php _e( 'Display post date?' ); ?></label></p>
<?php
    }
}

Register the new custom recent posts widget

function wpsites_widgets_init() {
    if ( !is_blog_installed() )
        return;

    register_widget('WPSites_Recent_Posts');
    do_action( 'widgets_init' );
}

add_action( 'init', 'wpsites_widgets_init', 2 );

The code includes a modified WP_Query which includes an array for post types including the portfolio CPT which you can rename to match your custom post type.

Here’s the line of code that needs to be modified:

'post_type'           => array('post', 'portfolio',

Method 7

It’s 2020 and I came here to find a solution to the “10 most recent custom post type XYZ”. I found the plugin that does that, and more.

Custom Post Type Widgets extends the usual widget standard post functionalities (most recent, monthly archives, used categories, recent comments, search, calendar) to custom post types.

You select the widget you need (in my case “most recent”) and you get first a select where you specify the custom post type the widget must target. Default choice is good old ‘post’, so this plugin is a substitute of the vanilla WP post-related widgets.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Comment