125

What is the best cross browser way to open a download dialog (let's assume we can set content-disposion:attachment in the headers) without navigating away from the current page, or opening popups, which doesn't work well in Internet Explorer(IE) 6.

RBT
  • 18,275
  • 13
  • 127
  • 181
mkoryak
  • 54,015
  • 59
  • 193
  • 252

13 Answers13

211

This javascript is nice that it doesn't open a new window or tab.

window.location.assign(url);
mozgras
  • 2,832
  • 3
  • 18
  • 17
  • 20
    This is the same as window.location = url; “Whenever a new value is assigned to the location object, a document will be loaded using the URL as if window.location.assign() had been called with the modified URL” - https://developer.mozilla.org/en-US/docs/Web/API/window.location – Rob Juurlink Aug 19 '13 at 11:47
  • 17
    This causes WebSocket connection to disconnect. – igorpavlov Jan 23 '16 at 18:15
  • 4
    I have used the same solution but it opens file in same tab instead of opening a download dialog. – Techno Cracker Jul 06 '16 at 17:35
  • 2
    it's same to window.open(url, '_self') if the url is for download page then. – Expert wanna be Sep 08 '16 at 09:05
  • 5
    When using IE11 I found that this caused JS to stop. So for IE 11 I used window.open(url, '_blank') which did open another tab, however that tab closed when it worked out the file was a download. This kept the JS running. – Luke Sep 08 '17 at 04:54
  • this is the best answer i've come across. I use typescript, axios and react and this is just what I needed. I just want to amend that I needed this code to complete the work ``` . let blob = new Blob([response.data], { type: 'text/csv' }); let link = window.URL.createObjectURL(blob); window.location.assign(link);``` – Andy Mar 08 '19 at 01:51
  • Thanks, it's smart and working perfectly, no need to create temporary – Praveen Danagoudru Jan 25 '21 at 15:37
118

7 years have passed and I don't know whether it works for IE6 or not, but this prompts OpenFileDialog in FF and Chrome.

var file_path = 'host/path/file.ext';
var a = document.createElement('A');
a.href = file_path;
a.download = file_path.substr(file_path.lastIndexOf('/') + 1);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
Dcoder14
  • 1,137
  • 2
  • 10
  • 20
0x000f
  • 1,360
  • 1
  • 9
  • 8
  • First thank you for this solution, but I found a bug if removeChild(a) the zip will unzip error with zip is broken, so remove this code solve it – Roy Sep 18 '17 at 03:16
  • , but A tag will more and more, so how do I check download is done? – Roy Sep 18 '17 at 03:22
  • safari has a problem with `a.click();` – Cyril Cherian Oct 05 '17 at 12:32
  • had to change the line `var a = document.createElement('A');` to `let a = document.createElement('a');` to get it to work in angular 2 typescript – Winnipass Feb 03 '18 at 11:27
  • +1 There is no apparent need to add the `a` element to `document.body` for it to work as intended. Also, tried this approach in Chrome (64.0.3282.167) and Safari (11.0.3) -- worked in both cases. – 01es Feb 20 '18 at 01:39
  • this is not working for firefox. how to download in firefox ? – Manoj Rana Mar 08 '18 at 10:28
  • 2
    @Manoj Rana - I have checked on FF 58.0.2 (64-bit) it is working. It won't work on any FF if you remove 2 lines _document.body.appendChild(a);_ _document.body.removeChild(a);_ – 0x000f Mar 09 '18 at 13:58
  • 1
    To make it work on Edge 16 the header, from where the file comes, should contains `Content-Type: application/octet-stream` and `Content-Disposition: attachment`. – Simon Mar 22 '18 at 19:05
  • 15
    this doesnt work anymore https://developers.google.com/web/updates/2018/02/chrome-65-deprecations – Paulius Dragunas Mar 24 '18 at 00:05
  • 2
    @user1933131 chrome removes only for cross-origin – Brandy23 Sep 27 '18 at 20:54
  • @Simon thanks for your comment! It solved my problem for Edge 44, although I didn't need the `Content-Disposition` header for Edge 42. – Lucas Carneiro Jul 22 '20 at 18:21
  • Great answers. Additional Question for your solution. It is possible to use a different Download-Filename ? – TheAlphaGhost Mar 17 '21 at 09:52
  • @TheAlphaGhost you can do it on a server side – 0x000f Mar 31 '21 at 10:17
28

I know the question was asked 7 years and 9 months ago but many posted solutions doesn't seem to work, for example using an <iframe> works only with FireFox and doesn't work with Chrome.

Best solution:

The best working solution to open a file download pop-up in JavaScript is to use a HTML link element, with no need to append the link element to the document.body as stated in other answers.

You can use the following function:

function downloadFile(filePath){
    var link=document.createElement('a');
    link.href = filePath;
    link.download = filePath.substr(filePath.lastIndexOf('/') + 1);
    link.click();
}

In my application, I am using it this way:

downloadFile('report/xls/myCustomReport.xlsx');

Working Demo:

function downloadFile(filePath) {
  var link = document.createElement('a');
  link.href = filePath;
  link.download = filePath.substr(filePath.lastIndexOf('/') + 1);
  link.click();
}

downloadFile("http://www.adobe.com/content/dam/Adobe/en/accessibility/pdfs/accessing-pdf-sr.pdf");

Note:

  • You have to use the link.download attribute so the browser doesn't open the file in a new tab and fires the download pop-up.
  • This was tested with several file types (docx, xlsx, png, pdf, ...).
cнŝdk
  • 28,676
  • 7
  • 47
  • 67
  • What would be the best way to display a loading gif until the file has downloaded? – Ctrl_Alt_Defeat Jun 12 '17 at 08:03
  • 1
    @Ctrl_Alt_Defeat Well in this case it won't be easy to track the download process, but one trick can be to show this gif animation on the `link` click and hide it after a timeout, using this code: `link.onclick = function() { document.body.innerText = "The file is being downloaded ..."; setTimeout(function() { document.body.innerText = ""; }, 2000); }`, you can see it working [in **this fiddle**](https://jsfiddle.net/chsdk/0pvdvssd/6/), but keep in mind that it's not a recommended way to do it, it would be better handled if we were using `Ajax`. – cнŝdk Jun 12 '17 at 08:14
  • 1
    it's not work in firefox. how to download in firefox ? – Manoj Rana Mar 08 '18 at 10:37
  • @ManojRana For firefox, you can use an `iframe` see [this answer](https://stackoverflow.com/a/18064978/3669624). – cнŝdk Mar 08 '18 at 11:13
  • 2
    This solution works in Chrome, Safari and Firefox for me :) – ariebear Mar 27 '20 at 00:18
  • 1
    this looks like the best approach for my case – Aw Snap Sep 09 '20 at 07:16
  • 1
    Thanks @cнŝdk! I deleted my previous comment, as I found that there are other things that PWA breaks while on the way to `document.createElement()`. If I clean them up, the method still works. – iva2k Feb 08 '21 at 18:25
  • @iva2k cool, glad to hear that it works for you. – cнŝdk Feb 09 '21 at 08:37
25

Put this in the HTML head section, setting the url var to the URL of the file to be downloaded:

<script type="text/javascript">  
function startDownload()  
{  
     var url='http://server/folder/file.ext';    
     window.open(url, 'Download');  
}  
</script>

Then put this in the body, which will start the download automatically after 5 seconds:

<script type="text/javascript">  
setTimeout('startDownload()', 5000); //starts download after 5 seconds  
</script> 

(From here.)

ccondrup
  • 474
  • 1
  • 8
  • 18
George Claghorn
  • 24,999
  • 3
  • 40
  • 45
25

I always add a target="_blank" to the download link. This will open a new window, but as soon as the user clicks save, the new window is closed.

jao
  • 17,118
  • 13
  • 58
  • 92
  • 2
    This is the best answer. In Internet Explorer, adding the 'target="_blank"' to a link that is to be downloadedwill stop the browser from navigating away (where "HTML1300: Navigation occurred" is printed), and thus can leave the page in an inconsistent state. – user64141 Feb 28 '17 at 21:24
17

I've been looking for a good way to use javascript to initiate the download of a file, just as this question suggests. However these answers not been helpful. I then did some xbrowser testing and have found that an iframe works best on all modern browsers IE>8.

downloadUrl = "http://example.com/download/file.zip";
var downloadFrame = document.createElement("iframe"); 
downloadFrame.setAttribute('src',downloadUrl);
downloadFrame.setAttribute('class',"screenReaderText"); 
document.body.appendChild(downloadFrame); 

class="screenReaderText" is my class to style content that is present but not viewable.

css:

.screenReaderText { 
  border: 0; 
  clip: rect(0 0 0 0); 
  height: 1px; 
  margin: -1px; 
  overflow: hidden; 
  padding: 0; 
  position: absolute; 
  width: 1px; 
}

same as .visuallyHidden in html5boilerplate

I prefer this to the javascript window.open method because if the link is broken the iframe method simply doesn't do anything as opposed to redirecting to a blank page saying the file could not be opened.

window.open(downloadUrl, 'download_window', 'toolbar=0,location=no,directories=0,status=0,scrollbars=0,resizeable=0,width=1,height=1,top=0,left=0');
window.focus();
alockwood05
  • 868
  • 10
  • 18
7

Using HTML5 Blob Object-URL File API:

/**
 * Save a text as file using HTML <a> temporary element and Blob
 * @see https://stackoverflow.com/questions/49988202/macos-webview-download-a-html5-blob-file
 * @param fileName String
 * @param fileContents String JSON String
 * @author Loreto Parisi
*/
var saveBlobAsFile = function(fileName,fileContents) {
    if(typeof(Blob)!='undefined') { // using Blob
        var textFileAsBlob = new Blob([fileContents], { type: 'text/plain' });
        var downloadLink = document.createElement("a");
        downloadLink.download = fileName;
        if (window.webkitURL != null) {
            downloadLink.href = window.webkitURL.createObjectURL(textFileAsBlob);
        }
        else {
            downloadLink.href = window.URL.createObjectURL(textFileAsBlob);
            downloadLink.onclick = document.body.removeChild(event.target);
            downloadLink.style.display = "none";
            document.body.appendChild(downloadLink);
        }
        downloadLink.click();
    } else {
        var pp = document.createElement('a');
        pp.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(fileContents));
        pp.setAttribute('download', fileName);
        pp.onclick = document.body.removeChild(event.target);
        pp.click();
    }
}//saveBlobAsFile

