LFW

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.

By Leland Fiegel · Web operations, LFW · September 2, 2026

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.

Keep reading

September 2, 2026 · 7 minutes

Why the Page Jumps While It Loads

What layout shift is and why it registers as brokenness, the four causes behind nearly every case (images without dimensions, embeds without reserved space, fonts swapping, banners injected late), and the exact fixes, including where WordPress already knows every image's width and height.

September 2, 2026 · 7 minutes

Phones Download Your Desktop Images

Why a 2.6 MB hero reaches a phone that needs 33 KB, how srcset and sizes let the browser choose, when you actually need the picture element, what WebP buys you, and where WordPress themes quietly undo the work it already does for you.

Prefer to write?

Tell us what needs to work better.

Slow, fragile, hard to edit, missing a workflow. Say it plainly, and you'll get a straight answer, not a ticket number.

You'll get a straight answer, usually the same day.