Forcing a browser to download a file (HTML and server side)
đ Wiki page | đ Last updated: Aug 28, 2022When a user clicks a normal HTML link to a file, i.e.:
<a href="path/to/file.ext">
Browsers will usually try to open the file directly (inline) instead of opening the file download dialog.
You can use HTML5 download
attribute to tell modern browsers to open the download dialog instead:
<a href="path/to/file.ext" download>
You can also specify the suggested filename the user will see in the download dialog:
<a href="path/to/file.ext" download="myfile.ext">
If you specify just the file name (without the extension), the extension will usually be inferred from the original filename (this example will still suggest "myfile.ext" in most browsers):
<a href="path/to/file.ext" download="myfile">
Limitations and workarounds
Nowadays, download
attribute is supported in all mainstream browsers, but there's an important limitation: this attribute only works for same-origin URLs (protocol, port, and host have to match), and the blob:
and data:
schemes.
Besides the download
attribute, there's not much else you can do to force this behavior from the client side, but you can on the server side.
Including the Content-Disposition
header in the HTTP response will have a similar effect to download
attribute (but without this limitation):
Content-Disposition: attachment; filename=file.ext
You'll also need to send the Content-type
header and the actual file contents.
In PHP, that would look something like this:
header('Content-Disposition: attachment; filename="downloaded.pdf"');
header('Content-type: application/pdf');
readfile('test.pdf');
But to avoid the performance overhead, I recommend doing that on the webserver level instead of the app level.
nginx
For nginx
this could be as simple as:
add_header Content-Disposition 'attachment; filename="file.ext"';
Note: nginx will usually send the correct Content-type
header automatically, but you can override that if needed:
add_header Content-Type 'application/pdf'
For a more general case, you could send a generic application/octet-stream
header and Content-Disposition
without a filename:
location /myloc {
if ($request_filename ~* ^.*?\.(pdf|zip|docx)$) {
add_header Content-Disposition attachment;
add_header Content-Type application/octet-stream;
}
}
Combination of download attribute and Content-Disposition header
If both the download attribute and Content-Disposition header are present and both define the filename, the filename defined in the header will have priority.
In the case of Content-Disposition: inline
header (which tells the browser to display the contents inline
- as a part of the page), the download attribute will have the priority (for the same-origin URLs).
Ask me anything / Suggestions
If you find this site useful in any way, please consider supporting it.