How to Export CSV and PDF Files Using Axios

Handling file downloads such as CSVs and PDFs in modern web applications requires configuring the Axios HTTP client to process raw binary streams rather than parsing standard JSON payloads. This guide provides a straightforward, robust pattern to request binary data from an API, extract filenames from response headers, trigger browser-based file downloads via Blobs, and gracefully handle server-side errors returned as binary objects.


1. Set the responseType to 'blob'

By default, Axios attempts to parse server responses as JSON. When downloading files (PDF, CSV, Excel, ZIP), you must explicitly set responseType: 'blob' in the request configuration. This ensures Axios returns the raw binary data encapsulated within a Blob object.

import axios from 'axios';

const fetchFile = async (url) => {
  return await axios.get(url, {
    responseType: 'blob', // Critical for binary data handling
  });
};

2. Implement the Download Trigger

Browsers do not automatically prompt a download when receiving a file via an AJAX request. You must programmatically create a temporary anchor (<a>) tag, assign it an object URL generated from the Blob, trigger a click event, and clean up the object URL to prevent memory leaks.

const triggerBrowserDownload = (blob, defaultFilename = 'download') => {
  // Create a temporary URL pointing to the Blob
  const blobUrl = window.URL.createObjectURL(blob);

  // Create an invisible <a> element
  const link = document.createElement('a');
  link.href = blobUrl;
  link.setAttribute('download', defaultFilename);
  document.body.appendChild(link);

  // Trigger download and cleanup
  link.click();
  link.parentNode.removeChild(link);
  window.URL.revokeObjectURL(blobUrl);
};

3. Extracting the Filename from Headers

If your backend specifies the filename using the Content-Disposition header, extract it directly from the Axios response headers. Note that your server must expose this header via CORS using Access-Control-Expose-Headers: Content-Disposition.

const extractFilename = (response, fallbackName) => {
  const disposition = response.headers['content-disposition'];
  if (disposition && disposition.includes('filename=')) {
    const matches = disposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
    if (matches && matches[1]) {
      return matches[1].replace(/['"]/g, '');
    }
  }
  return fallbackName;
};

4. Handling Error Responses Correctly

When responseType: 'blob' is set, error payloads (like a 400 or 500 JSON response) are also returned as Blobs. To display server error messages, convert the error Blob back to JSON text:

const parseBlobError = async (error) => {
  if (error.response && error.response.data instanceof Blob) {
    try {
      const errorText = await error.response.data.text();
      return JSON.parse(errorText);
    } catch {
      return { message: 'An unknown error occurred while downloading the file.' };
    }
  }
  return error;
};

5. Complete Reusable Export Utility

Combine the steps into a single, clean function for exporting any file type:

import axios from 'axios';

/**
 * Downloads a file from an API endpoint cleanly using Axios.
 * @param {string} url - The endpoint URL.
 * @param {string} defaultFilename - Fallback filename with extension (e.g., 'report.pdf', 'data.csv').
 * @param {object} params - Optional query parameters.
 */
export const exportFile = async (url, defaultFilename = 'export.bin', params = {}) => {
  try {
    const response = await axios.get(url, {
      params,
      responseType: 'blob',
    });

    const filename = extractFilename(response, defaultFilename);
    triggerBrowserDownload(response.data, filename);
  } catch (error) {
    const parsedError = await parseBlobError(error);
    console.error('File export failed:', parsedError);
    throw parsedError;
  }
};

Usage Example

// Export a PDF report
await exportFile('/api/reports/quarterly', 'quarterly-report.pdf');

// Export a CSV dataset with filters
await exportFile('/api/users/export', 'users.csv', { status: 'active' });