209

I have some big size PDF catalogs at my website, and I need to link these as download. When I googled, I found such a thing noted below. It should open the "Save As..." popup at link click...

 <head>
    <meta name="content-disposition" content="inline; filename=filename.pdf">
    ...

But it doesn't work :/ When I link to a file as below, it just links to file and is trying to open the file.

    <a href="filename.pdf" title="Filie Name">File name</a>

UPDATE (according to answers below):

As I see there is no 100% reliable cross-browser solution for this. Probably the best way is using one of the web services listed below, and giving a download link...

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
designer-trying-coding
  • 5,994
  • 17
  • 70
  • 99

19 Answers19

301

From an answer to Force a browser to save file as after clicking link:

<a href="path/to/file" download>Click here to download</a>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ayush Gupta
  • 5,568
  • 2
  • 21
  • 17
  • 25
    At the time of this comment, the `download` attribute is limited to Chrome, Firefox and Opera. Even recent versions of IE and Safari do not support it. For future support: check http://caniuse.com/#feat=download ! – Sygmoral Sep 05 '14 at 22:48
  • Netbeans error checking complains about it, but it seems to work fine. You can specify a preliminary name for the new file like so: download="myFile.txt" – Yster May 26 '15 at 11:55
  • 2
    This works in Edge at the time of writing this comment, and seems like the only way possible to stop hyperlinked PDFs opening in Edge's very poor attempt at a PDF viewer. –  Nov 30 '16 at 16:24
  • At the time of this comment, I've tested it on Chrome, Opera, Edge, Mozilla. All latest. Is working on all except on Mozilla. – S.I. Sep 29 '17 at 05:07
  • 12
    This only works for same-origin links, as mentioned on https://caniuse.com/#feat=download. If your links are cross-origin, your only option (for now) is to force the response type to "application/octet-stream", as many answers suggest. If you don't have access to the server, then you can try to proxy it and set the response header manually. – jean-baptiste May 19 '18 at 14:39
  • 1
    @jean-baptiste agree... And a bit absurd restriction, since it greatly limits functionality whereas there are many ways to circumvent the presume "protection" – user1156544 Aug 13 '18 at 17:11
  • It might be worth noting that this is a rather new feature and therefore still can be prone to browser bugs, e.g.: https://crbug.com/993362 – phk Sep 24 '19 at 13:51
  • attribute "download" doesn't show save-as dialogue if web app is run with "--app=" – Dee Sep 02 '23 at 07:00
97

Use the download attribute, but take into account that it only works for files hosted in the same origin that your code. It means that users can only download files that are from the origin site, same host.

Download with original filename:

<a href="file link" download target="_blank">Click here to download</a>

Download with 'some_name' as filename:

<a href="file link" download="some_name" target="_blank">Click here to download</a>

Adding target="_blank" we will use a new Tab instead of the actual one, and also it will contribute to the proper behavior of the download attribute in some scenarios.

It follows the same rules as same-origin policy. You can learn more about this policy on the MDN Web Doc same-origin policy page

You can lern more about this download HTML5 attribute on the MDN Web Doc anchor's attributes page.

DrWaky
  • 1,165
  • 8
  • 7
  • 3
    It's exactly the same as the current top answer though, from Ayush Gupta in 2013. The `target="_blank"` may make it slightly more usable for non-supporting browsers, though (PDFs then open in a new window/tab, rather than overwriting the current page). – Sygmoral Sep 05 '14 at 22:52
  • 3
    For me, `target="_blank"` was necessary as `download` only failed to trigger the download properly (Chrome) – casraf Apr 18 '17 at 09:43
  • 4
    If you give a string in the `download` attribute, it will be used as file name. I'm using it in userscripts all the time. – weaknespase Apr 09 '18 at 06:36
  • 2
    Correct me if I'm wrong, but download is not a "save-as" dialog box. Doenload and target _blank simply opens the file in a new tab. – RationalRabbit Apr 20 '21 at 21:34
  • Works only with same origin! – Cristian E. Nov 30 '21 at 11:19
37

Meta tags are not a reliable way to achieve this result. Generally you shouldn't even do this - it should be left up to the user/user agent to decide what do to with the content you provide. The user can always force their browser to download the file if they wish to.

