I have a need to limit items in the media library for users without a specific capability. There are a lot of examples out there for how to limit them to only items that a given user has uploaded, but my need is more complex than that.

I need to limit media items for users without a specific capability to those attachments that are NOT:

  1. header images (i.e., set in Appearance > Headers)
  2. page thumbnails (i.e., featured images)

Below is the solution I've come up.

 * restrict media items available to users without cap=mycap to those that are NOT:
 * 1. theme headers (i.e., set in Appearance > Headers)
 * 2. page thumbnails (i.e., featured images)
add_action ('pre_get_posts', 'restrict_media') ;
restrict_media ($query)
    if (!is_admin () || $query->get ('post_type') != 'attachment' ||
            current_user_can ('mycap')) {
        return ;

    // get our theme's headers (i.e., set in Appearance > Headers)
    $args = array (
        'post_type' => 'attachment',
        'post_status' => array ('inherit', 'private'),
        'posts_per_page' => -1,
        'meta_query' => array (
            'relation' => 'AND',
            array (
                'key' => '_wp_attachment_context',
                'value' => 'custom-header',
            array (
                'key' => '_wp_attachment_is_custom_header',
                'value' => 'antelopevalley',
        ) ;
    // remove ourself from pre_get_posts to avoid infinite regression
    remove_action ('pre_get_posts', array ($this, 'restrict_media')) ;
    $headers = new WP_Query ($args) ;
    // get the IDs of the headers
    $header_ids = array_map (function ($p) { return ($p->ID) ; }, $headers->posts) ;

    // add ourself back to pre_get_posts for subsequent queries
    add_action ('pre_get_posts', array ($this, 'restrict_media')) ;

    // get posts with thumbnails
    $args = array (
        'post_type' => 'page',
        'post_status' => array ('publish', 'pending', 'draft', 'private'),
        'posts_per_page' => -1,
        'meta_query' => array (
            array (
                'key' => '_thumbnail_id',
                'compare' => 'EXISTS',
        ) ;
    $with_thumbnail = new WP_Query ($args) ;
    // get the IDs of the thumbnails
    $thumbnail_ids = array_map (function ($p) { return (get_post_meta ($p->ID, '_thumbnail_id', true)) ; },
        $with_thumbnail->posts) ;

    // exclude all theme headers and thumbnails
    $query->set ('post__not_in', array_merge ($header_ids, $thumbnail_ids)) ;

    return ;

The above solution works; however, it seems overly complex to me!

I'm wondering if anyone can suggest a simpler solution.

Note: I've tried the post_{where,join} filters before for other needs and have found them too complex to maintain so I didn't even try to figure out a solution to this problem using them, but I'm more than open to one that uses them

1 Answer 1


Good code. I think you could simplify by making direct SQL queries instead:

-- Featured Images
SELECT `meta_value` FROM `wp_postmeta` WHERE `meta_key` = '_thumbnail_id';
--> $thumbnail_ids

-- Header Images
SELECT `post_id` FROM `wp_postmeta` WHERE `meta_key` = '_wp_attachment_context' and `meta_value` = 'custom-header';
--> $header_ids

Also, with your method, I think that if you call WP_Query with suppress_filters => true (or use get_posts() instead; it suppresses filters by default) you can avoid having to remove your own filter.

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.