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:
form-data: A widely used, Node-specific library with its own stream-based API.formdata-node: A spec-compliant implementation of the Web APIFormDatastandard.
Install your chosen package via your package manager:
npm install form-data
# or for the spec-compliant version:
npm install formdata-nodeConfiguring 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.
- Avoid hardcoding
Content-Type: multipart/form-data: Hardcoding this string strips the required dynamic boundary parameter. - Use
form.getHeaders()for Nodeform-data: Always mergeform.getHeaders()into the Axiosheadersobject when using the legacyform-datapackage. - Let Axios manage headers for standard polyfills:
When using spec-compliant polyfills like
formdata-nodewith Axios v1.x, omit theContent-Typeheader so Axios can automatically generate the correct boundary.