If you still want to force the browser to download the file, modify the HTTP headers directly. Here's a PHP code example:

$path = "path/to/file.pdf";
$filename = "file.pdf";
header('Content-Transfer-Encoding: binary');  // For Gecko browsers mainly
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($path)) . ' GMT');
header('Accept-Ranges: bytes');  // Allow support for download resume
header('Content-Length: ' . filesize($path));  // File size
header('Content-Encoding: none');
header('Content-Type: application/pdf');  // Change the mime type if the file is not PDF
header('Content-Disposition: attachment; filename=' . $filename);  // Make the browser display the Save As dialog
readfile($path);  // This is necessary in order to get it to actually download the file, otherwise it will be 0Kb

Note that this is just an extension to the HTTP protocol; some browsers might ignore it anyway.

peterjwest
  • 4,294
  • 2
  • 33
  • 46
Karel Petranek
  • 15,005
  • 4
  • 44
  • 68
  • 4
    In practice I believe this is widely implemented. – Matthew Wilson Sep 27 '10 at 10:00
  • This is the best solution, hassle free and works. You can directly download any file type using this method. +1 – casraf Jun 16 '12 at 18:47
  • 3
    Content-Transfer-Encoding does not exist in HTTP. Content-Encoding: none is useless. – Julian Reschke Sep 15 '12 at 07:03
  • MAKE A NOTE in your answer, that IF file is from external server/domain, then this answer may not be solution... – T.Todua May 12 '14 at 10:52
  • I'd like to add that I have had this or similar code fail on certain servers when files get large (>80 MB in my case). Using `ob_end_flush()` didn't help either. – Hendrik Jul 14 '16 at 18:06
  • "it should be left up to the user/user agent to decide what do to with the content you provide" - depends on what kind of user you have. We have employee users, and also for customers for some of our .pdf forms where the behavior, especially printing behavior, varies widely from browser to browser and differs greatly from a pdf reader app in how it renders the images. Items disappear, thick field borders are conjured out of thin air...... we need our users to open specific pdfs from a pdf app, not in the browser. – PoloHoleSet Jan 26 '18 at 17:34
25

I had this same issue and found a solution that has worked great so far. You put the following code in your .htaccess file:

<FilesMatch "\.(?i:pdf)$">
  ForceType application/octet-stream
  Header set Content-Disposition attachment
</FilesMatch>

It came from Force a File to Download Instead of Showing Up in the Browser.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tony
  • 287
  • 4
  • 9
  • 4
    This worked well, since I had a particular document directory for downloads. I omitted the ForceType, just to let the types stay as-is. I also didn't need the case-insensitive; mine seems to be case-insensitive already. I added a couple extra types, including optional-x for the old/new Office extensions: `` – goodeye Jun 27 '14 at 18:04
  • This worked wonderfully, even in OSX Safari, where as other answers (including the accepted one, as one commenter pointed out there) are limited to certain browsers which support the given HTML feature. This one seems more ideal to me since it worked across the board, though it does require higher-level access to the server configuration files. – bwright Jul 31 '15 at 06:22
  • You could put that right in your apache.conf file as well, I don't use .htaccess as it takes more resources. – Michael Fever Sep 10 '15 at 14:42
  • How would a Windows-hosted web site implement this, since they can't read htaccess files? – PoloHoleSet Jan 26 '18 at 17:38
12

I found a very simple solution for Firefox (only works with a relative rather than a direct href): add type="application/octet-stream":

<a href="./file.pdf" id='example' type="application/octet-stream">Example</a>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
User
  • 157
  • 1
  • 4
  • 2
    @Martin Chrome on Chrome OS and Firefox on Linux. Turns out the file needs to be local in order for it to work. My href was a url to cloud storage. – ttfreeman Apr 30 '19 at 20:52
7

Try adding this line to your .htaccess file.

AddType application/octet-stream .pdf

