I have a question today. I am looking for a solution to make sticky post available on my website. But, i just got it work on homepage with the code below.

function wpb_latest_sticky() { 

/* Get all sticky posts */
$sticky = get_option( 'sticky_posts' );

/* Sort the stickies with the newest ones at the top */
rsort( $sticky );

/* Get the 5 newest stickies (change 5 for a different number) */
$sticky = array_slice( $sticky, 0, 5 );

/* Query sticky posts */
$the_query = new WP_Query( array( 'post__in' => $sticky, 'ignore_sticky_posts' => 1 ) );
// The Loop
if ( $the_query->have_posts() ) {
    $return .= '<ul>';
    while ( $the_query->have_posts() ) {
        $return .= '<li><a href="' .get_permalink(). '" title="'  . get_the_title() . '">' . get_the_title() . '</a><br />' . get_the_excerpt(). '</li>';

    $return .= '</ul>';

} else {
    // no posts found
/* Restore original Post Data */

return $return; 

add_shortcode('latest_stickies', 'wpb_latest_sticky');

I was thinking to make the sticky post to be displayed on search, tag, and category as well.

Any solution? Thanks!

  • Can you clarify your question? There should only be only one question per post.
    – jetlej
    Apr 8, 2015 at 5:23
  • Hi @jetlej, updated the question.
    – Jornes
    Apr 8, 2015 at 5:40

3 Answers 3


This same exact question was asked earlier this week or over the weekend, and it had me thinking. Here is the idea that I came up with.

If you look at the source code of the WP_Query class, you will see that sticky posts is only added to the first page of the home page. There is also no filter supplied to change this behavior in order to set the required templates according to your liking.

There are many ways to display sticky posts on other templates through widgets, custom queries, shortcodes (which I will not recommend due to the fact that using do_shortcode() is slower that using the function itself) or custom functions where you need to display them. I have opted to go with using the_posts filter and pre_get_posts action.


  • Get the post ID's saved as sticky posts with get_option( 'sticky_posts' )

  • Remove this posts from the main query with pre_get_posts. As stickies are included on the home page, we will exclude the home page. We will also exclude normal pages

  • Inside a custom function, run a custom query with get_posts to get the posts set as sticky posts.

  • Merge the returned array of sticky posts with the current returned posts of the main query with array_merge

  • Hook this custom function to the_posts filter

Here is the code: (Requires PHP 5.4+)

add_action( 'pre_get_posts', function ( $q )
    if (    !is_admin()
         && $q->is_main_query()
         && !$q->is_home()
         && !$q->is_page()
    ) {

        $q->set( 'post__not_in', get_option( 'sticky_posts' ) );

        if ( !$q->is_paged() ) {
            add_filter( 'the_posts', function ( $posts )
                $stickies = get_posts( ['post__in' => get_option( 'sticky_posts' ), 'nopaging' => true] );

                $posts = array_merge( $stickies, $posts );

                return $posts;

            }, 10, 2);

  • I wonder if you could simplify it and call the filter within the action? - i.e. to reuse the conditions to DRY-it ;-)
    – birgire
    Apr 8, 2015 at 18:53
  • 1
    Seems to work with quick testing. Thanks for the suggestion :-) @birgire Apr 8, 2015 at 19:16
  • @Pieter Goosen, thanks! I already added your code to my functions.php and it worked. But, i noticed an issue after i applied your code to my functions.php. I have a post which has been chosen as sticky post under a category name example. When i was at the category page of example, it says no posts found for the said category. But the post is displaying there as sticky post. But i don't mind the same post display as duplication post. Please let me know if you do not understand what i'm trying to explain the issue.
    – Jornes
    Apr 9, 2015 at 4:25
  • What I have done here is to exclude the sticky post from the main loop so that you will not have duplication. This is done in this line $q->set( 'post__not_in', get_option( 'sticky_posts' ) );. You can remove this line in my current answer, or if you have the old code with separate action and filter, remove the pre_get_posts action completely, but this will show your post twice. Apr 9, 2015 at 5:45

Pieter's answer does work, but it displays all sticky posts even on pages when we want to display filtered posts (to a given category for instance).

The following works for me. Adding this to functions.php only displays stickies within the subset of filtered posts :

add_filter('the_posts', 'bump_sticky_posts_to_top');
function bump_sticky_posts_to_top($posts) {
    $stickies = array();
    foreach($posts as $i => $post) {
        if(is_sticky($post->ID)) {
            $stickies[] = $post;
    return array_merge($stickies, $posts);

(Credits : http://pastebin.com/Y5jVrKg4, with a slight change to prevent a Warning: array_merge() [function.array-merge]: Argument #1 is not an array on line 9 error)

However, there is a big flaw. It will not promote a sticky post on top if this sticky post in not to be present in the current page.

A system that would set the .sticky CSS class to the sticky posts would also be nice.


I needed some modifications(original see Pieters answer) to get it to work just on category pages. This solution will display all sticky posts of the current category (and child categories), but not on search, tag and single pages:

add_action( 'pre_get_posts', function ( $q ) {
    if ( !is_admin()
         && $q->is_main_query()
         && !$q->is_home()
         && !$q->is_tag()
         && !$q->is_search()
         && !$q->is_page()
         && !$q->is_single()
    ) {

      function is_existing_cat($q_catName) {
           $existingCats = get_categories();
           $isExistingCat = false;

           foreach ($existingCats as $cat) {
                if ($cat->name == $q_catName) {
                     $isExistingCat = true;

           return $isExistingCat;

        if ($q->is_category() && !$q->is_paged() && is_existing_cat($q->query['category_name'])) {

            $q->set( 'post__not_in', get_option( 'sticky_posts' ) );

            add_filter( 'the_posts', function ( $posts ) {
                $catName = get_category(get_query_var('cat'))->name;
                $catID = get_cat_ID($catName);

                if ( !empty(get_option( 'sticky_posts' )) ) {
                    $stickies = get_posts( [
                        'category' => $catID,
                        'post__in' => get_option( 'sticky_posts' )
                    ] );

                    $posts = array_merge( $stickies, $posts );

                return $posts;

            }, 10, 2);

I added a function to check if the category is an existing category because I run into the issue that I was directed to the category.php instead of the expected 404.php. Links like /existing-cat/not-existing-post worked but links like /not-existing-post-or-page-or-cat didn't.

If you need a css class on that post you add this code to the PHP part of the template that displays the content of the post (in twentysixteen for example: template-parts/content.php)

if (is_sticky()) {
    $stickyClass = 'sticky';
} else {
    $stickyClass = '';

Than you add this class to the css classes of the post post_class($stickyClass); (this function will add the class attribute to the HTML element with a few classes and your added class). You may find this function already located at the article:

<article id="post-<?php the_ID(); ?>" <?php post_class($stickyClass); ?>>
</article><!-- #post-## -->

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.