How to get a file or blob from an object URL?
Asked Answered
W

10

252

I am allowing the user to load images into a page via drag&drop and other methods. When an image is dropped, I'm using URL.createObjectURL to convert to an object URL to display the image. I am not revoking the url, as I do reuse it.

So, when it comes time to create a FormData object, so that I can allow them to upload a form with one of those images in it, is there some way I can then reverse that Object URL back into a Blob or File so I can then append it to a FormData object?

Wolcott answered 9/8, 2012 at 3:0 Comment(4)
nevermind about the previous two comments - all you need to do is send an XMLHttpRequest to the blob URL.Premiere
Why not simply store the original file objects somewhere, then use the object URL to display them and on form submit use the original files?Allophone
@Allophone because trying to get the URL of the file being uploaded by the user displays as "C:\fakepath"Arytenoid
This is a XY problem. A script obtains references to one or multiple files as these are dropped onto a page. You only need to create URLs for these in order to create links for these for the user to use (look at what they dragged, f.e.), but for including the file(s) with submission of a form, you need to add them one way or another -- whether gotten back from URLs or the original objects. You can look at the DataTransfer class to reset your file input control, otherwise your form is almost useless anyway (you can forego it for script-assisted submission).Olympia
V
274

Modern solution:

let blob = await fetch(url).then(r => r.blob());

The url can be an object url or a normal url.