I hope it'll work as it is browser independent.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sharad Gautam
  • 89
  • 1
  • 1
  • I'd be interested to know why this has been downvoted - it's one of the simplest methods and works: https://css-tricks.com/snippets/htaccess/force-files-to-download-not-open-in-browser/ – Nathan Hornby Oct 15 '15 at 12:42
  • People try to modify the core functionality of a server on free hosting sites and then downvote because they can't get it to work. Every other solution will have caveats because it is a "hack" to replace this. – Abandoned Cart Jun 11 '17 at 15:19
  • I'm downvoting it because it's the wrong solution to the problem. It's a bad idea to mess with types of resources. – Brad Nov 14 '17 at 06:05
  • @NathanHornby - I didn't downvote it, but this would not work for any Windows hosted site, since they don't use .htaccess. – PoloHoleSet Jan 26 '18 at 17:40
  • I didn't downvote, but this solution blindly affects ALL files of that suffix. It's indiscriminate and therefore won't be appropriate in all cases. – JBH Feb 15 '18 at 00:12
  • It may be browser independent, but isn't it dependent on the web server being [Apache](http://en.wikipedia.org/wiki/Apache_HTTP_Server)? – Peter Mortensen Mar 28 '18 at 19:54
7

Generally it happens, because some browsers settings or plug-ins directly open PDF in the same window like a simple web page.

The following might help you. I have done it in PHP a few years back. But currently I'm not working on that platform.

<?php
    if (isset($_GET['file'])) {
        $file = $_GET['file'];
        if (file_exists($file) && is_readable($file) && preg_match('/\.pdf$/',$file)) {
            header('Content-type: application/pdf');
            header("Content-Disposition: attachment; filename=\"$file\"");
            readfile($file);
        }
    }
    else {
        header("HTTP/1.0 404 Not Found");
        echo "<h1>Error 404: File Not Found: <br /><em>$file</em></h1>";
    }
?>

Save the above as download.php.

Save this little snippet as a PHP file somewhere on your server and you can use it to make a file download in the browser, rather than display directly. If you want to serve files other than PDF, remove or edit line 5.

You can use it like so:

Add the following link to your HTML file.

<a href="download.php?file=my_pdf_file.pdf">Download the cool PDF.</a>

Reference from: This blog

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ashay
  • 675
  • 4
  • 13
  • 9
    This appears to be a nice way to serve up every single file on your server to anyone who's paying attention. `download.php?file=database_password_file.php` for example. – pbarney Aug 17 '16 at 15:34
7

I just used this, but I don't know if it works across all browsers.

It works in Firefox:

