Use this guide when a shortcode does not render a usable download link or a protected link stops with an error.
Confirm that:
- the visitor is logged in
- exactly one download source is configured
- the user currently qualifies through
manage_options, an allowed role, a recorded WooCommerce purchase, or an eligible WooCommerce subscription - required plugins are active for the configured access method
- the page was reloaded after changing settings, purchases, roles, subscriptions, release tags, uploaded assets, or GitHub credentials
Protected links are tied to one user, expire after 15 minutes, and become invalid after a successful redirect or local GitHub ZIP preparation. Reload the page to create a new link when the user still has access.
Invalid download source provided. means the shortcode does not define exactly one valid source.
Check for:
- no
urlorgithub_repo - both
urland any GitHub source attribute github_tagorgithub_assetwithout a validgithub_repo- a repository value that is not in
owner/repositoryformat
Use either:
[file_access url="https://example.com/plugin.zip"]
or:
[file_access github_repo="littlebizzy/private-plugin"]
The configured Invalid File URL message is used only when a non-empty url value cannot be accepted as an HTTP or HTTPS destination.
Confirm that the URL:
- begins with
http://orhttps:// - is complete and correctly quoted in the shortcode
- does not use another protocol such as
ftp:,file:, orjavascript:
Missing or conflicting source attributes use the separate built-in invalid-source message.
Access methods use OR logic. A logged-in user needs only one of the following:
- the
manage_optionscapability - an allowed WordPress role
- a recorded purchase of an allowed WooCommerce product
- an active or pending-cancel subscription for an allowed subscription product ID
A non-empty shortcode products, roles, or subscriptions value replaces the corresponding saved default for that download. It is not merged with the default.
Access is checked when the shortcode is displayed and checked again when the protected link is opened. Reload the page after changing the user's access.
Product purchase checks require WooCommerce and use its native purchase history check.
Confirm that:
- WooCommerce is active
- the configured product ID is correct
- WooCommerce considers the order paid
- the order belongs to the same logged-in WordPress user account
Secure File Access supplies the logged-in user ID and does not match guest purchases by billing email. A guest order or an order attached to another account will not grant access.
Secure File Access does not add separate refund rules, download limits, license keys, or guest-purchase matching.
Subscription checks require WooCommerce Subscriptions.
Confirm that:
- WooCommerce and WooCommerce Subscriptions are active
- the configured subscription product ID is correct
- the subscription belongs to the logged-in user
- the subscription status is
activeorpending-cancel
Other subscription statuses do not grant access.
GitHub Release downloads require a token configured under Settings > Secure File Access > GitHub Access.
For private repositories:
- prefer a fine-grained personal access token limited to the required repositories with Contents: Read-only permission
- a classic personal access token generally requires the broader
reposcope - verify any organization approval or SSO authorization requirement
- replace or remove the saved token from the GitHub Access tab when needed
Leaving the token field blank preserves the existing token.
GitHub can return similar failures for an inaccessible private repository and a repository, release, tag, archive, or asset that does not exist.
Confirm that:
github_repouses the correctowner/repositoryvalue- the configured token can access that repository
github_tagexactly matches a published stable GitHub Release tag- the release is not a draft or prerelease
github_assetexactly matches the uploaded ZIP filename when supplied
A Git tag without an associated GitHub Release is not used.
A specified github_tag that does not match a published stable release does not fall back to the latest release. A specified github_asset that does not match an uploaded ZIP asset does not fall back to the generated archive.
When github_asset is omitted, Secure File Access downloads and rebuilds GitHub's generated ZIP archive for the selected release tag. No uploaded release asset is required.
The resulting ZIP uses the repository name as its filename and places the repository contents directly at the archive root. For example:
private-plugin.zip
├── private-plugin.php
└── ...
When github_asset is supplied, the filename must exactly match an uploaded .zip asset in the selected release.
[file_access github_repo="littlebizzy/private-plugin" github_asset="private-plugin-2.0.0.zip"]
The named asset receives priority and must exist. It redirects directly when GitHub supplies a temporary URL or streams unchanged through WordPress when GitHub returns 200 OK. It is not renamed or rebuilt, and the plugin does not silently substitute the generated source archive.
Generated archive processing requires WordPress to download, inspect, extract, stage, rebuild, and stream a temporary ZIP package.
Confirm that:
- the WordPress temporary directory exists and is writable
- the server has enough temporary disk space for the source ZIP, extracted files, and rebuilt ZIP
- PHP can create ZIP archives through
ZipArchiveor WordPress's bundled PclZip library - the generated GitHub archive is complete and valid
- no security or hosting rule blocks temporary files, archive extraction, or streamed responses
- the request has enough execution time to download and rebuild the package
Secure File Access requires exactly one root directory in the generated archive. It rejects:
- empty or malformed ZIP files
- multiple top-level roots
- files placed directly at the archive root
- absolute paths, parent-directory traversal, Windows drive paths, or backslash paths
- symbolic links
The plugin removes GitHub's generated outer directory and keeps the repository name as the ZIP filename. It does not modify the files or relative paths inside the repository tree.
A direct 200 OK uploaded asset requires WordPress to write the unchanged ZIP into a private temporary workspace before sending it to the user.
Confirm that:
- the WordPress temporary directory exists and is writable
- the server has enough temporary disk space for the uploaded ZIP
- no security or hosting rule blocks temporary files or streamed responses
- the request has enough execution time to download and stream the asset
This temporary-file path is used only when GitHub returns the ZIP body directly. Temporary redirects continue to transfer the asset from GitHub to the authorized browser.
Generated archives and direct 200 OK uploaded assets use the same local ZIP sender. Secure File Access refuses the response rather than risking a corrupted or fully buffered ZIP when response headers were already sent or an active output buffer cannot be removed.
Check for:
- PHP warnings, notices, or whitespace output before the download response
- a UTF-8 byte-order mark or other output in a plugin or theme PHP file
- a plugin, theme, host rule, or proxy that sends response headers early
- a non-removable output buffer created by another plugin or the hosting stack
The plugin closes the temporary file and removes its private workspace when this guard is triggered. Fix the early output or buffering source, reload the page, and use a newly generated protected link.
Generated archives should download as repository.zip with repository files and directories directly at the ZIP root.
If the old GitHub-generated name such as owner-repository-tag-hash.zip appears, or the ZIP still contains another repository-named wrapper folder:
- confirm that Secure File Access 1.6.2 or later is active
- confirm that
github_assetis omitted - reload the page to create a new protected link
- clear any full-page or object cache that preserved an older shortcode response
Explicitly uploaded assets keep the filename and internal structure chosen by their publisher.
Temporary workspace paths are unique per request and registered for shutdown cleanup. The plugin also removes the workspace immediately after a completed local stream or redirect fallback when possible.
Temporary files can remain when PHP or the server process is terminated before shutdown handlers run. Remove abandoned sfa-... directories from the configured WordPress temporary directory only after confirming that no download request is using them.
Generated archives and direct 200 OK uploaded assets are not cached, so persistent ZIP packages are not expected.
The plugin distinguishes GitHub rate limits and temporary GitHub server failures without displaying API response bodies.
It does not automatically retry failed requests. Reload the WordPress page later to create a new protected link and try again.
The generated-archive API request must return a temporary redirect. A direct ZIP body from that endpoint is rejected because generated archives require the validated temporary-URL normalization flow.
An uploaded-asset API request may return either a temporary redirect or a direct 200 OK ZIP body. Other response forms are rejected.
Every temporary URL must:
- use HTTPS
- include a valid host
- contain no embedded username or password
- pass WordPress URL safety validation
For a generated archive, WordPress downloads the validated temporary URL and rebuilds the package locally. For an uploaded asset, the authorized browser follows a validated redirect or WordPress streams a direct 200 OK response unchanged through its private workspace.
This download link is invalid or has expired. can mean that:
- more than 15 minutes passed
- the link already completed a successful redirect
- a local GitHub ZIP was already prepared for that link
- the temporary record was removed by WordPress or an object cache
- the stored record is incomplete
Reload the page to generate a new link. Protected links cannot be transferred between user accounts.
Deactivation preserves all settings.
Deleting the plugin removes only the saved sfa_github_token, including per-site tokens across Multisite. Other saved options are preserved for possible reinstallation.
See Shortcode for attributes and examples, Settings for saved defaults and credentials, and Downloads for the complete protected-link flow.