Automatic PDF download in all browsers

Automatic PDF download in all browsers

Downloading a PDF automatically (such as when clicking a submit button on a form) is tricky and especially so when it needs to work in Internet Explorer. Browsing the web, I found several 'solutions' to this problem, but, in the end, only one solution worked properly.

The code

Let's assume we're going to use axios to make a post request to an API, which in turn responds with a base64 string (inline data) and a content type of application/pdf. In this case, we'll need to convert the inline data to a blob using a function like this:

/**
 * Convert Base64 (dataURI) to blob with mimeType
 * @param {String} dataURI
 * @returns {Blob}
 */
const dataURItoBlob = dataURI => {
  const byteString = atob(dataURI.split(',')[1])
  const mimeString = dataURI
    .split(',')[0]
    .split(':')[1]
    .split(';')[0]

  // write the bytes of the string to an ArrayBuffer
  const ab = new ArrayBuffer(byteString.length)
  const ia = new Uint8Array(ab)
  for (let i = 0; i < byteString.length; i++) {
    ia[i] = byteString.charCodeAt(i)
  }

  // write the ArrayBuffer to a blob, and you're done
  const bb = new Blob([ab], { type: mimeString })
  return bb
}

Now, let's assume we have a payload sent to axios to receive the base64 string and download the PDF:

/**
 * requestPdf function to return a PDF from API endpoint * Converts base64 string to blob and inits download
 * @param {String} url
 * @param {Object} payload
 */
const requestPdf = async (url, payload) => {
  return await axios.post(url, payload).then(response => {
    const linkSource = `data:application/pdf;base64,${response}`
    const pdfBlob = dataURItoBlob(linkSource)

    /**
     * Internet Explorer stuff!
     */
    if (window.navigator && window.navigator.msSaveOrOpenBlob) {
      window.navigator.msSaveOrOpenBlob(
        pdfBlob,
        `${Date.now()}.pdf`
      )
      return
    }

    const url = window.URL.createObjectURL(pdfBlob)
    const link = document.createElement('a')
    link.href = url
    link.setAttribute('download', `${Date.now()}.pdf`)
    document.body.appendChild(link)
    link.click()

    link.remove()
    return response
  })
}

And that's it! Assign the above function to a click event or whatever you prefer and the PDF will automatically save in any browser.

The requestPdf function uses the dataURItoBlob function we created above to convert the base64 string to a blob (note that the linkSource variable may not be required if data:application/pdf;base64, is already at the start of the string provided from the API endpoint). From there, it checks if the browser is Internet Explorer and initiates the download if it is. If not, the function creates a fake button in the DOM, clicks it and removes it again (again, downloading the PDF). Please note the PDF, in this case, is using the current date as the filename. Feel free to change `${Date.now()}.pdf` to the filename you prefer.