<a href="myfile.pdf" download>Click to Download</a>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
NowLiveLove
  • 197
  • 1
  • 2
  • 4
  • 2
    That is suggested, and with more background information, in [an earlier answer](https://stackoverflow.com/a/28810543/2564301). – Jongware Jan 05 '18 at 10:08
5

Just put the below code in your .htaccess file:

AddType application/octet-stream .csv
AddType application/octet-stream .xls
AddType application/octet-stream .doc
AddType application/octet-stream .avi
AddType application/octet-stream .mpg
AddType application/octet-stream .mov
AddType application/octet-stream .pdf

Or you can also do trick by JavaScript

element.setAttribute( 'download', whatever_string_you_want);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
manish1706
  • 1,571
  • 24
  • 22
5

A really simple way to achieve this, without using external download sites or modifying headers etc. is to simply create a ZIP file with the PDF inside and link directly to the ZIP file. This will ALWAYS trigger the Save/Open dialog, and it's still easy for people to double-click the PDF windows the program associated with .zip is launched.

BTW great question, I was looking for an answer as well, since most browser-embedded PDF plugins take sooo long to display anything (and will often hang the browser whilst the PDF is loading).

Mr. Bungle
  • 1,696
  • 17
  • 21
  • 34
    Terrible usability. Many end users wouldn't know what to do with a zip file. – CodeWarrior Feb 07 '14 at 16:11
  • 1
    Sometimes simple solutioans are the best solutions! – elrado Oct 21 '15 at 06:50
  • 1
    Not a good solution. I have exactly that problem with Firefox and a ZIP file. What should I do? ZIP the file? In that case, modifying the HTTP headers could be a solution but I have no access to that. – Arnaud Weil Feb 15 '17 at 13:18
3

A very easy way to do this, if you need to force download for a single link on your page, is to use the HTML5 download-attribute in the href-link.

See: http://davidwalsh.name/download-attribute

with this you can rename the file that the user will download and at the same time it forces the download.

There has been a debate whether this is good practice or not, but in my case I have an embedded viewer for a PDF file and the viewer does not offer a download link, so i have to provide one separately. Here I want to make sure the user does not get the PDF opened in the web browser, which would be confusing.

This won't necessary open the save as-dialog, but will download the link straight to the preset download destination. And of course if you are doing a site for someone else, and need them to write in manually attributes to their links is probably a bad idea, but if there is way to get the attribute into the links, this can be a light solution.

j-beda
  • 123
  • 4
JJxyz
  • 160
  • 11
2

A server-side solution is more compatible, until the "download" attribute is implemented in all the browsers.

One Python example could be a custom HTTP request handler for a filestore. The links that point to the filestore are generated like this:

http://www.myfilestore.com/filestore/13/130787e71/download_as/desiredName.pdf

Here is the code:

class HTTPFilestoreHandler(SimpleHTTPRequestHandler):

    def __init__(self, fs_path, *args):
        self.fs_path = fs_path                          # Filestore path
        SimpleHTTPRequestHandler.__init__(self, *args)

    def send_head(self):
        # Overwrite SimpleHTTPRequestHandler.send_head to force download name
        path = self.path
        get_index = (path == '/')
        self.log_message("path: %s" % path)
        if '/download_as/' in path:
            p_parts = path.split('/download_as/')
            assert len(p_parts) == 2, 'Bad download link:' + path
            path, download_as = p_parts
        path = self.translate_path(path )
        f = None
        if os.path.isdir(path):
            if not self.path.endswith('/'):
                # Redirect browser - doing basically what Apache does
                self.send_response(301)
                self.send_header("Location", self.path + "/")
                self.end_headers()
                return None
            else:
                return self.list_directory(path)
        ctype = self.guess_type(path)
        try:
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, "File not found")
            return None
        self.send_response(200)
        self.send_header("Content-type", ctype)
        fs = os.fstat(f.fileno())
        self.send_header("Expires", '0')
        self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
        self.send_header("Cache-Control", 'must-revalidate, post-check=0, pre-check=0')
        self.send_header("Content-Transfer-Encoding", 'binary')
        if download_as:
            self.send_header("Content-Disposition", 'attachment; filename="%s"' % download_as)
        self.send_header("Content-Length", str(fs[6]))
        self.send_header("Connection", 'close')
        self.end_headers()
        return f


class HTTPFilestoreServer:

    def __init__(self, fs_path, server_address):
        def handler(*args):
            newHandler = HTTPFilestoreHandler(fs_path, *args)
            newHandler.protocol_version = "HTTP/1.0"
        self.server = BaseHTTPServer.HTTPServer(server_address, handler)

    def serve_forever(self, *args):
        self.server.serve_forever(*args)


def start_server(fs_path, ip_address, port):
    server_address = (ip_address, port)
    httpd = HTTPFilestoreServer(fs_path, server_address)

    sa = httpd.server.socket.getsockname()
    print "Serving HTTP on", sa[0], "port", sa[1], "..."
    httpd.serve_forever()
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
yucer
  • 4,431
  • 3
  • 34
  • 42
2

This is old post but here is the one my solution in JavaScript what using jQuery library.

<script>
(function($){
    var download = [];
    $('a.force-download, .force-download a').each(function(){
        // Collect info
        var $this = $(this),
            $href = $this.attr('href'),
            $split = $href.split('/'),
            $name = document.title.replace(/[\W_]/gi, '-').replace(/-{2,}/g, '-'); // get title and clean it for the URL

        // Get filename from URL
        if($split[($split.length-1)])
        {
            $tmp = $split[($split.length-1)];
            $tmp = $tmp.split('.');
            $name = $tmp[0].replace(/[\W_]/gi, '-').replace(/-{2,}/g, '-');
        }

        // If name already exists, put timestamp there
        if($.inArray($name, download) > -1)
        {
            $name = $name + '-' + Date.now().replace(/[\W]/gi, '-');
        }

        $(this).attr("download", $name);
        download.push($name);
    });
}(jQuery || window.jQuery))
</script>

You just need to use class force-download inside your <a> tag and will force download automaticaly. You also can add it to parent div and will pickup all links inside it.

Example:

<a href="/some/good/url/Post-Injection_Post-Surgery_Instructions.pdf" class="force-download" target="_blank">Download PDF</a>