/**
 * Save a text as file using HTML <a> temporary element and Blob
 * @see https://stackoverflow.com/questions/49988202/macos-webview-download-a-html5-blob-file
 * @param fileName String
 * @param fileContents String JSON String
 * @author Loreto Parisi
 */
var saveBlobAsFile = function(fileName, fileContents) {
  if (typeof(Blob) != 'undefined') { // using Blob
    var textFileAsBlob = new Blob([fileContents], {
      type: 'text/plain'
    });
    var downloadLink = document.createElement("a");
    downloadLink.download = fileName;
    if (window.webkitURL != null) {
      downloadLink.href = window.webkitURL.createObjectURL(textFileAsBlob);
    } else {
      downloadLink.href = window.URL.createObjectURL(textFileAsBlob);
      downloadLink.onclick = document.body.removeChild(event.target);
      downloadLink.style.display = "none";
      document.body.appendChild(downloadLink);
    }
    downloadLink.click();
  } else {
    var pp = document.createElement('a');
    pp.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(fileContents));
    pp.setAttribute('download', fileName);
    pp.onclick = document.body.removeChild(event.target);
    pp.click();
  }
} //saveBlobAsFile

var jsonObject = {
  "name": "John",
  "age": 31,
  "city": "New York"
};
var fileContents = JSON.stringify(jsonObject, null, 2);
var fileName = "data.json";

