Easy file downloads for WordPress
Secure File Access creates protected download links using the [file_access] shortcode. Downloads can use a normal HTTP or HTTPS URL, a normalized ZIP archive from a public or private GitHub Release, or an explicitly named uploaded ZIP asset.
Visitors must be logged in, administrators always have access, and other users receive access when they match any configured WordPress role, purchased any configured WooCommerce product, or have an active or pending-cancel WooCommerce subscription for any configured product ID.
Default product IDs, subscription product IDs, WordPress roles, the download button label, and frontend error messages can be configured under Settings > Secure File Access. Non-empty shortcode products, roles, and subscriptions values override their corresponding defaults for individual downloads.
WooCommerce and WooCommerce Subscriptions are optional. Product purchase checks require WooCommerce, while subscription checks require WooCommerce Subscriptions. If no product IDs, roles, or subscription product IDs are configured, only administrators receive access. Normal file URLs are sanitized and unsupported protocols are rejected before download links are rendered.
Authorized downloads use a short-lived local ?download= link instead of placing the destination URL or GitHub token in the page HTML. Each link is tied to the current user, expires after 15 minutes, rechecks access when requested, and becomes invalid after a successful redirect or local GitHub ZIP preparation. Protected download responses are marked private and non-cacheable and do not forward referrer information.
The GitHub Access tab stores one personal access token per WordPress site. GitHub shortcodes use the latest published stable release by default and can optionally require an exact release tag or uploaded ZIP asset. Generated archives are temporarily downloaded, rebuilt with the repository name as the ZIP filename and the repository contents directly at the ZIP root, and streamed through WordPress. Explicitly named uploaded assets redirect directly when GitHub supplies a temporary URL or stream unchanged through WordPress when GitHub returns 200 OK.
For example, github_repo="littlebizzy/private-plugin" without github_asset produces:
private-plugin.zip
├── private-plugin.php
└── ...
Normal URL usage:
[file_access url="https://example.com/plugin.zip"]
WooCommerce product purchase access:
[file_access url="https://example.com/plugin.zip" products="123,456"]
WooCommerce subscription purchase access with latest GitHub Release:
[file_access github_repo="littlebizzy/private-plugin" subscriptions="123"]
Override the configured access defaults and GitHub release selection:
[file_access github_repo="littlebizzy/private-plugin" github_tag="v2.0.0" github_asset="private-plugin.zip" label="Download Plugin" products="123,456" subscriptions="789" roles="customer,shop_manager"]
- flattens normalized generated archives so repository files and directories sit directly at the ZIP root while the filename remains
repository.zip, avoiding duplicate folders after extraction - applies the same flat package layout through both
ZipArchiveand WordPress's bundled PclZip fallback
- fixes private source ZIP creation by passing a trailing-slash workspace path to
wp_tempnam(), preventing valid generated archives from failing the workspace containment check - accepts direct
200 OKGitHub release asset responses by streaming the unchanged ZIP through the private workspace while preserving temporary redirect handling - refuses ZIP streaming when response headers were already sent or an active output buffer cannot be removed, preventing corrupted or buffered file responses
- rebuilds generated GitHub release archives with the repository name as the ZIP filename and internal root folder
- streams normalized generated archives through WordPress using temporary disk files without caching the rebuilt package
- validates generated archives for one safe root directory, unsafe paths, and symbolic links before rebuilding them
- separates
sourceandpackagestaging directories so repository names cannot collide with internal paths - requires private
0700workspace permissions before generated archive processing - streams generated source archives with
wp_safe_remote_get()before validation and extraction - uses
wp_safe_remote_get()for GitHub release metadata and validates any followed redirects - preserves direct GitHub asset redirects, including
302 Found, for explicitly named uploaded ZIP assets
- downloads GitHub's generated ZIP archive for the selected release when
github_assetis omitted - fails when a specified release tag or named uploaded ZIP asset does not exist, without falling back
- preserves authenticated temporary redirects for generated archives and uploaded assets
- removes the repeated Access Defaults, Error Messages, and GitHub Access headings from inside their matching settings tabs
- renames the Access Defaults field label from Default Download Button Label to Default Button Label
- moves the GitHub personal access token link into the GitHub Access introduction and removes the duplicate link beside the token field
- changes the settings tabs from an
h2wrapper to a labellednavand addsaria-current="page"to the active tab - changes each settings section heading from
h3toh2and updatesaria-currentwhen tabs switch without reloading
- preserves the selected settings tab after saving settings or removing the GitHub token
- adds validated query-based tab URLs using
defaults,errors, andgithub, with invalid values falling back to Access Defaults
- adds a direct link from the GitHub Access settings tab to GitHub's personal access token page, opening safely in a new browser tab
- redirects after saving settings or removing the GitHub token to prevent browser form resubmission prompts while preserving standard WordPress success notices
- adds WooCommerce product purchase access to URL and GitHub downloads through the
productsshortcode attribute and a Default Product IDs setting - grants logged-in users access when their account purchased any listed product, using OR logic alongside administrator, role, and eligible subscription access without matching guest orders by billing email
- stores product rules in short-lived protected tokens and rechecks the customer's purchase access when each download link is opened
- distinguishes rejected tokens, GitHub rate limits, access failures, missing resources, and temporary GitHub server failures without exposing API response bodies
- validates temporary GitHub asset redirects as safe HTTPS URLs with a valid host and no embedded credentials before redirecting the browser
- adds
github_repo,github_tag, andgithub_assetsource attributes to the existing[file_access]shortcode while preserving normalurldownloads - resolves the latest published stable GitHub Release by default, with optional exact stable release tags
- automatically selects a single uploaded ZIP asset and requires an exact
github_assetfilename when multiple ZIP assets exist - uses the saved
sfa_github_tokenonly in server-side GitHub API requests and redirects authorized users to GitHub's temporary asset URL without proxying or streaming files through PHP
- adds root-level
uninstall.phpto delete the per-sitesfa_github_tokenoption when the plugin is deleted, including every site on Multisite, while preserving all other plugin options - renames
protected-downloads.phptodownloads.phpand updates the loader path insecure-file-access.phpwithout changing runtime download behavior
- adds a GitHub Access settings tab for one personal access token per WordPress site
- stores the token in the non-autoloaded
sfa_github_tokenoption without displaying the saved value - preserves the configured token when the masked token field is blank and supports explicit replacement or removal
- prepares credential storage for future private repository downloads without making GitHub API requests
- replaces destination URLs in rendered shortcode HTML with 64-character, user-bound transient download tokens
- expires protected download links after 15 minutes and invalidates them after a successful redirect
- rechecks login, administrator capability, configured roles, and active or pending-cancel WooCommerce subscriptions when each download is requested
- sends private no-store cache headers and a no-referrer policy before redirecting to the sanitized HTTP or HTTPS destination
- preserves access for pending-cancel WooCommerce subscriptions until the prepaid term ends
- sanitizes file URLs and rejects unsupported protocols before rendering download links
Tested up to:bumped to 7.0
- supports shortcode
[file_access]for secure file download links with role and WooCommerce subscription-based access control - creates admin settings page under Settings with tabbed navigation for Access Defaults and Error Messages
- integrated optional WooCommerce Subscriptions check for active subscription based file access
- administrators always have access to files displayed on the frontend
- default roles and WooCommerce subscription settings are blank
- default label for file download buttons is "Download File"
- supports customizable error messages for “no access”, “invalid URL”, and “not logged in” states
- supports localization/translation
- supports Git Updater
- supports PHP 7.0 to 8.4
- supports Multisite