How To Set Axios POST Headers and Manage Headers Across All Request Types
Axios POST headers are one of the most important items for JavaScript developers working with HTTP. Configure them incorrectly, and your requests fail, authentication breaks, or data gets rejected. The good news? Axios gives developers several ways to manage headers, including inline on individual requests, globally via defaults, through reusable instances, and dynamically with interceptors. This guide explores how to use Axios to set headers across all request types, covering POST, GET, PUT, and DELETE requests, plus common pitfalls and fixes.
Justinas Tamasevicius
Last updated: Apr 22, 2026
22 min read

TL;DR
- Axios headers can be set per request (inline config), globally via axios.defaults, per instance with axios.create(), or dynamically using interceptors
- For POST requests, pass headers in the third argument: axios.post(url, data, { headers: {...} })
- Never manually set Content-Type when sending FormData
- Use Axios interceptors headers for token injection and refresh logic
- Combine Axios custom headers with proxy rotation for reliable JavaScript web scraping
- Use per-request headers for one-off calls where you need specific, isolated config
- Set axios.defaults.headers for headers that every request in your app should carry
- Create isolated Axios instances with axios.create() when you work with multiple APIs
- Use request interceptors to inject tokens dynamically, and response interceptors to handle token refresh
- Never manually set Content-Type: multipart/form-data. Axios handles the boundary automatically.
Installing Axios and basic setup
Axios is an isomorphic, promise-based HTTP client that allows developers to use the same codebase for network requests in both Node.js and browser environments, which matters because some header behaviors differ between the two environments.
Install it via the package manager your project uses:
If you're working browser-only without a build step, use the CDN:
Then import the Axios library in your code, depending on your JavaScript environment:
ESM (Modern JavaScript):
CommonJS (Older Node.js):
- Use ESM (import) if you're starting a new project
- Use CommonJS (require) only if you're maintaining older codebases
Note: All examples in this guide use Axios v1.0+ and Node.js 18+.
CORS restrictions apply in browsers but not in Node.js. That means Axios custom headers that work fine in Node.js might trigger CORS preflight errors in the browser, depending on server configuration. We'll cover more on that in the troubleshooting section.
Setting headers for individual Axios requests
Per-request headers are the most explicit option. You pass a headers object inside the config argument on each call. They apply only to that single request and nothing else.
This is the right choice when you're making a one-off call with unique credentials, testing a specific endpoint, or calling an API that doesn't fit your app's global defaults.
GET request with custom headers
For GET requests, the config object (which holds your headers) is the second argument.
To make this example fully testable, we'll use the JSONPlaceholder, a publicly accessible test API endpoint that returns the headers you send.
Run it with:
Sample output:
The code uses Axios to send a request to JSONPlaceholder and fetch a sample post. It then prints the response status, the returned data, and the content type or shows an error if the request fails.
Here are a few things worth knowing here:
- Accept: application/json signals to the server that you want JSON back. Many APIs will return a different format (or an error) if this header is missing or wrong.
- X-Request-Source is a custom non-standard header; anything prefixed with X- is application-defined, and the server has to be built to read it.
- Accept-Language affects localized responses on APIs that support it. It's useful if you're fetching content for a specific location.
These headers exist only for this request. They don't affect the global Axios state, any other pending requests, or requests made elsewhere in your app.
POST request with headers and body data
This is the core pattern behind Axios post headers.
The key thing to get right is: For POST requests, Axios takes three arguments: axios.post(url, data, config)
We'll use httpbin.org/post, a real API testing service specifically designed for request inspection.
The code sends an HTTP POST request to https://httpbin.org/post using Axios, including a JSON body with user details and custom headers like Content-Type and Authorization. It then logs the response status and data if successful, or prints error details if the request fails.
Note: When you pass a plain JavaScript object as the request body, Axios automatically serializes it to JSON and sets Content-Type: application/json.
You don't need to set Content-Type manually in most cases, as Axios handles it. You only need to set it explicitly if you're working with a different format, sending raw strings, or overriding an instance default.
Besides, many REST APIs (Stripe, Shopify, etc.) support the Idempotency-Key header to make POST requests safe to retry. If your request times out and you retry with the same key, the server recognizes it as a duplicate. It then returns the original response instead of creating a second record.
You should also not swap the argument order. For instance, if you pass the config object as the second argument and the data as the third, Axios won't throw an error; it'll silently send an empty body and the config object as the body, which is baffling to debug.
Always remember: POST/PUT/PATCH are (url, data, config), while GET/DELETE are (url, config).
PUT and DELETE requests with headers
PUT follows the same three-argument pattern as POST. The only difference is semantic. For instance, POST creates a new resource, and PUT replaces an existing one entirely. PATCH updates only specific fields.
Here's a complete script demonstrating both.
A few things worth noting here.
Both PUT and PATCH can include headers such as:
- Authorization (authentication token)
- If-Match (used for optimistic concurrency with ETags)
Note: Some testing APIs (like httpbin) don't enforce ETags or concurrency rules, they only echo what you send.
DELETE uses (url, config) like GET, but unlike GET it may include a request body via config.data. when you need to pass extra context to the server. As shown below:
Request bodies in DELETE requests are technically valid per the HTTP spec, but some servers and proxies don't support them.
Setting global headers in Axios
Once your app makes more than a handful of requests, setting the same Authorization header on every call can become repetitive and fragile.
If the token changes, you have to update it everywhere. Axios gives you the following ways to define headers once and have them applied automatically.
Using axios.defaults.headers.common
This sets a header that attaches to every request regardless of HTTP method. The typical pattern is to set it once at application startup, often right after the user logs in, and you have a token.
Here's how to set global Axios default headers after login so every request automatically includes API authentication and app metadata.
Sample output:
The code shows how to set global headers in Axios so that every HTTP request automatically includes things like an authorization token and app information after a user logs in.
It then sends test requests to a demo API (httpbin) to confirm the headers are being sent correctly, and demonstrates how removing or isolating those headers affects later requests.
Here's something worth noting: axios.defaults modifies the global Axios instance. Every request in your app inherits these headers, including requests made by third-party libraries that use Axios internally. This can cause unexpected header leakage. If you need isolation, use axios.create() instead.
Moreover, deleting a default header on logout is important because if you just set it to an empty string, Axios will still send the header, just with an empty value. The server may accept it, reject it, or behave unpredictably.
Method-specific global defaults
You can also set defaults that only apply to a specific HTTP method.
The code sets up default settings in Axios so that all HTTP requests automatically include certain headers, like sending JSON for POST and PUT requests and accepting JSON responses for all requests.
It also adds a rule that automatically adds an extra header for POST requests and then sends a test request to show what headers are actually being sent and received.
Creating Axios instances with axios.create()
The cleanest solution for apps that talk to multiple APIs is creating separate Axios instances using axios.create(). Each instance has its own defaults, its own interceptors, and no connection to the global instance.
It's the right pattern you can use for any application that talks to more than one API, which is almost every real application.
This code creates three separate Axios clients, each configured for a different API with its own settings like base URL, timeout, and headers.
It then makes requests using each client to fetch data, inspect request headers, and send a sample payment request, printing the results to the console.
Each instance keeps its configuration isolated. For example, adding an interceptor to internalAPI doesn't affect weatherAPI. Equally, changing internalAPI's Authorization header doesn't touch paymentsAPI. This is crucial when you're building applications that authenticate differently with different third-party services.
Pro tip: When building scrapers or data pipelines, create a dedicated Axios instance for scraping. That instance gets the browser-like headers, the proxy agent, and any retry logic separate from the instances handling your internal API calls.
Common header scenarios: Authorization, content types, and sessions
Syntax aside, the headers you'll actually spend time on in real projects fall into a few well-defined categories. Here's how to handle each one correctly.
Authorization and bearer tokens
The Authorization header is the most frequent use case of Axios custom headers. There are three common patterns depending on how the API authenticates, as shown here:
Sample output:
This code sends four different HTTP requests using Axios to demonstrate common API authentication methods, including Bearer tokens, API keys in headers, and Basic Auth.
To run it, ensure you have installed Axios with npm install axios, ensure Node.js is set to use ES modules, then execute the file with node auth_examples.mjs.
Content-Type for JSON, forms, and file uploads
Content-Type tells the server how to interpret the request body. If you get this wrong, your data can arrive as garbled bytes the server can't deserialize. Here's when to use each value:
Content-Type
When to use it
How to set it in Axios
application/json
Sending a JavaScript object as JSON
Automatic when the body is a plain object. Set manually only if overriding.
application/x-www-form-urlencoded
Traditional HTML form submissions, OAuth token exchanges
Encode body with new URLSearchParams(data). Set Content-Type manually.
multipart/form-data
File uploads, mixed file and field submissions
DON'T set manually. Axios plus the runtime add the boundary parameter automatically.
text/plain
Sending raw text, webhooks that expect plain strings
Set manually. Pass a string as the body, not an object.
application/octet-stream
Streaming binary data, large file uploads
Set manually. Pass a buffer or stream as the body.
The multipart/form-data rule is important enough to show both the wrong and correct approaches side by side.
Node.js (using form-data package)
In Node.js, use the form-data package and call form.getHeaders() to get the correct Content-Type with boundary included automatically.
This script creates a small text file, then uploads it from a Node.js environment using the form-data package and Axios. It sends both the file and a userId to httpbin.org, then prints the server's response so you can verify the file and form data were received correctly.
Session and cookie headers
Not all APIs use bearer tokens. Some use server-side sessions with cookies, others use custom session headers, and many modern web apps need CSRF protection on top of sessions.
You'll need to install dependencies if you haven't done that already:
This snippet shows two common authentication patterns in Axios: cookie-based sessions and header-based sessions.
CSRF tokens are required by most server-rendered web frameworks, such as Rails, Django, and Laravel, for state-changing requests. The server embeds the token in the page HTML; your JavaScript reads it and sends it back:
The interceptor approach is more robust when the server rotates the CSRF token on each response (which some frameworks do for extra security).
Instead of setting the header once at startup, the interceptor reads the token freshly from the DOM before every mutating request, so you always send a valid token even after the server has rotated it.
Using interceptors to manage headers dynamically
Interceptors are middleware for your Axios instance. A request interceptor runs before Axios sends each request; a response interceptor runs after the response arrives. They're the right tool when header logic needs to be dynamic; reading from state, reacting to responses, or applying conditional rules.
Unlike defaults, which are static values set at configuration time, interceptors are functions that run at request time and can read current application state, call async functions, or make decisions based on what request is being sent.
Here's how request and response interceptors let you inject or modify headers automatically.
Request interceptors for injecting headers
A single request interceptor runs just before Axios sends each request. You get full access to the config object, including headers:
This code creates an Axios instance and uses a request interceptor to automatically add headers before every request is sent. It injects an authorization token plus custom headers like a unique trace ID and timestamp into each outgoing request.
It then makes a GET request to https://httpbin.org/headers and prints back the headers that were received by the server. This helps demonstrate how interceptors can dynamically modify requests without changing each request manually.
You must not forget the return config object. Otherwise, if your interceptor doesn't return the config object, Axios receives undefined, and the request hangs or throws a cryptic error. Always return config or a modified copy of it.
You can register multiple interceptors as well; they execute in the order they were added. This lets you compose header logic: one interceptor for auth, another for logging, another for tracing.
When you run this code via:
You'll first see:
Then for each request, you'll see logs like:
GET request log
POST request log
Each successful request triggers:
Printed twice: once for GET, once for POST.
Then the final output:
This code sets up several request interceptors that each add different behavior: one adds an auth token, one adds tracing information, and one logs the request details. It also adds a response interceptor that logs successful responses or errors.
Then it runs a GET and a POST request to httpbin.org to show how all interceptors work together, automatically modifying requests and handling responses in a structured way.
This pattern is similar to how request handling works in other ecosystems. For example, in Python, you might use the Requests library.
Response interceptors for token refresh
The token refresh pattern is the most important real-world use of response interceptors. When an access token expires, the server returns a 401 Unauthorized. Instead of propagating the error to your UI, the interceptor catches it, refreshes the token, and retries the original request, transparently:
This code sets up an Axios HTTP client that automatically handles expired authentication tokens by refreshing them and retrying failed requests while keeping the user logged in without interruption.
Without the refreshPromise deduplication, if five requests fail with 401 at the same time, you'd fire five parallel refresh requests.
Most auth servers accept only one refresh per token and invalidate the refresh token after its first use.
The second through fifth refresh calls would fail with invalid_grant, logging the user out even though they're still active. The shared promise keeps all five retried requests waiting for the same single refresh call.
Conditional headers based on request URL or method
Interceptors can inspect config.url and config.method to decide which headers to attach. This is cleaner than putting conditional logic in every individual API call:
This is a solid Axios request-interceptor example for conditionally injecting headers based on route using HTTP method.
The pattern of checking if (!config.headers['Idempotency-Key']) before setting a header is deliberate. It lets individual callers override the interceptor's default.
If a specific POST call sets its own Idempotency-Key (because it needs a deterministic key for retry logic), the interceptor won't clobber it. This is the right way to build interceptors: add defaults, don't override explicit caller choices.
Advanced header behaviors and troubleshooting
Most Axios header problems come from a small set of root causes. This section covers the tricky parts: merge order, CORS rules, casing behavior, and the FormData pitfall to save your debugging time.
How Axios merges headers from different sources
When you set headers in multiple places, Axios merges them in a specific order. You need to understand the order because each layer can override the ones before it.
Priority
Source
Override behavior
1 (lowest)
Axios library defaults
Built-in defaults like Accept: application/json
2
axios.defaults.headers.common
Applied to all methods; overrides library defaults
3
axios.defaults.headers[method]
e.g., axios.defaults.headers.post — method-specific
4
Instance defaults (axios.create headers)
Overrides global defaults for this instance
5
Per-request config headers
Passed inline to .get/.post/etc.
6 (highest)
Request interceptor
Runs last, can override everything above it
You may encounter a situation where you set an Authorization header in your per-request config, but the interceptor unconditionally overwrites it with a different token. The interceptor always wins.
Fix it by guarding in the interceptor:
This code shows how to avoid clobbering per-request headers with a global interceptor.
Setting headers dynamically from environment variables
Never hardcode API keys or tokens in source code because they will eventually be committed to a repository.
Create a .env file with the following information:
Import environment variables into your code, as shown below:
The code creates an Axios HTTP client that automatically reads an API base URL and API key from environment variables instead of hardcoding them in the source code. It then sends a request to https://httpbin.org/headers and prints the response so you can confirm that the API key was correctly included in the request headers.
Note: Environment variables set in the browser at build time are bundled into the JavaScript output and visible to users. Don't expose secret keys this way. In browser apps, sensitive API calls should go through a backend proxy that holds the real credentials.
CORS restrictions and browser vs. Node.js differences
CORS (Cross-Origin Resource Sharing) is a browser security feature. The browser blocks cross-origin requests that the server hasn't explicitly allowed. Node.js has no such restriction. That means you can send any header to any server from Node.js without a CORS preflight.
This distinction matters when you're testing an Axios request in Node.js that you'll later run in a browser, or when you're wondering why your scraper works fine but the same logic fails in the browser.
Here's what that means for headers:
- In Node.js, you can send any header to any server without CORS restrictions
- In browsers: if a custom header triggers a CORS preflight (an OPTIONS request), the server must respond with Access-Control-Allow-Headers listing that header. Otherwise, the request fails before Axios sends it
- Common custom headers like Authorization, Content-Type with non-standard values, and X-* headers typically trigger preflights
- withCredentials: true requires the server to send Access-Control-Allow-Credentials: true and a specific (not wildcard) origin
If you're hitting CORS errors, this issue is always a server configuration problem, never an Axios problem. Axios can't change the browser's security rules. To fix this, you need to update the CORS middleware to allow your headers and origin.
Header casing issues
HTTP/1.1 defines headers as case-insensitive, and Axios normalizes header names internally (converting them to lowercase before merging). But a few edge cases can still bite you:
- Some legacy servers running custom HTTP parsers are case-sensitive. Always use standard HTTP casing to avoid this: Content-Type, Authorization, X-API-Key
- When you read response headers from response.headers, Axios returns them lowercased regardless of what the server sent. So check response.headers['content-type'] not Content-Type.
- If you set the same header twice with different casing, say authorization and Authorization in the same config object, the last one wins after Axios normalizes both to lowercase
- HTTP/2 (used by many modern APIs) requires lowercase header names by spec. Axios handles this for you under the hood via the underlying http2 module, but if you're doing raw HTTP/2 elsewhere, use lowercase.
To avoid edge cases, use standard HTTP header casing as shown here:
File upload Content-Type pitfall
This is one of the most common Axios bugs. When you create a FormData object, the browser will generate a unique boundary string like ----WebKitFormBoundaryXyZ123. This boundary is what separates each form field in the request body. The full Content-Type header looks like:
When you manually set Content-Type: multipart/form-data in your Axios config, you're replacing that full header with just the type without the boundary. The server receives a multipart body but has no delimiter to split it by, so it can't parse any of the fields.
The server error you'll get depends on the backend framework, but common ones are: "no file received", "missing required field", "400 Bad Request", or a silent failure where fields are empty on the server side.
This code uploads a file (an image) and some related data to a remote API using a multipart/form-data HTTP request.
The maxBodyLength and maxContentLength settings are also commonly needed for file uploads. Axios has a 10MB limit on body size by default.
Uploading a video, a high-resolution image, or a large dataset without raising these limits can result in a 'Request body larger than maxBodyLength' error that looks like an Axios bug but is actually a configuration limit.
Using Axios with proxies for web scraping
Axios handles web scraping requests well in Node.js, but scraping requires more care with headers than standard API calls. This is because servers actively inspect request metadata to identify and block bots.
The right header strategy, combined with proxy rotation, can determine whether your scraper works reliably or gets blocked within minutes.
Setting browser-like headers for scraping
When a browser makes a request, it sends a predictable set of headers that identify its version, platform, language preference, and accepted formats. A default Axios request sends almost none of these. That mismatch is the primary signal servers use to detect non-human traffic.
Here's a browser header profile to set on your scraping instance:
Here's what these headers actually mean:
- User-Agent. It identifies the browser and OS. Servers use this for compatibility decisions (mobile vs desktop rendering, etc.).
- Accept. Tells the server what response formats the client can handle, such as HTML, XML, and images.
- Accept-Language. Preferred language for content. Helps with localization.
- Accept-Encoding. Compression formats supported, such as gzip, deflate, and br. Browsers use this to reduce bandwidth.
- Connection: keep-alive. Reuses TCP connections instead of opening a new one each time.
- Upgrade-Insecure-Requests. Signals preference for HTTPS over HTTP when available.
Configuring proxy settings in Axios
Routing requests through a proxy hides your real IP address and can help with privacy, debugging, or accessing region-restricted services. Axios supports proxy configuration in two main ways:
- A simple built-in proxy option for quick setup
- A more flexible HTTPS agent when you need advanced control, like custom certificates or connection tuning
To route requests through a proxy server, you can use the proxy configuration option directly in your Axios request or instance. Here's a Decodo example:
This code shows two different ways to send Axios HTTP requests through a proxy server instead of directly from your machine.
Use the agent approach when you need HTTPS tunneling, need to rotate agents dynamically, or when the native proxy option doesn't work with your proxy provider's setup. The proxy: false is intentional when using an agent. Without it, Axios may try to apply both proxy mechanisms, and the behavior gets unpredictable.
Configuring rotating proxies to avoid rate limits
Sending many requests from a single IP to the same target can trigger rate limits and bans quickly. Normally, sites typically block an IP after 50–200 requests per session, depending on their protection level.
Rotating proxies cycle through different IP addresses so each request appears to come from a different location.
You can integrate Decodo residential proxies with Axios by configuring the proxy option with Decodo credentials to enable automated IP rotation and stable connections for web requests.
This setup helps you avoid rate limits, improve anonymity, and maintain consistent scraping performance across multiple endpoints when collecting structured or unstructured data from websites that enforce strict access controls or geo restrictions.
First, install dependencies:
Then:
For each request (1 → 5), you'll see something like:
When manual header management gets too complex
Some sites go beyond IP blocking and header inspection. They identify Axios by its TLS handshake signature before even looking at headers, check JS APIs like canvas rendering and WebGL, JavaScript challenges, and CAPTCHA challenges.
At that point, managing Axios post headers and proxy settings yourself no longer solves the problem since the block happens at a layer below headers.
The Decodo Web Scraping API handles all of these layers. It routes requests through residential proxies, uses real browser fingerprints, executes JavaScript challenges, and returns the final rendered HTML or extracted data. You make one Axios call to the API and get clean output back:
This approach is especially useful for dynamic sites built with React, Vue, or Angular, where the data you want isn't in the initial HTML but loaded by JavaScript after the page renders.
Axios + proxies, sorted
Plug Decodo's rotating residential proxies into your Axios config and stop worrying about IP bans.
Final thoughts
Managing Axios post headers well is mostly about choosing the right tool for the scope of the problem. Use per-request headers when you need something for one call, axios.defaults, or axios.create() when the same header applies to many requests. Meanwhile, use interceptors when headers need to be dynamic, such as reading from a store, refreshing on 401, or varying by route.
Most Axios headers bugs trace back to a handful of root causes: the wrong merge order overriding what you set, missing CORS configuration on the server side, or manually setting Content-Type on a FormData upload and stripping the boundary. Keep these in mind, and you'll avoid 90% of the common pitfalls.
Lastly, when you combine proper Axios set headers practices with proxy rotation, Axios becomes a capable tool for both API integration and large-scale data collection.
Skip the boilerplate
Decodo's Web Scraping API returns ready-to-parse data – no proxy agents, no retry interceptors, no CAPTCHA logic.
About the author

Justinas Tamasevicius
Director of Engineering
Justinas Tamaševičius is Director of Engineering with over two decades of expertise in software development. What started as a self-taught passion during his school years has evolved into a distinguished career spanning backend engineering, system architecture, and infrastructure development.
Connect with Justinas via LinkedIn.
All information on Decodo Blog is provided on an as is basis and for informational purposes only. We make no representation and disclaim all liability with respect to your use of any information contained on Decodo Blog or any third-party websites that may belinked therein.


