File Gate is a free, open-source Drupal module from Wilkes & Liberty that turns a "private" file into a genuinely gated one: deny the ordinary download, and deliver the bytes only through a short-lived, server-minted signed URL that the browser redeems after your front end's gate. Headless-first, and it fails closed.
Out of the box, Drupal grants a private-file download to anyone who can view the entity that references it. Because anonymous visitors can view published content, a private:// file attached to published media is effectively public to anyone with the link — private:// means "not sitting in a public directory," not "gated." That is exactly the gap that bites lead-gen documents, gated reports, and paywalled deliverables, and it is sharpest on a decoupled site, where the gate (a form, a login, a purchase) lives in a different system from the file. File Gate closes the gap for any front end.
/system/files for any private file a gated field references — overriding core's permissive private-file access. Delivery never happens there.private:// location — so your JSON:API/GraphQL layer can expose that a document exists, its name, type, and size, without a directly resolvable file URL.GateMethod plugin type decides how a request proves it passed the gate. Ships with Signed URL (HMAC) and Authenticated access; add your own in a few lines.503 and every gated file is denied.An editor uploads a file to a private, gated field. A visitor passes your gate — a lead form, a login, a purchase — entirely in your front end. Your back end then asks File Gate to mint a signed URL for that specific file, and hands the browser the public link to redeem. File Gate verifies the signature (and any usage limit) and streams the file; the raw /system/files path stays denied to the public throughout. File Gate never decides how you gate — it only mints and verifies.
// Server-side only — never expose the secret to the browser:
const res = await fetch(`${DRUPAL_INTERNAL_ORIGIN}/api/file-gate/mint`, {
method: 'POST',
headers: {
'Authorization': 'Basic ' + Buffer.from(':' + process.env.DRUPAL_FILE_GATE_SECRET).toString('base64'),
'Content-Type': 'application/json',
},
body: JSON.stringify({ media: mediaUuid }),
});
const { path } = await res.json();
// Hand the browser the public URL to redeem:
const downloadUrl = `${DRUPAL_PUBLIC_FILE_ORIGIN}${path}`;
FileReferenceResolver, introduced in 11.4)file only — Media is optionalInstall with Composer:
composer require drupal/file_gate
drush en file_gateThen inject the signing secret in settings.php and generate a strong value:
$config['file_gate.settings']['download_secret'] = getenv('DRUPAL_FILE_GATE_SECRET');
// openssl rand -hex 32 → set DRUPAL_FILE_GATE_SECRET in your environmentThe current release is 1.0.0-beta1. Contributions are welcome, and the module is released under the GPL-2.0-or-later license. For the engineering story and the principles behind it, read our whitepaper Gating Private Files in a Headless CMS.