Insights · What the free audit finds · 5 minutes
Your Media Library Is Public
The media library is not a private folder, in WordPress or anywhere else. What that means for the drafts and packets that get uploaded to grab a link, why switching the API off is the wrong fix, and how to mark a file private without breaking anything.
Public by default, in every system
A media library is a folder on a web server. Uploading a file publishes it. That is true of WordPress, Drupal, and a static site alike; the difference is only how easy the listing is to find.
In WordPress, the listing is one address away. The REST API at /wp-json/wp/v2/media returns every file in the library to anyone who asks, with no login. Drupal's JSON:API does the equivalent. This is not a flaw. The same API powers the block editor, galleries, mobile apps, and the integrations your team already uses.
Where the surprise comes from
People carry a Dropbox model into the library: a private folder where you park a file to grab a link. So the library fills with what was never meant to be broadcast. Draft three of a paper whose draft four is the one on the page. A board packet uploaded so one person could download it. A report scheduled for next week, uploaded today.
Two facts get confused. A file that no page links to is unlinked, not private. A file that is hard to find is hidden, not private. The API lists both, and a search engine that finds the address will index both.
Why switching the API off is the wrong fix
The tempting response is to disable the REST API, or to hide the media endpoint. That is security by obscurity: it stops one way of listing files while every direct address keeps working, and it breaks the editor features and integrations that depend on the API.
The real question is not whether the library can be listed. It is which files in it should be public. Once you answer that per file, the API stops being a problem and goes back to being infrastructure.
How to handle it in WordPress
First, look. Open /wp-json/wp/v2/media?media_type=application on your own site and read what comes back. That is the list a stranger sees.
Second, decide per file. Public files stay. Superseded drafts and anything never meant to publish get deleted; one released version per paper. Files that need to exist but should not be discoverable get marked private.
Third, mark them. We wrote a small plugin for exactly this, LFW Private Media: a Private checkbox on every attachment, plus bulk actions in the Media list. A private file disappears from the public API, site search, attachment pages, and sitemaps, while your editors still see everything. Download it at lfw.com/dl/lfw-private-media.zip. If you would rather see the mechanism than install anything, the whole idea fits in one small file:
<?php
/**
* Plugin Name: Private media (minimal)
* Keeps attachments carrying the meta key _lfw_private out of the public REST API.
* Editors (anyone who can upload files) still see them.
*/
// The collection: /wp-json/wp/v2/media
add_filter( 'rest_attachment_query', function ( $args ) {
if ( current_user_can( 'upload_files' ) ) { return $args; }
$args['meta_query'] = isset( $args['meta_query'] ) ? $args['meta_query'] : array();
$args['meta_query'][] = array( 'key' => '_lfw_private', 'compare' => 'NOT EXISTS' );
return $args;
} );
// A single item: /wp-json/wp/v2/media/123
add_filter( 'rest_request_before_callbacks', function ( $response, $handler, $request ) {
if ( current_user_can( 'upload_files' ) ) { return $response; }
if ( preg_match( '#^/wp/v2/media/(\d+)$#', $request->get_route(), $m )
&& '1' === get_post_meta( (int) $m[1], '_lfw_private', true ) ) {
return new WP_Error( 'rest_post_invalid_id', 'Invalid post ID.', array( 'status' => 404 ) );
}
return $response;
}, 10, 3 );
// Mark a file private: add the meta key _lfw_private with the value 1.
// With WP-CLI: wp post meta update <attachment-id> _lfw_private 1 One honest limit, for either version. A file that is already public at a direct address stays reachable by anyone who has that address. Marking it private stops strangers from discovering it. It does not put the file behind a login. Anything genuinely confidential does not belong in the uploads folder at all; it belongs behind an access-controlled download.
If you are a client
You do not need to install anything. Open a request in your portal and we will audit the library with you, delete what should go, mark private what should stay, and install the plugin. It is a small task under the retainer, not a project.
If you are not a client yet, the free audit will tell you how many files your site lists to strangers today. That is usually the moment the Dropbox model ends.