Custom FormData in Axios for Non-Browser Runtimes

This guide explains how to configure and use custom FormData polyfills with the Axios HTTP client across non-browser environments such as Node.js, serverless functions, and edge runtimes. You will learn how to resolve multipart/form-data transmission issues, integrate packages like form-data or formdata-node, and configure Axios instance settings to handle custom payload serializations seamlessly.

Why Axios Requires a Polyfill in Non-Browser Environments

In standard browser environments, the FormData interface is built into the window global scope. Non-browser runtimes like Node.js (prior to version 18 or in environments with stripped global web APIs) lack a native implementation of the standard FormData API. When handling file uploads or multipart payloads, Axios requires a compliant FormData class to build the payload and calculate appropriate boundary headers.

Choosing a Polyfill

Depending on your target runtime and standards compliance requirements, choose one of the following packages:

Install your chosen package via your package manager:

npm install form-data
# or for the spec-compliant version:
npm install formdata-node

Configuring Axios with Custom FormData

Axios (v1.x and newer) supports configuring custom environment adapters and classes directly via the env configuration object or by supplying the polyfill instance directly to the request.

Method 1: Configuring an Axios Instance Globally

You can define a custom FormData constructor inside an Axios instance using the env.FormData config option:

import axios from 'axios';
import FormData from 'form-data';

const apiClient = axios.create({
  baseURL: 'https://api.example.com',
  env: {
    FormData: FormData
  }
});

export default apiClient;

With this configuration, any plain object passed with file buffers or streams that Axios serializes internally into FormData will utilize your provided constructor.

Method 2: Supplying the Polyfilled Instance Directly

When using form-data (the Node-specific library), instantiate the form manually and attach streams or buffers. You must also supply the headers generated by the library to ensure boundary delimiters are set correctly:

import axios from 'axios';
import FormData from 'form-data';
import fs from 'fs';

async function uploadFile() {
  const form = new FormData();
  
  form.append('username', 'john_doe');
  form.append('file', fs.createReadStream('./document.pdf'));

  const response = await axios.post('https://api.example.com/upload', form, {
    headers: {
      ...form.getHeaders()
    }
  });

  return response.data;
}

Method 3: Using a Spec-Compliant Polyfill (formdata-node)

If you are using formdata-node, it behaves identically to browser FormData. When using Axios v1.x, Axios automatically serializes the boundary and headers when you pass a standard-compliant FormData instance:

import axios from 'axios';
import { FormData, File } from 'formdata-node';
import { readFile } from 'fs/promises';

async function uploadSpecCompliant() {
  const form = new FormData();
  
  const fileBuffer = await readFile('./document.pdf');
  const file = new File([fileBuffer], 'document.pdf', { type: 'application/pdf' });

  form.set('file', file);
  form.set('description', 'User uploaded PDF');

  const response = await axios.post('https://api.example.com/upload', form);

  return response.data;
}

Troubleshooting Common Boundary Errors

When sending multipart/form-data without configuring headers properly, servers may return 400 Bad Request or fail to parse files due to a missing boundary parameter in the Content-Type header.