I've got a custom WP_Query loop based of meta_value variables:

$meta_cat = get_sub_field('category');
$posts = new WP_Query( array(
    'cat' => $meta_cat,
    'posts_per_page' => get_sub_field('recent_ppp'),
) );

But somehow when I'll make a post sticky it isn't shown as the first item in the while loop.

I know ignore_sticky_posts is default set to false so I won't bother changing it. Also tried looking in de order_by documentation if some value is related to sticky post but it isn't.

Although The order_by => 'post__in' might be close:

Preserve post ID order given in the post__in array

But when I set post__in => get_options('sticky_posts') only the sticky post is showing.

Is this even possible without building two custom loops (one handeling the sticky posts and the other for the other posts? That would be really bad, because not all categories contain sticky posts.

  • Not the answer, but you have a typo in $$meta_cat Commented Mar 2, 2016 at 11:18
  • I've changed a few thing for this post. So just a little typo, thanks though ;)
    – ronnyrr
    Commented Mar 2, 2016 at 11:20

1 Answer 1


If you look ate the source code where stickies are included, we find the following condition before WP_Query carries on to include sticky posts

if (    $this->is_home 
     && $page <= 1 
     && is_array($sticky_posts) 
     && !empty($sticky_posts)  
     && !$q['ignore_sticky_posts'] 
) {

The big game player is the is_home condition. Conditionals inside WP_Query is set according to the arguments passed to it, and not the page where the query happens. Because you pass cat as an argument, is_home will be set to false, and is_category will be set to true. Because is_home is set to false, the above condition fails and stickies are not included.


What we can do, is to manually set is_home to true via pre_get_posts. All we need to do is to pass 'wpse_is_home' => true, to your custom WP_Query instance

Then we run our pre_get_posts action as follow

add_action( 'pre_get_posts', function ( $q )
    if ( true === $q->get( 'wpse_is_home' ) ) 
        $q->is_home = true;


We would probably only want to show stickies which has relevance to the queried category. In that case, we should remove stickies that does not belong to the specific queried category

We can try the following, still inside our pre_get_posts action

add_action( 'pre_get_posts', function ( $q )
    remove_action( current_action(), __FUNCTION__ );

    if ( true !== $q->get( 'wpse_is_home' ) )

    // Make sure get_sub_field exists, if not, bail
    if ( !function_exists( 'get_sub_field' ) )

    // Lets query everything to keep code clean
    $meta_cat = get_sub_field( 'category' ); // Copied from your code
    $meta_cat = filter_var( $meta_cat, FILTER_VALIDATE_INT );
    // Make sure we have a value, if not, bail
    if ( !$meta_cat )

    $q->set( 'cat', $meta_cat );

    // Set pagination if recent_ppp exists
    $ppp = get_sub_field( 'recent_ppp');
    $ppp = filter_var( $ppp, FILTER_VALIDATE_INT );
    if ( $ppp )
        $q->set( 'posts_per_page', $ppp );      

    // Set is_home to true 
    $q->is_home = true;

    // Get all stickies
    $stickies = get_option( 'sticky_posts' );
    // Make sure we have stickies, if not, bail
    if ( !$stickies )

    // Query the stickies according to category
    $args = [
        'post__in'            => $stickies,
        'posts_per_page'      => -1,
        'ignore_sticky_posts' => 1, // Ignore stickies
        'cat'                 => $meta_cat,
        'orderby'             => 'post__in',
        'order'               => 'ASC',
        'fields'              => 'ids' // Get only post ID's
    $valid_sticky_ids = get_posts( $args );

    // Make sure we have valid ids
    if ( !$valid_sticky_ids ) {
        $q->set( 'post__not_in', $stickies );

    // Remove these ids from the sticky posts array
    $invalid_ids = array_diff( $stickies, $valid_sticky_ids );

    // Check if we still have ids left in $invalid_ids
    if ( !$invalid_ids )

    // Lets remove these invalid ids from our query
    $q->set( 'post__not_in', $invalid_ids );

Before we run our custom query, we would need to make sure that get_sub_field exists, and most importantly, that get_sub_field( 'category' ) is set and valid. This will avoid catastrophic failure which will lead to all posts being returned, regardless.

Your WP_Query can look something like this as we only need to pass 'wpse_is_home' => true:

if (    function_exists( 'get_sub_field' )
     && filter_var( get_sub_field( 'category' ), FILTER_VALIDATE_INT )
) {
    $args = [
        'wpse_is_home' => true
    $posts_array = new WP_Query( $args ); // DO NOT USE $posts

    // Run your loop as normal


Code is tested and working as expected

  • 1
    Please see my edit. I hope this is what you need Commented Mar 2, 2016 at 12:18
  • Yesterday I'll already implemented the basic pre_get_posts action, which also did work. I will try the advanced one this afternoon. Thanks for helping out! :)
    – ronnyrr
    Commented Mar 3, 2016 at 11:36
  • My pleasure, glad the first edition works. Let me know if you encounter any issues with the advance code. I did test it, but my local install is quite small, so it tends to miss stuff. Enjoy and good luck on your project ;-) Commented Mar 3, 2016 at 11:44

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.