This is great for WordPress and any other systems or custom websites.

Ivijan Stefan Stipić
  • 6,249
  • 6
  • 45
  • 78
2

Add a response header Content-Disposition:attachment; followed by the file name. Remove the Meta Content-Disposition;Inline; which will open the document in the same window

In java it is set as

response.setHeader("Content-Disposition", "attachment;filename=test.jpg");
BJ5
  • 512
  • 6
  • 22
1

After the file name in the HTML code I add ?forcedownload=1

This has been the simplest way for me to trigger a dialog box to save or download.

ByteHamster
  • 4,884
  • 9
  • 38
  • 53
Bonnie
  • 19
  • 1
  • this solution and @j-beda suggested download attribute works well with me. Note, I am having issues with .dot and .doc automatically opened by chrome instead of showing thd download box. – jona Mar 09 '22 at 11:06
0

If you have a plugin within the browser which knows how to open a PDF file it will open directly. Like in case of images and HTML content.

So the alternative approach is not to send your MIME type in the response. In this way the browser will never know which plugin should open it. Hence it will give you a Save/Open dialog box.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
sushil bharwani
  • 29,685
  • 30
  • 94
  • 128
  • That's hard to achieve because it's the server that sends the mime type and when linking directly to a PDF file, you cannot change the headers. – Karel Petranek Sep 27 '10 at 09:48
0

I just had a very similar issue with the added problem that I needed to create download links to files inside a ZIP file.

I first tried to create a temporary file, then provided a link to the temporary file, but I found that some browsers would just display the contents (a CSV Excel file) rather than offering to download. Eventually I found the solution by using a servlet. It works both on Tomcat and GlassFish, and I tried it on Internet Explorer 10 and Chrome.

The servlet takes as input a full path name to the ZIP file, and the name of the file inside the zip that should be downloaded.

Inside my JSP file I have a table displaying all the files inside the zip, with links that say: onclick='download?zip=<%=zip%>&csv=<%=csv%>'

The servlet code is in download.java:

package myServlet;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.zip.*;
import java.util.*;

// Extend HttpServlet class
public class download extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        PrintWriter out = response.getWriter(); // now we can write to the client

        String filename = request.getParameter("csv");
        String zipfile = request.getParameter("zip");

        String aLine = "";

        response.setContentType("application/x-download");
        response.setHeader( "Content-Disposition", "attachment; filename=" + filename); // Force 'save-as'
        ZipFile zip = new ZipFile(zipfile);
        for (Enumeration e = zip.entries(); e.hasMoreElements();) {
            ZipEntry entry = (ZipEntry) e.nextElement();
            if(entry.toString().equals(filename)) {
                InputStream is = zip.getInputStream(entry);
                BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"), 65536);
                while ((aLine = br.readLine()) != null) {
                    out.println(aLine);
                }
                is.close();
                break;
            }
        }
    }
}

To compile on Tomcat you need the classpath to include tomcat\lib\servlet-api.jar or on GlassFish: glassfish\lib\j2ee.jar

But either one will work on both. You also need to set your servlet in web.xml.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
mljm
  • 327
  • 3
  • 13
0

Chrome 91 had a new change, it supported in chrome 86-90 and 91+. The following syntax will make it happen.

const fileHandle = await self.showSaveFilePicker({
   suggestedName: 'Untitled Text.txt',
   types: [{
      description: 'Text documents',
      accept: {
        'text/plain': ['.txt'],
      },
   }],
});

Read more here:

https://developer.chrome.com/blog/new-in-chrome-91/

**Another solution you can just make it as a blob and then use saveAs **

const blob = fetch("some-url-here").then(data => data.blob());
saveAs(blob, "filename.txt")
Ariwibawa
  • 627
  • 11
  • 23
Aviv Day
  • 428
  • 4
  • 13
-2

With large PDF files the browser hangs. In Mozilla, menu ToolsOptionsApplications, then next to the content type Adobe Acrobat document. In the Action drop down, select Always ask.

This did not work for me, so what worked was:

Menu Tools* → Add-onsAdobe Acrobat (Adobe PDF plugin for Firefox) → DISABLE. Now I am able to download e-books!

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Joojoo
  • 1