How to Create a File Download Link in HTML, PHP, and JavaScript — 4 Working Methods
When developing websites, it is often necessary to add a link that allows users to download a file to their devices. This could be a PDF document, an image, a ZIP archive, an Excel spreadsheet, a manual, or any other downloadable resource.
However, a standard HTML link does not always trigger a download. For example, when clicking a PDF link, the browser may open the document in its built-in PDF viewer, while an image may simply appear in a new tab.
In this guide, we will explain how to create a file download link in a browser, explore different methods using HTML, PHP, and JavaScript, configure forced downloads on the server, and discuss important considerations for modern browsers.
Last updated: October 14, 2026.
Why Does a Browser Open a File Instead of Downloading It?
By default, browsers determine how to handle files based on their content type, HTTP response headers, user preferences, and built-in viewing capabilities.
For example, a standard link:
<a href="/files/catalog.pdf">Open Catalog</a>
may open the PDF document directly in the browser.
Two main mechanisms can control this behavior:
- The HTML download attribute — tells the browser that the linked resource should be downloaded rather than opened.
- The Content-Disposition HTTP header — allows the server to specify how the browser should handle the file.
Downloads can also be initiated using JavaScript or managed through a dedicated PHP download handler.
1. Creating a Download Link Using the HTML download Attribute
The simplest way to create a file download link is to use the HTML download attribute in an <a> element.
Example:
<a href="/files/catalog.pdf" download>
Download PDF Catalog
</a>
When the user clicks the link, the browser receives an instruction to download the document instead of navigating to the file.
The download attribute is supported by modern browsers, including Google Chrome, Mozilla Firefox, Safari, and Microsoft Edge.
How to Specify the Downloaded File Name
The download attribute can also specify the suggested name under which the browser should save the file.
<a href="/files/catalog.pdf"
download="product-catalog.pdf">
Download Catalog
</a>
In this example, the browser will suggest the filename product-catalog.pdf, even if the original file on the server has a different name.
However, the specified filename is not guaranteed to be used. If the server provides its own filename through the Content-Disposition header, that filename generally takes priority.
Downloading Images
The same approach can be used to create image download links:
<a href="/images/photo.jpg"
download="photo.jpg">
Download Image
</a>
This method is useful for photo galleries, product catalogs, and websites that provide downloadable graphical materials.
Limitations of the download Attribute
Despite its simplicity, the HTML download attribute has an important limitation: it is primarily intended for same-origin resources.
This means that the webpage and the downloadable file should use the same protocol, domain, and port. Special blob: and data: URLs generated by the browser are also supported.
For example, if your website is hosted at example.com, the attribute will generally work for:
https://example.com/files/catalog.pdf
However, if you attempt to download a file from another domain:
<a href="https://cdn.example.net/catalog.pdf"
download>
Download Catalog
</a>
the browser may ignore the download attribute and open the resource normally.
In this situation, forced downloading is best configured on the server that delivers the file.
Advantages: minimal code, no PHP or JavaScript required, and easy integration into HTML templates.
Disadvantages: cross-origin limitations and behavior that depends on browser settings and server response headers.
2. Configuring Forced File Downloads Using .htaccess
If your website runs on the Apache web server, you can configure file downloads at the HTTP level using the .htaccess file.
This method uses the following response header:
Content-Disposition: attachment
The attachment value tells the browser that the response should be handled as a downloadable attachment.
Example: Configuring a Specific PDF File
Suppose the file is available at:
https://example.com/files/catalog.pdf
Create or edit the /files/.htaccess file and add the following configuration:
<IfModule mod_headers.c>
<Files "catalog.pdf">
Header set Content-Disposition "attachment"
</Files>
</IfModule>
Now, when a user requests /files/catalog.pdf, Apache will add the Content-Disposition: attachment header to the response.
The HTML link itself remains unchanged:
<a href="/files/catalog.pdf">
Download PDF
</a>
Unlike the HTML download attribute, this method controls browser behavior directly through the HTTP response.
How to Force Downloads for All PDF Files in a Directory
If all PDF files in a particular directory should be downloaded automatically, you can use the FilesMatch directive.
For example, add the following to /files/.htaccess:
<IfModule mod_headers.c>
<FilesMatch "\.pdf$">
Header set Content-Disposition "attachment"
</FilesMatch>
</IfModule>
This rule applies to matching PDF files within the configuration's scope.
Important: Avoid forcing downloads for every file on your website unless necessary. Doing so may interfere with documents and resources that should normally be displayed in the browser.
Important Considerations When Using .htaccess
The configuration requires:
- An Apache web server with the
mod_headersmodule enabled. - Permission to use the corresponding directives in
.htaccess. - No conflicting configurations at the virtual host or reverse proxy level.
If your website runs exclusively on Nginx, the .htaccess file will not be processed. In that case, you must configure the corresponding HTTP header directly in Nginx.
Advantages: server-side download control without modifying every HTML link.
Disadvantages: requires access to server configuration, and an incorrectly configured rule may affect more files than intended.
3. Downloading Files Using PHP
PHP provides greater control over the file transfer process. This method is particularly useful when you need to verify user permissions, track downloads, hide physical file locations, or generate documents dynamically.
Let's look at an example of a simple PHP download handler.
Creating a PHP Download Handler
Create a file named /download.php with the following contents:
<?php
declare(strict_types=1);
// File path outside the public document root
$file = dirname($_SERVER['DOCUMENT_ROOT'])
. '/private_downloads/catalog.pdf';
// Check whether the file exists and is readable
if (!is_file($file) || !is_readable($file)) {
http_response_code(404);
exit('File not found');
}
// HTTP response headers
header('Content-Type: application/pdf');
header(
'Content-Disposition: attachment; filename="catalog.pdf"'
);
header('Content-Length: ' . filesize($file));
header('X-Content-Type-Options: nosniff');
// Send the file contents
readfile($file);
exit;
In this example, the file is stored outside the website's public directory. You must create the private_downloads directory beforehand and ensure that the PHP process has permission to read the file.
Users can download the document using the following link:
<a href="/download.php">
Download Document
</a>
Understanding the PHP Code
Checking the file:
if (!is_file($file) || !is_readable($file)) {
http_response_code(404);
exit('File not found');
}
This checks whether the specified path points to an existing, readable file. If the file is unavailable, the server returns an HTTP 404 response.
Content type:
header('Content-Type: application/pdf');
This specifies the MIME type of a PDF document. For arbitrary binary files, you can use application/octet-stream.
Forcing the download:
header(
'Content-Disposition: attachment; filename="catalog.pdf"'
);
This tells the browser to offer the file for download and provides a suggested filename.
File size:
header('Content-Length: ' . filesize($file));
This sends the file size in bytes, allowing the browser to display download progress information.
Sending the file contents:
readfile($file);
exit;
The readfile() function reads the file and writes its contents to the output stream.
HTTP headers must be sent before the response body. Therefore, the PHP handler must not output HTML, whitespace, or debugging messages before calling header().
How to Download Files with Non-ASCII Names
If a filename contains non-ASCII characters, including Cyrillic letters or accented characters, it is recommended to use the filename* parameter, which supports UTF-8 encoding.
Example:
$fileName = 'Каталог продукции.pdf';
header(
'Content-Disposition: attachment; '
. 'filename="catalog.pdf"; '
. "filename*=UTF-8''"
. rawurlencode($fileName)
);
Here, filename provides an ASCII fallback filename, while filename* allows the original Unicode filename to be transmitted correctly.
For maximum compatibility, both parameters should be included.
Downloading Protected Files in 1C-Bitrix
On websites powered by 1C-Bitrix, PHP download handlers are particularly useful when documents should only be accessible to authenticated users.
For example, at the beginning of the handler, you can include the Bitrix service prolog and check whether the user is authorized:
require(
$_SERVER['DOCUMENT_ROOT']
. '/bitrix/modules/main/include/prolog_before.php'
);
global $USER;
if (!$USER->IsAuthorized()) {
http_response_code(403);
exit('Access denied');
}
After verifying authorization, the handler can send the requested file.
Important: Authentication alone is not sufficient if different users have access to different documents. In such cases, you must also verify access permissions for the specific file.
If a document must remain private, do not store it in a publicly accessible directory without additional restrictions. Otherwise, users may bypass the PHP handler by accessing the file directly through its URL.
Never insert an unvalidated user-supplied query parameter directly into a filesystem path. This can introduce path traversal vulnerabilities and expose sensitive server files. For dynamic file selection, use validated identifiers and an allowlist of permitted files.
Advantages: flexible implementation, authentication checks, access control, and download tracking.
Disadvantages: requires server-side development; transferring very large files through PHP may increase server load. For large files, consider web server transfer mechanisms such as X-Sendfile or X-Accel-Redirect.
4. Downloading Files Using JavaScript
JavaScript allows downloads to be initiated in response to user actions, such as clicking a button or generating a document dynamically.
The simplest approach is to create an HTML link with the download attribute dynamically and call its click() method.
Example: Download Button
<button type="button" id="downloadButton">
Download PDF
</button>
<script>
document
.getElementById('downloadButton')
.addEventListener('click', function () {
const link = document.createElement('a');
link.href = '/files/catalog.pdf';
link.download = 'catalog.pdf';
document.body.appendChild(link);
link.click();
link.remove();
});
</script>
When the user clicks the button, JavaScript creates a temporary link, initiates the download, and removes the link from the document.
This method is convenient for dynamic interfaces, but it inherits the limitations of the HTML download attribute, including restrictions on cross-origin resources.
Modern browsers may also restrict automatic or multiple downloads, especially when they are initiated without explicit user interaction.
Creating and Downloading a File Using Blob
JavaScript can also generate files directly in the browser without first saving them on the server.
For example, you can create a text document:
function downloadTextFile() {
const content = 'Example text file';
const blob = new Blob(
[content],
{ type: 'text/plain;charset=utf-8' }
);
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'example.txt';
document.body.appendChild(link);
link.click();
link.remove();
// Release the temporary URL after a delay
setTimeout(() => {
URL.revokeObjectURL(url);
}, 60000);
}
This function should be called in response to a user action, such as clicking a button.
The example uses a Blob object containing the file contents and the URL.createObjectURL() method to generate a temporary download URL.
Once the temporary URL is no longer needed, it should be released using URL.revokeObjectURL(). Avoid revoking the URL too early while the browser may still need it to initiate the download.
This approach is useful for exporting CSV, JSON, TXT, and other files generated on the client side.
Downloading Files Using the Fetch API
If you need to retrieve a file from the server before saving it under a specific name, you can combine fetch() with Blob.
async function downloadFile() {
try {
const response = await fetch(
'/files/catalog.pdf'
);
if (!response.ok) {
throw new Error(
'Download error: ' + response.status
);
}
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'catalog.pdf';
document.body.appendChild(link);
link.click();
link.remove();
setTimeout(() => {
URL.revokeObjectURL(url);
}, 60000);
} catch (error) {
console.error(error);
}
}
This method is useful when additional validation or processing is required before downloading the file.
However, fetch() is subject to the Cross-Origin Resource Sharing (CORS) policy. If the file is hosted on another domain, that server must explicitly allow the cross-origin request.
Also note that retrieving an entire file as a Blob consumes additional browser memory. For large files, direct download links or server-side file transfers are generally preferable.
5. How to Download a File from Another Website or CDN
A separate challenge arises when a file is hosted outside your primary domain, such as on an external server, cloud storage platform, or Content Delivery Network (CDN).
For example:
https://cdn.example.net/documents/catalog.pdf
Using the HTML download attribute in this situation does not guarantee that the file will be downloaded, because the URL belongs to a different origin.
The most reliable solution is to configure the server delivering the file to send the following header:
Content-Disposition: attachment; filename="catalog.pdf"
If you do not control the external server, your ability to force downloads is limited.
Another approach is to provide downloads through your own server-side handler, which retrieves an authorized file and delivers it to the user. However, you must consider access permissions, data transfer volume, and the security of requests to external URLs.
You cannot use fetch() to bypass restrictions imposed by an external server. The server must permit access to the response through its CORS configuration.
6. How to Verify That a File Is Downloaded Correctly
After implementing a download link, test it in multiple browsers and verify that the server returns the correct HTTP headers.
You can inspect the response using the Network tab in your browser's developer tools.
Alternatively, use the curl command:
curl -I https://example.com/files/catalog.pdf
When server-side downloading is configured correctly, the response may contain the following headers:
HTTP/2 200
Content-Type: application/pdf
Content-Disposition: attachment; filename="catalog.pdf"
Content-Length: 125830
The key header is Content-Disposition: attachment.
If the download is triggered exclusively by the HTML download attribute, this header may be absent.
Make sure to test download behavior on mobile devices as well. For example, Safari on an iPhone may use its own interface for downloading or saving files.
7. Common Errors When Creating File Download Links
A PDF Opens in the Browser Instead of Downloading
Check whether the link includes the download attribute and whether the file belongs to the same origin. If that is insufficient, configure the server to send the Content-Disposition: attachment header.
The download Attribute Does Not Work for External Files
This is a standard limitation of modern browsers. To reliably control downloads, configure HTTP headers on the server delivering the file or use an authorized server-side download handler.
PHP Reports a "Headers Already Sent" Error
This error indicates that output began before calling header(). Check for HTML, whitespace, error messages, or other output generated before the HTTP headers.
An HTML Page Is Downloaded Instead of the File
This can happen when a download handler returns a login page, an error message, or an HTML template instead of the actual file contents.
Check the HTTP response status, MIME type, and contents of the downloaded file.
JavaScript Does Not Start the Download
Check the browser console, file availability, and security policy restrictions. Whenever possible, initiate downloads directly from a user interaction handler.
The Downloaded File Is Corrupted
When using PHP, ensure that no additional HTML, debugging output, or error messages are included in the file stream.
Also verify the transmitted content and the accuracy of the Content-Length header.
8. Which File Download Method Should You Choose?
The best implementation depends on your website's requirements and where the file is hosted.
| Method | Recommended Use | Main Features |
|---|---|---|
| HTML download | Public files hosted on your own website | Simple implementation without server-side code |
| .htaccess | Files that must always be offered for download | HTTP header configuration on Apache |
| PHP | Protected files and documents in user accounts | Access control, permission checks, and download tracking |
| JavaScript | Dynamic interfaces and files generated in the browser | Blob, Fetch API, and user-triggered downloads |
For a typical public file, an HTML link with the download attribute is usually sufficient.
If you need to control downloading independently of the HTML markup, using the server-side Content-Disposition header is generally preferable.
For documents stored in user accounts, files with restricted access, and other protected resources, a dedicated server-side download handler is recommended.
9. Important Security and Compatibility Considerations
The methods described in this article are supported by modern browsers, but actual download behavior may vary depending on user settings, the operating system, and browser security policies.
Before implementing file downloads on a production website, consider the following:
- The
downloadattribute does not guarantee identical behavior for files hosted on external domains. - The
Content-Disposition: attachmentheader is the primary server-side mechanism for suggesting that a file should be downloaded. - Links to protected documents are not a substitute for server-side permission checks.
- PHP download handlers should never accept arbitrary filesystem paths supplied by users.
- Large files require careful consideration of server and browser resource usage.
- Files retrieved with
fetch()from another origin require appropriate CORS configuration. - The browser may ask the user to choose a download location, save the file automatically to a downloads folder, or handle it in another permitted way.
If your website uses 1C-Bitrix, it is recommended to use the platform's standard authentication and permission-checking mechanisms. Download handlers should also be implemented in a way that does not conflict with website templates or the standard Bitrix URL routing system.
10. Useful Documentation
Additional information about file downloads and browser behavior is available in the official documentation:
- MDN — HTML Anchor Element and the download Attribute
- MDN — HTTP Content-Disposition
- Apache — mod_headers
- PHP — readfile() Function
- MDN — URL.createObjectURL()
- MDN — Cross-Origin Resource Sharing (CORS)
Conclusion
There are several ways to create a file download link in a browser, including the HTML download attribute, .htaccess configuration, PHP download handlers, and JavaScript.
Each approach serves different purposes. HTML is suitable for simple public downloads, web server settings provide control over HTTP headers, PHP enables access management, and JavaScript is convenient for dynamic interfaces and files generated directly in the browser.
When choosing a solution, consider where the file is hosted, security requirements, browser limitations, and whether server-side processing is necessary.
For most straightforward use cases, the download attribute is sufficient. For server-controlled downloads, the Content-Disposition: attachment HTTP header remains the most widely applicable mechanism.