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.


The most robust and industry-standard way to serialize nested objects is with the qs library.

1. Install qs

npm install qs

2. 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=admin

Method 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 })
  }
});