Custom Params Serializer for Nested Objects in Axios
Axios does not natively serialize deeply nested objects into standard
query string formats by default, often leading to unexpected
[object Object] values in URLs. This guide provides a
direct, step-by-step walkthrough on how to configure custom
paramsSerializer functions in Axios using both popular
libraries like qs and lightweight vanilla JavaScript
functions to correctly format nested data structures.
Understanding the Nested Object Problem in Axios
When passing a nested object inside the params property
of an Axios request:
axios.get('/api/search', {
params: {
filter: {
category: 'electronics',
price: { min: 100, max: 500 }
}
}
});By default, Axios may serialize this into
?filter=[object+Object]. To serialize it into a standard
format (such as
?filter[category]=electronics&filter[price][min]=100&filter[price][max]=500),
you must supply a custom serializer function.
Method 1: Using the
qs Library (Recommended)
The most robust and industry-standard way to serialize nested objects
is with the qs library.
1. Install qs
npm install qs2. Configure
paramsSerializer in Axios (Axios v1.x+)
In modern Axios versions (v1.0.0 and above),
paramsSerializer accepts an object with a custom
serialize method:
import axios from 'axios';
import qs from 'qs';
const apiClient = axios.create({
baseURL: 'https://api.example.com',
paramsSerializer: {
serialize: (params) => {
return qs.stringify(params, {
arrayFormat: 'brackets', // 'brackets' | 'indices' | 'repeat' | 'comma'
allowDots: true // serializes nested objects as obj.subKey=value if preferred
});
}
}
});
// Usage
apiClient.get('/products', {
params: {
user: {
id: 42,
profile: { role: 'admin' }
}
}
});
// Resulting URL: /products?user%5Bid%5D=42&user%5Bprofile%5D%5Brole%5D=adminMethod 2: Writing a Vanilla JavaScript Recursive Serializer
If you want to avoid third-party dependencies, you can implement a recursive serializer using standard JavaScript:
function buildQueryString(params, prefix = '') {
const queryParts = [];
for (const [key, value] of Object.entries(params)) {
if (value === null || value === undefined) {
continue;
}
const fullKey = prefix ? `${prefix}[${encodeURIComponent(key)}]` : encodeURIComponent(key);
if (typeof value === 'object' && !(value instanceof Date)) {
queryParts.push(buildQueryString(value, fullKey));
} else {
queryParts.push(`${fullKey}=${encodeURIComponent(value)}`);
}
}
return queryParts.filter(Boolean).join('&');
}Applying the Vanilla Serializer to Axios:
import axios from 'axios';
const api = axios.create({
baseURL: 'https://api.example.com',
paramsSerializer: {
serialize: (params) => buildQueryString(params)
}
});
// Example Request
api.get('/items', {
params: {
sort: 'date',
filters: {
tags: ['news', 'tech'],
author: { id: 10 }
}
}
});Per-Request Configuration
You can also apply or override the paramsSerializer on
individual requests rather than across an entire Axios instance:
axios.get('https://api.example.com/data', {
params: {
filter: { active: true, level: 2 }
},
paramsSerializer: {
serialize: (params) => qs.stringify(params, { encode: false })
}
});