Protect file downloads with Ultimate Member WordPress plugin

Ultimate Member is one of the premier WordPress plugins for membership management, but it is lacking one feature that many users might find handy: the ability to restrict file downloads to specific roles.

The developers of Ultimate Member have indicated that protecting content isn’t high on their list of features to implement, so for the time being you’ll have to improvise. The good news is that it’s not too difficult of a task – as long as your requirements are relatively simple.

For my particular use case, I need to allow only members who are assigned to a particular community role (or administrators) to download PDF files from a specific folder.

My approach is pretty straightforward:

  1. Create a php script that uses the Ultimate Member API to check that the current user is in a role that matches the download location hard coded in the script, and then return the contents of the file with the appropriate http headers
  2. Prevent normal http downloads from the protected folder with a .htaccess file

Limitations of my approach:

  1. Files need to be uploaded to a specific directory using FTP as you cannot choose the directory from the WordPress media manager
  2. You’ll need multiple php scripts depending on the number of roles -> download folder location pairs you have.
  3. There is no UI for controlling these permissions – it’s all in the scripts

So, it’s a super simple approach with some limitations – but it’s good enough for me, for now.

Okay, show me how

Let’s start with the download folder on the server. In my case, I’ve created a new directory under the wp-content/uploads directory called paid-content:


In that folder, I’ve placed a .htaccess file that prevents downloads:

order deny,allow
deny from all

Then, I’ve created a new php script in my root directory:


require( dirname( __FILE__ ) . '/wp-blog-header.php' );
global $ultimatemember;
// The path to the protected folder relative to the wordpress uploads directory
// e.g. /wp-content/uploads/<$protectedDir>
$protectedDir = 'paid-content';
// The required member role (slug of the role)
$requiredRole = 'paidsubscriber';
// Map file extensions to mime types (TODO: better implementation?)
$extMap = array(
    'pdf' => 'application/pdf',
    'zip' => 'application/zip'
// ----- you shouldn't need to modify anything below here -------
// Get the user id of the currently logged in user. If no user is logged in, return a 404
$userId = um_profile_id();
if (!$userId) {
// Check the status of the user
$isAdmin = um_user('administrator') === true; // is the user an admin?
$isApproved = um_user('status') === 'approved'; // is the user approved?
$userRole = um_user('role_name'); // the slug of the community role assigned to the user
// Check if the user is allowed to access the requested folder
$allow = (($isAdmin === true || $userRole === $requiredRole) && $isApproved === true);
if ($allow !== true) {
// Ensure that the file name provided really and truly exists in the directory we expect
$uploadBase = preg_replace('/[\\/\\\]/', DIRECTORY_SEPARATOR, wp_upload_dir()['basedir']);
$docName = $_GET['f'];
$docPath = realpath(join('/', array($uploadBase, $protectedDir, $docName)));
if (strpos($docPath, $uploadBase) !== 0 || !is_file($docPath)) {
if ($fd = fopen($docPath, "r")) {
    // Try to determine the mime type based on the file extension
    $pathInfo = pathinfo($docPath);
    $ext = strtolower($pathInfo["extension"]);
    if (isset($extMap[$ext])) {
        $contentType = $extMap[$ext];
    else {
        $contentType = 'application/octet-stream'; // TODO: Just a hack fallback
    // Set http status to 200 (OK), otherwise it will default to 404
    header("Content-type: $contentType");
    header("Content-Disposition: attachment; filename=\"".$pathInfo["basename"]."\"");
    header("Content-Transfer-Encoding: chunked");
    header("Content-length: " . filesize($docPath));
    header("Cache-control: private");
    // Output the contents of the file in 2048 byte chunks
    while (!feof($fd)) {
        echo fread($fd, 2048);
    fclose ($fd);
else {
    wp_die('Oops, there was a problem downloading your file. Please try again.');
* Output the WordPress 404 page
function exitWith404() {

How do I use it?

Well that’s the easy part 🙂 So say you have uploaded a document to this location:


To create a protected link so that only members in the Paid Subscriber role (slug = paidsubscriber) can download it, simply create the link like this:

<a href="/paid-download.php?f=VeryValuableInfo.pdf">Download some valuable info!</a>

That’s all there is to it! Anyone who is not logged in (and who is not in the Paid Subscriber role) that tries to access this link will see the standard 404 WordPress error page for your site.

One Comment

  1. Josef

    May 21, 2021 at 12:48 pm

    Thanks for your script, it made me find the way of doing this. By the way, you don’t really need to access the Ultimate Member api. You can do it with only the WordPress API and it will work even with other plugins like the Ultimate Member. I’m using this on my script:

    $auth_user = wp_get_current_user();
    $auth_user_login = ‘Anonymous’;
    $auth_user_role = ‘Anonymous’;
    if ($auth_user->ID != 0) {
    $auth_user_login = $auth_user->user_login;
    $auth_user_role = $auth_user->roles[0];

    The only part I’m not sure of is this one:
    $isApproved = um_user(‘status’) === ‘approved’; // is the user approved?

    I don’t know really if an user with a non approved status will be able to login. If so, then I guess you will still need that line.

    By the way, I ended using this script:

    I think that using RewriteRules are even better than disabling the entire site. With those RewriteRules you can even do things like:
    RewriteRule ^wp-content/uploads/(ultimatemember/.*)$ dl-file.php?file=$1 [QSA,L]

    which will protect only the files inside the ultimatemember folder and the rest of the website will remain public.

    Best regards


Leave a Reply

Your email address will not be published. Required fields are marked *