'How to get the filename from a file downloaded using Javascript Fetch API?

In my Javascript client, I'm using Fetch API to call the server to retrieve a server-generated file. I'm using the following client-side code:

var _url = "";    
var initParms = {  
   method: "GET",
   mode: 'cors'
}

fetch(_url, initParms)
.then(response => {
   if(response.ok){
      alert(response.headers.get("content-disposition"));
      return response.blob();
   }

   throw new Error("Network response was not OK.");
})
.then(blob => {
   var url = new URL.createObjectURL(blob);
})     

This actually works just fine. However, the server generates a filename for the file and includes it in the response as part of the content-disposition header.

I need to save this file to the user's machine using the filename generated by the server. In Postman, I can actually see that the content-disposition header of the response is set to: Content-Disposition: attachment;filename=myfilename.txt.

I made an attempt to read the content-disposition from the response (see the alert in my JS code), but I always get null (even though the same response shows the content-disposition in Postman).

Am I doing something wrong? Is there a way to retrieve the filename using the fetch response? Is there a better way to get the filename from the server along with the file?

P.S. This is my server-side code for returning the file:

Controller Action

public IHttpActionResult GetFile(){
   return new FileResult("myfilename.txt","Hello World!");
}

FileResult Class

public class FileResult : IHttpActionResult
{
   private string _fileText = "";
   private string _fileName = "";
   private string _contentType = "";

   public FileResult(string name, string text)
   {
       _fileText = text;
       _fileName = name;
       _contentType = "text/plain";
   }

   public Task<HttpResponseMessage> ExecuteActionAsync(CancellationToken token)
   {
        Stream _stream = null;
        if (_contentType == "text/plain")
        {
            var bytes = Encoding.Unicode.GetBytes(_fileText);
            _stream = new MemoryStream(bytes);
        }
        return Task.Run(() =>
        {
            var response = new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StreamContent(_stream),
            };

            response.Content.Headers.ContentType = 
                new MediaTypeHeaderValue(_contentType);
            response.Content.Headers.ContentDisposition = 
                new ContentDispositionHeaderValue("attachment")
            {
                FileName = _fileName
            };

            return response;

        }, token);

Edit

My question was specifically about the fetch not the ajax api. Also, in my code, I showed that I was already reading the header from the response exactly like the accepted answer demonstrated on the suggested answer. However, as stated in my post, this solution was not working with fetch.



Solution 1:[1]

So, shortly after posting this question, I ran across this issue on Github. It apparently has to do with using CORS.

The suggested work around was adding Access-Control-Expose-Headers:Content-Disposition to the response header on the server.

This worked!

Solution 2:[2]

You can extract the filename from the content-disposition header like this:

let filename = '';

fetch(`/url`, { headers: { Authorization: `Bearer ${token}` }}).then((res) => {
    const header = res.headers.get('Content-Disposition');
    const parts = header!.split(';');
    filename = parts[1].split('=')[1];
    return res.blob();
}).then((blob) => {
    // Use `filename` here e.g. with file-saver:
    // saveAs(blob, filename);
});

Solution 3:[3]

Decided to post this, as the accepted answer (although helpful to many people) does not actually answer the original question as to how:

"to get the filename from a file downloaded using javascript fetch api?".

One can read the filename (as shown below), and download the file using a similar approach to this (the recommended downloadjs library by this does not get updated anymore; hence, I wouldn't suggest using it). The below also takes into account scenarios where the filename includes unicode characters (i.e.,-, !, (, ), etc.) and hence, comes (utf-8 encoded) in the form of, for instance, filename*=utf-8''Na%C3%AFve%20file.txt (see here for more details). In such cases, the decodeURIComponent() function is used to decode the filename. Working example is given below:

const url ='http://127.0.0.1:8000/'
fetch(url)
    .then(res => {
        const disposition = res.headers.get('Content-Disposition');
        filename = disposition.split(/;(.+)/)[1].split(/=(.+)/)[1];
        if (filename.toLowerCase().startsWith("utf-8''"))
            filename = decodeURIComponent(filename.replace("utf-8''", ''));
        else
            filename = filename.replace(/['"]/g, '');
        return res.blob();
    })
    .then(blob => {
        var url = window.URL.createObjectURL(blob);
        var a = document.createElement('a');
        a.href = url;
        a.download = filename;
        document.body.appendChild(a); // append the element to the dom, otherwise it won't work in Firefox
        a.click();
        a.remove(); // afterwards, remove the element  
    });

As mentioned in the accepted answer, if you are doing a cross-domain request, make sure to add Access-Control-Expose-Headers:Content-Disposition to the response headers on the server side (to expose the Content-Disposition header), otherwise the filename won't be accessible on client side. For instance:

headers = {'Access-Control-Expose-Headers': 'Content-Disposition'}
return FileResponse("Naïve file.txt", filename="Naïve file.txt", headers=headers)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 RHarris
Solution 2
Solution 3