Making a Chrome Extension download a file

72,840

Solution 1

Fast-forward 3 years, and now Google Chrome offers chrome.downloads API (since Chrome 31).

After declaring "downloads" permission in the manifest, one can initiate a download with this call:

chrome.downloads.download({
  url: "http://your.url/to/download",
  filename: "suggested/filename/with/relative.path" // Optional
});

If you want to generate the file content in the script, you can use Blob and URL APIs, e.g.:

var blob = new Blob(["array of", " parts of ", "text file"], {type: "text/plain"});
var url = URL.createObjectURL(blob);
chrome.downloads.download({
  url: url // The object URL can be used as download URL
  //...
});

For more options (i.e. Save As dialog, overwriting existing files, etc.), see the documentation.

Solution 2

I used a variation on the solution here

var downloadCSS = function () {
    window.URL = window.webkitURL || window.URL;
    file = new BlobBuilder(); //we used to need to check for 'WebKitBlobBuilder' here - but no need anymore
    file.append(someTextVar); //populate the file with whatever text it is that you want
    var a = document.createElement('a');
    a.href = window.URL.createObjectURL(file.getBlob('text/plain'));
    a.download = 'combined.css'; // set the file name
    a.style.display = 'none';
    document.body.appendChild(a);
    a.click(); //this is probably the key - simulatating a click on a download link
    delete a;// we don't need this anymore
}

One thing you need to bare in mind is that this code needs to execute on the page and not your extension - otherwise the user won't see the download action that chrome does. The download will still happen and you will be able to see it in the download tab, but they won't see the actual download happen.

Edit (afterthought about making your code execute on the content page):

The way you make an action occur on the content page rather than your extension is to use Chrome "message passing". Basically, you pass a message from your extension (which is almost like a separate page) to the content page that the extension is working with. You then have a listener that your extension has injected into the content page that reacts to the message and does the download. Something like this:

chrome.extension.onMessage.addListener(
  function (request, sender, sendResponse) {  
      if (request.greeting == "hello") {
          try{
              downloadCSS();
          }
          catch (err) {
              alert("Error: "+err.message);
          }
      }
  });

Solution 3

This is a slightly modified version of @Steve Mc's answer that just makes it into a generalized function that can easily be copied and used as is:

function exportInputs() {
    downloadFileFromText('inputs.ini','dummy content!!')
}

function downloadFileFromText(filename, content) {
    var a = document.createElement('a');
    var blob = new Blob([ content ], {type : "text/plain;charset=UTF-8"});
    a.href = window.URL.createObjectURL(blob);
    a.download = filename;
    a.style.display = 'none';
    document.body.appendChild(a);
    a.click(); //this is probably the key - simulating a click on a download link
    delete a;// we don't need this anymore
}

Solution 4

Here's a concise way to download a file using "downloads" permission in Chrome manifest using @Xan and @AmanicA's solution

function downloadFile(options) {
    if(!options.url) {
        var blob = new Blob([ options.content ], {type : "text/plain;charset=UTF-8"});
        options.url = window.URL.createObjectURL(blob);
    }
    chrome.downloads.download({
        url: options.url,
        filename: options.filename
    })
}

// Download file with custom content
downloadFile({
  filename: "foo.txt",
  content: "bar"
});

// Download file from external host
downloadFile({
  filename: "foo.txt",
  url: "http://your.url/to/download"
});

Solution 5

I did it as follows in Appmator code on Github.

The basic approach is to build your Blob, however you want (Chrome has a responseBlob on XmlHttpRequest so you can use that), create an iframe (hidden, display:none) then assign the src of the iframe to be the Blob.

This will initiate a download and save it to the filesystem. The only problem is, you can't set the filename yet.

var bb = new (window.BlobBuilder || window.WebKitBlobBuilder)();

var output = Builder.output({"binary":true});
var ui8a = new Uint8Array(output.length);

for(var i = 0; i< output.length; i++) {
  ui8a[i] = output.charCodeAt(i);
}

bb.append(ui8a.buffer);

var blob = bb.getBlob("application/octet-stream");
var saveas = document.createElement("iframe");
saveas.style.display = "none";

if(!!window.createObjectURL == false) {
  saveas.src = window.webkitURL.createObjectURL(blob); 
}
else {
  saveas.src = window.createObjectURL(blob); 
}

document.body.appendChild(saveas);

An example of using XmlHttpRequest's responseBlob (see: http://www.w3.org/TR/XMLHttpRequest2/#dom-xmlhttprequest-responseblob)

