How to force the browser to download remote resource

Monday, April 5, 2021
2 min read

In a recent project, I needed to offer the ability to download files. Until recently I wasn't aware of the HTMLAnchorElement.download property. I'll explain how to use the download property and why this wouldn't work for my situation.

First though, here is a link to the repository and demo:

Using the HTML5 download property

By default, anchor elements will navigate to the href element. You can add the download property which will prompt the browser to download the file:

<a download href="https://darrenwhite.dev/images/john-fowler-d2YMQ-hZ3og-unsplash.jpg">
  Download image
</a>

However, images and also Firefox only allows users to download files of the same origin. As the files for my project would be served from a different origin I would need a way to force the download.

Using fetch to download

My solution was to use fetch to get the remote resource:

const file = await fetch(*URL_TO_REMOTE_RESOURCE*);

const blob = await file.blob();
const url = URL.createObjectURL(blob);

Once the resource is downloaded, create an anchor element with the download and trigger the click event:

const downloadLink = document.createElement("a");

downloadLink.href = linkSource;
downloadLink.download = [download name];
downloadLink.click();

Here's the complete code:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>dlw download as data demo</title>
    <link rel="stylesheet" href="./build/tailwind.css" />
  </head>
  <body class="bg-gray-900 h-full">
    <main class="min-h-screen flex flex-col justify-center py-12 sm:px-6 lg:px-8">
      <div class="max-w-3xl mx-auto flex flex-col items-center space-y-2">
        <div class="aspect-w-3 aspect-h-2 w-96">
          <img 
            class="object-fill shadow-lg rounded-lg"
            src="https://darrenwhite.dev/images/john-fowler-d2YMQ-hZ3og-unsplash.jpg"
            alt="Star shot in Cathedral Valley State Park, Nevada"
          >
        </div>
        <a
          href="https://darrenwhite.dev/images/john-fowler-d2YMQ-hZ3og-unsplash.jpg"
          class="inline-flex items-center mt-4 px-6 py-3 border border-transparent shadow-sm text-base font-medium rounded-md text-white bg-pink-600 hover:bg-pink-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500"
          data-remote
        >
          Remote
          <svg
            class="ml-2 w-6 h-6"
            fill="currentColor"
            viewBox="0 0 20 20"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              fill-rule="evenodd"
              d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
              clip-rule="evenodd"
            ></path>
          </svg>
        </a>
      <div>
    </main>

    <script defer type="text/javascript">
      const a = document.querySelector('a[data-remote]')

      a.addEventListener('click', async (e) => {
        e.preventDefault()

        const file = await fetch(e.target.href);

        const blob = await file.blob();

        const blobUrl = URL.createObjectURL(blob);

        const downloadLink = document.createElement("a");
        downloadLink.href = blobUrl;
        downloadLink.download = 'download-example.jpg';

        downloadLink.click();
      })
    </script>
  </body>
</html>

Find me on