2025-09-06

Force iOS Safari (and other browsers) to download media file

đź’ˇ How to Force File Downloads in Safari Using JavaScript (JS-Only Solution)

Safari has a mind of its own when it comes to downloading media files. Even if your server correctly sets Content-Disposition: attachment and Content-Type: application/octet-stream, Safari might still display files like PDFs, MP4s, or images instead of downloading them.

If you’re a frontend developer looking for a JS-only workaround that actually works, this post is for you.


đź§  Why Safari Ignores Content-Disposition

Safari uses its own internal logic to decide whether to show or download a file. If it recognizes the MIME type (like video/mp4, application/pdf, image/jpeg), it often overrides your server headers and opens the file inline.

That’s frustrating — especially when all you want is a reliable download button.


âś… The Reliable JavaScript Solution

By using the Fetch API to grab the file as a Blob, and then simulating a click on a dynamically created <a> element with a download attribute, you can bypass Safari’s preview behavior entirely.

đź§Ş Code Example

<button id="download-btn">Download File</button>

<script>
function forceDownload(url, filename) {
  fetch(url)
    .then((response) => {
      if (!response.ok) {
        throw new Error("Network response was not ok");
      }
      return response.blob();
    })
    .then((blob) => {
      const blobUrl = URL.createObjectURL(blob);
      const link = document.createElement("a");
      link.href = blobUrl;
      link.download = filename;
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
      URL.revokeObjectURL(blobUrl);
    })
    .catch((error) => {
      console.error("Download failed:", error);
      alert("Download failed.");
    });
}

document.getElementById("download-btn").addEventListener("click", () => {
  // Adjust the URL and filename as needed
  forceDownload("/download/video.mp4", "my_video.mp4");
});
</script>

← Previous Post | Next Post →