saveBlobAsFile(fileName, fileContents)
loretoparisi
  • 12,864
  • 9
  • 78
  • 108
5

If the link is to a valid file url, simply assigning window.location.href will work.

However, sometimes the link is not valid, and an iFrame is required.

Do your normal event.preventDefault to prevent the window from opening, and if you are using jQuery, this will work:

$('<iframe>').attr('src', downloadThing.attr('href')).appendTo('body').on("load", function() {
   $(this).remove();
});
IvanH
  • 4,686
  • 13
  • 55
  • 71
TaylorMac
  • 8,452
  • 20
  • 71
  • 103
5

Modifying the location of window might cause some issue especially when you have a persistent connection like websocket. So I always resort to good old iframe solution.

HTML

<input type="button" onclick="downloadButtonClicked()" value="Download"/>
...
...
...
<iframe style="display:none;" name="hiddenIframe" id="hiddenIframe"></iframe>

Javascript

function downloadButtonClicked() {
    // Simulate a link click
    var url = 'your_download_url_here';
    var elem = document.createElement('a');
    elem.href = url;
    elem.target = 'hiddenIframe';
    elem.click();
}
Bnrdo
  • 4,947
  • 3
  • 30
  • 60
2

After hours of trying, the function is born :) I had a scenario where I had to display loader in time while the file is preparing for download:

Working in Chrome, Safari and Firefox

function ajaxDownload(url, filename = 'file', method = 'get', data = {}, callbackSuccess = () => {}, callbackFail = () => {}) {
    $.ajax({
        url: url,
        method: 'GET',
        xhrFields: {
            responseType: 'blob'
        },
        success: function (data) {
            // create link element
            let a = document.createElement('a'), 
                url = window.URL.createObjectURL(data);

            // initialize 
            a.href = url;
            a.download = filename;

            // append element to the body, 
            // a must, due to Firefox
            document.body.appendChild(a);

            // trigger download
            a.click();

            // delay a bit deletion of the element
            setTimeout(function(){
                window.URL.revokeObjectURL(url);
                document.body.removeChild(a);
            }, 100);

            // invoke callback if any 
            callbackSuccess(data);
        },
        error: function (err) {
            // invoke fail callback if any
            callbackFail(err)
        }
    });
М.Б.
  • 1,060
  • 13
  • 16
1

How about:

<meta http-equiv="refresh" content="5;url=http://site.com/file.ext">

This way works on all browsers (i think) and let you put a message like: "If the download doesn't start in five seconds, click here."

If you need it to be with javascript.. well...

document.write('<meta http-equiv="refresh" content="5;url=http://site.com/file.ext">');

Regards

  • Wow mate, this response is brilliant and exactly what I needed. Thanks from over a decade later – TKoL Oct 22 '20 at 09:48
1

Best solution as per new chrome specification https://developers.google.com/web/updates/2018/02/chrome-65-deprecations

Vanilla JavaScript

public static downloadFile(url: string): void {
     const xmlHttp = new XMLHttpRequest();
     xmlHttp.onreadystatechange = () => {
       if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
         const blobUrl = window.URL.createObjectURL(xmlHttp.response);
         const e = document.createElement('a');
         e.href = blobUrl;
         e.download = blobUrl.substr(blobUrl.lastIndexOf('/') + 1);
         document.body.appendChild(e);
         e.click();
         document.body.removeChild(e);
       }
     };
     xmlHttp.responseType = 'blob';
     xmlHttp.open('GET', url, true);
     xmlHttp.send(null);
   }

If you're using angular try this.

async downloadBrochure(url: string) {
    try {
      const res = await this.httpClient.get(url, { responseType: 'blob' }).toPromise();
      this.downloadFile(res);
    } catch (e) {
      console.log(e.body.message);
    }
  }

  downloadFile(data) {
    const url = window.URL.createObjectURL(data);
    const e = document.createElement('a');
    e.href = url;
    e.download = url.substr(url.lastIndexOf('/') + 1);
    document.body.appendChild(e);
    e.click();
    document.body.removeChild(e);
  }
0

A small/hidden iframe can work for this purpose.

That way you don't have to worry about closing the pop up.

Simon
  • 36,947
  • 2
  • 32
  • 27