Vaporize answered 19/9, 2018 at 16:4 Comment(9)
And if you want to directly get a file from the promise, you can generate a file as follows. let file = await fetch(url).then(r => r.blob()).then(blobFile => new File([blobFile], "fileNameGoesHere", { type: "image/png" })Gallinacean
Unfortunately, this solution does not work for me in Chrome. The browser fails to load this URL.Roderick
Waldgeist, did you wrap it in the createObjectUrl()?Lessee
let file = await fetch(url).then(r => r.blob()).then(blobFile => new File([blobFile], "fileNameGoesHere", { type: "image/png" })). There was a missing bracket at the endHelbonia
net::ERR_FILE_NOT_FOUNDPresbyter
I needed to add another await before the r.blob(), so it is async r => await r.blob() although my code is kinda different, idk if it helps anyone.Mitchel
Although it's a popular solution, CORS policy will stop youStanislas
not working to meSilverpoint
if you have the ability to inject code before URL.createObjectURL function is invoked then you could do this URL.createObjectURL = function(param) { // grab the blob }Kisner
W
92

As gengkev alludes to in his comment above, it looks like the best/only way to do this is with an async xhr2 call:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'blob:http%3A//your.blob.url.here', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
  if (this.status == 200) {
    var myBlob = this.response;
    // myBlob is now the blob that the object URL pointed to.
  }
};
xhr.send();

Update (2018): For situations where ES5 can safely be used, Joe has a simpler ES5-based answer below.

Wolcott answered 10/8, 2012 at 12:25 Comment(12)
When would you ever have a objectURL which is not local to the current domain and scope?Wolcott
i did it same as above, but got 404 not found with the xhr. what's going on?Undo
Please note that some browsers (read old IE...), if you need to handle those see post #17657684Deerhound
Can I save this file to the local file system?Balloon
@Wolcott "When would you ever have a objectURL which is not local to the current domain and scope?" In my case I am trying to give a Amazon S3 blob a different filename, that is currently a "guid" on the S3 without extension. So, in my case I am using a cross-domain call.Francefrancene
"Object URLs are URLs that point to files on disk." ObjectURLs by definition can only be local.Wolcott
combined with https://mcmap.net/q/66791/-convert-blob-to-base64 for reading into base64, this really helped!Rowdyism
how can I use this when multiple urls need to passHatcher
How can we write this as as simple utility function like passing url and getting file anyoneHatcher
There is no “Joe” below.Blown
Anyone know where the Joe answer is?Claw
Unfortunately, it looks like that answer was deleted some time since that 2018 update, and I didn't archive a copy of it.Wolcott
F
19

Maybe someone finds this useful when working with React/Node/Axios. I used this for my Cloudinary image upload feature with react-dropzone on the UI.

    axios({
        method: 'get',
        url: file[0].preview, // blob url eg. blob:http://127.0.0.1:8000/e89c5d87-a634-4540-974c-30dc476825cc
        responseType: 'blob'
    }).then(function(response){
         var reader = new FileReader();
         reader.readAsDataURL(response.data); 
         reader.onloadend = function() {
             var base64data = reader.result;
             self.props.onMainImageDrop(base64data)
         }

    })
Fenner answered 6/2, 2018 at 11:44 Comment(2)
Does this work for cross domain requests? Twitter videos have blob URL. I need to be able to gather the blob object that blob URL is pointing to. I'm using fetch api in the browser, which is giving me this error – Refused to connect to 'blob:https://twitter.com/9e00aec3-6729-42fb-b5a7-01f50be302fa' because it violates the following Content Security Policy directive: "connect-src . Could you have a hint what might I might be doing wrong / not getting?Ulrich
I was able to use this one-liner to get the result with the blob as data property: const result = await axios.get(url, { responseType: "blob" });Unequaled
P
12

The problem with fetching the blob URL again is that this will create a full copy of the Blob's data, and so instead of having it only once in memory, you'll have it twice. With big Blobs this can blow your memory usage quite quickly.

It's rather unfortunate that the File API doesn't give us access to the currently linked Blobs, certainly they thought web-authors should store that Blob themselves at creation time anyway, which is true:

The best here is to store the object you used when creating the blob:// URL.

If you are afraid this would prevent the Blob from being Garbage Collected, you're right, but so does the blob:// URL in the first place, until you revoke it. So holding yourself a pointer to that Blob won't change a thing.

But for those who aren't responsible for the creation of the blob:// URI (e.g because a library made it), we can still fill that API hole ourselves by overriding the default URL.createObjectURL and URL.revokeObjectURL methods so that they do store references to the object passed.

Be sure to call this function before the code that does generate the blob:// URI is called.

// Adds an URL.getFromObjectURL( <blob:// URI> ) method
// returns the original object (<Blob> or <MediaSource>) the URI points to or null
(() => {
  // overrides URL methods to be able to retrieve the original blobs later on
  const old_create = URL.createObjectURL;
  const old_revoke = URL.revokeObjectURL;
  Object.defineProperty(URL, 'createObjectURL', {
    get: () => storeAndCreate
  });
  Object.defineProperty(URL, 'revokeObjectURL', {
    get: () => forgetAndRevoke
  });
  Object.defineProperty(URL, 'getFromObjectURL', {
    get: () => getBlob
  });
  const dict = {};

  function storeAndCreate(blob) {
    const url = old_create(blob); // let it throw if it has to
    dict[url] = blob;
    return url
  }

  function forgetAndRevoke(url) {
    old_revoke(url);
    try {
      if(new URL(url).protocol === 'blob:') {
        delete dict[url];
      }
    } catch(e){}
  }

  function getBlob(url) {
    return dict[url] || null;
  }
})();

//  Usage:
const blob = new Blob( ["foo"] );
const url = URL.createObjectURL( blob );
console.log( url );
const retrieved = URL.getFromObjectURL( url );
console.log( "retrieved Blob is Same Object?", retrieved === blob );
fetch( url ).then( (resp) => resp.blob() )
  .then( (fetched) => console.log( "fetched Blob is Same Object?", fetched === blob ) );

And an other advantage is that it can even retrieve MediaSource objects, while the fetching solutions would just err in that case.

Penang answered 8/4, 2021 at 6:29 Comment(4)
Good call. If I remember correctly, those URL methods did not exist back when I first asked this question.Wolcott
@Wolcott they still don't exist, we have to implement it ourselves (well I did so now you just have to include that script before URL.createObjectURL is called).Penang
Thanks for this answer! Do you have a Patreon where people can support your work?Samalla
I guess we need some extension for MediaSource too, because I don't quite get how to crack it when it's live already. Maybe catch appendStream in a similar waySlither
R
9

Using fetch for example like below:

 fetch(<"yoururl">, {
    method: 'GET',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + <your access token if need>
    },
       })