var xhr = new XmlHttpRequest();
xhr.overrideMimeType("application/octet-stream"); // Or what ever mimeType you want.
xhr.onreadystatechanged = function() {
if(xhr.readyState == 4 && xhr.status == 200) {

  var blob = xhr.responseBlob();
  var saveas = document.createElement("iframe");
  saveas.style.display = "none";

  if(!!window.createObjectURL == false) {
    saveas.src = window.webkitURL.createObjectURL(blob); 
  }
  else {
    saveas.src = window.createObjectURL(blob); 
  }

  document.body.appendChild(saveas);
}
Share:
72,840
Franz Payer
Author by

Franz Payer

Updated on July 08, 2022

Comments

  • Franz Payer
    Franz Payer almost 2 years

    I am creating an extension that will download a mp3 file off a website. I am trying to do this by creating a new tab with the link to the mp3 file, but chrome keeps opening it inside the player instead of downloading it. Is there any way I can create a pop-up to ask the user to "save-as" the file?

  • Franz Payer
    Franz Payer almost 13 years
    Where do I specify the file I am trying to download?
  • Kinlan
    Kinlan almost 13 years
    That is up to you. It could be responseBlob on the xhr request
  • Franz Payer
    Franz Payer almost 13 years
    Yah, Can you edit the code to show how to do that? I have no idea where to put the location.
  • Franz Payer
    Franz Payer almost 13 years
    How can you set it to download as the correct file type? Chrome keeps trying to download it as an html file. The page that the blob is created on also freezes up and crashes. It also appears that the source of the blob is blank.
  • Kinlan
    Kinlan almost 13 years
    Can you show some example code of what you are trying to do that makes it crash - it is so hard to tell what you are now trying to do. I would say that I have added a small piece of code to override the mimetype.
  • Franz Payer
    Franz Payer almost 13 years
  • marlar
    marlar almost 10 years
    Thanks! Is it possible to have Chrome append a file to an existing file? I want to do download a file made of fragments, resulting in one complete file instead of multiple fragtments.
  • Xan
    Xan almost 10 years
    @marlar For that, it would be best to employ the filesystem API (despite the warning, it's supported in Chrome) to get the chunks, build the complete file, and then download the result into the Downloads folder. But it's a different question; if you still need help, ask a new question.
  • jumpjack
    jumpjack almost 8 years
    "filename" is completely ignored on my system!
  • questionasker
    questionasker almost 7 years
    @Xan i got this error : Unchecked runtime.lastError while running downloads.download: Invalid filename any idea ?
  • Ian Hyzy
    Ian Hyzy almost 7 years
    For current people: SteveMC's answer no longer works, but this one does.
  • ejm
    ejm over 6 years
    delete a; doesn't do what you think it does here, you actually need a.parentNode.removeChild(a);. (Deleting the reference to a is not necessary because as a local variable its lost when the function completes).
  • daka
    daka about 6 years
    Do you if this still works?, chrome.downloads doesn't work the way I'd like it to.
  • daka
    daka about 6 years
    Still works, but hit the same problem I was having with chrome downloads. Chrome is not ok with allowing extensions to download multiple files.
  • AGamePlayer
    AGamePlayer about 6 years
    Just wondering, how do I download a base64 image in this case?
  • SimplGy
    SimplGy over 5 years
    The concision is much appreciated, wish I'd noticed it sooner. There's no need to simulate a click on a hidden a tag. Thanks!
  • brasofilo
    brasofilo over 5 years
    @anunixercoder probably already found the issue, but for whomever cames after: this code needs to be on background.js, it doesn't run on content.js
  • gumuruh
    gumuruh over 4 years
    but can i request POST for this Download process?
  • Xan
    Xan over 4 years
    @gumuruh Yes; see docs, you can set the method, headers and post body.
  • Rishav
    Rishav over 4 years
    Is it at all possible to download the file using an external download manager like IDM. By default in chrome when I download the file it downloads using IDM but in this case, when downloading using the API it always downloads with the default chrome downloader.
  • Craig Lambie
    Craig Lambie over 4 years
    Is this script in the content script or background script or somewhere else? I am getting a "Uncaught TypeError: Cannot read property 'download' of undefined" on the line "chrome.downloads.download({
  • Raine Revere
    Raine Revere over 3 years
    Change type: "text/plain" to change the file extension
  • yusuf_sabri
    yusuf_sabri about 3 years
    I use chrome.downloads.download and I want to catch if the response status is 400, how can I do it?