.then((response) => response.blob())
.then((blob) => {
// 2. Create blob link to download
 const url = window.URL.createObjectURL(new Blob([blob]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `sample.xlsx`);
 // 3. Append to html page
 document.body.appendChild(link);
 // 4. Force download
 link.click();
 // 5. Clean up and remove the link
 link.parentNode.removeChild(link);
})

You can paste in on Chrome console to test. the file with download with 'sample.xlsx' Hope it can help!

Rossuck answered 20/9, 2019 at 4:34 Comment(1)
I think the OP asked about converting a blob url to actual file/blob, rather than the other way around.Pentamerous
A
5

See Getting BLOB data from XHR request which points out that BlobBuilder doesn't work in Chrome so you need to use:

xhr.responseType = 'arraybuffer';
Antons answered 12/12, 2014 at 21:58 Comment(0)
C
2

If you show the file in a canvas anyway you can also convert the canvas content to a blob object.

canvas.toBlob(function(my_file){
  //.toBlob is only implemented in > FF18 but there is a polyfill 
  //for other browsers https://github.com/blueimp/JavaScript-Canvas-to-Blob
  var myBlob = (my_file);
})
Cyanotype answered 7/5, 2014 at 16:31 Comment(1)
Note that that's not actually giving you back the same original file; it's making a new image file on the fly. When I last tested that a couple years ago, I found that at least in Chrome, the new image file isn't compressed at all - I had 10k jpgs turning into 2.5 mb jpgs.Wolcott
F
2

Unfortunately @BrianFreud's answer doesn't fit my needs, I had a little different need, and I know that is not the answer for @BrianFreud's question, but I am leaving it here because a lot of persons got here with my same need. I needed something like 'How to get a file or blob from an URL?', and the current correct answer does not fit my needs because its not cross-domain.

I have a website that consumes images from an Amazon S3/Azure Storage, and there I store objects named with uniqueidentifiers:

sample: http://****.blob.core.windows.net/systemimages/bf142dc9-0185-4aee-a3f4-1e5e95a09bcf

Some of this images should be download from our system interface. To avoid passing this traffic through my HTTP server, since this objects does not require any security to be accessed (except by domain filtering), I decided to make a direct request on user's browser and use local processing to give the file a real name and extension.

To accomplish that I have used this great article from Henry Algus: http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/

1. First step: Add binary support to jquery

/**
*
* jquery.binarytransport.js
*
* @description. jQuery ajax transport for making binary data type requests.
* @version 1.0 
* @author Henry Algus <[email protected]>
*
*/

// use this transport for "binary" data type
$.ajaxTransport("+binary", function (options, originalOptions, jqXHR) {
    // check for conditions and support for blob / arraybuffer response type
    if (window.FormData && ((options.dataType && (options.dataType == 'binary')) || (options.data && ((window.ArrayBuffer && options.data instanceof ArrayBuffer) || (window.Blob && options.data instanceof Blob))))) {
        return {
            // create new XMLHttpRequest
            send: function (headers, callback) {
                // setup all variables
                var xhr = new XMLHttpRequest(),
        url = options.url,
        type = options.type,
        async = options.async || true,
        // blob or arraybuffer. Default is blob
        dataType = options.responseType || "blob",
        data = options.data || null,
        username = options.username || null,
        password = options.password || null;

                xhr.addEventListener('load', function () {
                    var data = {};
                    data[options.dataType] = xhr.response;
                    // make callback and send data
                    callback(xhr.status, xhr.statusText, data, xhr.getAllResponseHeaders());
                });

                xhr.open(type, url, async, username, password);

                // setup custom headers
                for (var i in headers) {
                    xhr.setRequestHeader(i, headers[i]);
                }

                xhr.responseType = dataType;
                xhr.send(data);
            },
            abort: function () {
                jqXHR.abort();
            }
        };
    }
});

2. Second step: Make a request using this transport type.

function downloadArt(url)
{
    $.ajax(url, {
        dataType: "binary",
        processData: false
    }).done(function (data) {
        // just my logic to name/create files
        var filename = url.substr(url.lastIndexOf('/') + 1) + '.png';
        var blob = new Blob([data], { type: 'image/png' });

        saveAs(blob, filename);
    });
}

Now you can use the Blob created as you want to, in my case I want to save it to disk.

3. Optional: Save file on user's computer using FileSaver

I have used FileSaver.js to save to disk the downloaded file, if you need to accomplish that, please use this javascript library:

https://github.com/eligrey/FileSaver.js/

I expect this to help others with more specific needs.

Francefrancene answered 22/11, 2016 at 11:52 Comment(4)
ObjectURLs are by definition local. Object URLs are URLs that point to files on disk. Given that there is no such thing as a cross-domain objectURL, you're combing two different concepts, thus your problem using the given solution.Wolcott
@Wolcott I got it that is not exactly the answer of this question. But I still think that its a good answer to leave since I got here looking for the answer of a little different question 'How to get a file or blob from an URL?'. If you check you own answer, there are 10 upvotes on 'It doesn't work in case of cross domain requests. '. So more than 10 persons got here looking for that. I decided then to leave it here.Francefrancene
Problem is, your answer knowingly doesn't answer this question. A normal URL and an object URL are two entirely different things. Regarding the # of upvotes on "It doesn't work in case of cross domain requests.", wouldn't you think it better explain why that's literally impossible here, and to point to somewhere that does show the answer for normal URLs, rather than confusing things here by conflating normal URLs and object URLs?Wolcott
@Wolcott feel free to suggest an edit on my answer. I will study about the subject, maybe I misunderstood what is an "objectURL" vs an "object URL"... English is not my native. I will make a research about the subject to give a better answer. But I think that are others that come here looking for something different. I did not search for "objectURL" to get here, that was my point before. But I got you too.Francefrancene
M
2

Following @Kaiido answer, another way to overload URL without messing with URL is to extend the URL class like this:

export class URLwithStore extends URL {
  static createObjectURL(blob) {
    const url = super.createObjectURL(blob);
    URLwithStore.store = { ...(URLwithStore.store ?? {}), [url]: blob };
    return url;
  }

  static getFromObjectURL(url) {
    return (URLwithStore.store ?? {})[url] ?? null;
  }

  static revokeObjectURL(url) {
    super.revokeObjectURL(url);
    if (
      new URL(url).protocol === "blob:" &&
      URLwithStore.store &&
      url in URLwithStore.store
    )
      delete URLwithStore.store[url];
  }
}

Usage

const blob = new Blob( ["foo"] );
const url = URLwithStore.createObjectURL( blob );
const retrieved = URLwithStore.getFromObjectURL( url );
console.log( "retrieved Blob is Same Object?", retrieved === blob );
Massenet answered 6/10, 2021 at 10:51 Comment(0)
D
0

Open Chrome, press F12 then to open console window then write

var url = "https://images.rawpixel.com/image_800/cHJpdmF0ZS9sci9pbWFnZXMvd2Vic2l0ZS8yMDIyLTA1L3BmLXMxMjctdGQtMDAyMV80LmpwZw.jpg"

then write

fetch(url).then(res => res.blob()).then(blob => {
    console.log(blob) //this will give blob
    var reader = new FileReader()
    reader.readAsDataURL(blob)
    reader.onloadend = function() {
        var base64string = reader.result
        console.log(base64string) //this will give base64string
    }

    var bloburl = URL.createObjectURL(blob)
    console.log(bloburl) //this will give bloburl
    var link = document.createElement("a")
    link.href = bloburl
    link.setAttribute('download', 'image.jpeg')
    document.body.appendChild(link)
    link.click()
})

enter image description here

Dunagan answered 16/4, 2024 at 7:38 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.