Axios Interceptors in JavaScript: Complete Guide
Axios interceptors let you inspect or change every request and response before your code sees them. Learn how to register, chain, and remove interceptors with real examples.
An Axios interceptor is a function that runs automatically before a request leaves your app, or before a response reaches your calling code. You register it once on an Axios instance, and it applies to every request or response that passes through that instance from then on.
The Axios HTTP client guide covers interceptors briefly as one of many features among timeouts and cancellation. This article goes deeper into just interceptors: how they run, in what order, and how to use them for auth tokens and retrying an expired session.
Here is a request interceptor that logs every outgoing URL before the request is sent, then returns the config so the request can continue normally:
import axios from "axios";
const api = axios.create({ baseURL: "https://api.example.com" });
api.interceptors.request.use((config) => {
console.log("Sending request to:", config.url);
// Console shows: Sending request to: /products
return config;
});The interceptor receives the request config object, logs the URL, then returns it unchanged. If the function does not return the config, Axios has nothing valid to send over the network.
Request Interceptors
A request interceptor takes two functions, one for a successful config and one for a rejected request, and it runs after you call a method like get or post, but before the network call actually happens. The example below attaches an authentication token to every outgoing request instead of repeating that header at every call site. It targets a browser environment specifically, since localStorage is not available in Node.js; a Node app would read the token from memory or an environment variable instead:
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem("authToken");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);This interceptor runs before every request made through api, so no individual call needs to know the token exists. The config object holds everything about the outgoing request, including its URL, headers, and body, and whatever the success handler returns becomes the final request that gets sent.
Response Interceptors
A response interceptor also takes two functions, one for successful responses in the 2xx range, one for errors, which include both network failures and non-2xx status codes. The next example redirects the user once a session has expired:
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
console.log("Session expired, redirecting to login");
}
return Promise.reject(error);
}
);Axios treats any status outside the 2xx range as an error by default, so a 401 response lands in the second function here rather than the first. Handling the redirect in this single place means every API call in the app gets the same behavior, instead of each call site checking the response status on its own.
Execution Order with Multiple Interceptors
Request and response interceptors run in opposite orders from each other, which matters once you register more than one on the same instance.
Request interceptors run in reverse registration order, so the one you added most recently runs closest to the network call. Response interceptors run in the order you registered them, so the first one you added sees the response before any later ones do. This is the opposite of what many developers expect on first read, so it helps to log a message inside each interceptor while testing if the exact order matters for your logic.
Removing an Interceptor with Eject
Registering an interceptor returns a numeric id. Passing that id to the eject method removes only that one interceptor, leaving every other registered interceptor untouched:
const loggerId = api.interceptors.request.use((config) => {
console.log("Request:", config.url);
return config;
});
api.interceptors.request.eject(loggerId);After this call, requests made through api no longer trigger the logger. Store the returned id in a variable at registration time, since there is no other way to target that specific interceptor for removal later.
Practical Example: Retrying After a Token Refresh
A common real use of interceptors is refreshing an expired access token and retrying the original request automatically, instead of forcing the user to log in again over a single expired token. This is one narrow case of a broader idea covered in API retry patterns in JavaScript. The _retry flag below stops the code from refreshing more than once per request:
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const { data } = await axios.post("/auth/refresh");
localStorage.setItem("authToken", data.accessToken);
return api(originalRequest);
}
return Promise.reject(error);
}
);error.config holds the original failed request, so calling api(originalRequest) resends it once the new token is stored. Without the _retry flag, a permanently invalid token would trigger an endless loop of refresh attempts, since every retried request would fail with 401 again.
Common Mistakes
| Mistake | Why it breaks | Fix |
|---|---|---|
| Forgetting to return config in a request interceptor | Axios sends an undefined config instead of the real request | Always return config at the end of the success handler |
| Forgetting to reject the error in an error handler | The error is swallowed and the calling code's catch block never runs | Return a rejected promise unless you are intentionally recovering |
| Registering interceptors on the shared global axios object | Every part of a large app is affected, including code that expects different behavior | Create a dedicated instance and register interceptors on that instance |
| Missing a retry flag in a token refresh interceptor | A permanently invalid token causes an infinite refresh loop | Mark the request so the refresh only happens once |
When to Reach for an Interceptor
Use an interceptor when the same logic needs to run for every request or response on an instance, such as attaching a token, logging, or refreshing a session. For logic that only applies to a single call, pass it directly in that call's own config instead of adding a global interceptor every other request also has to pass through.
Rune AI
Key Insights
- A request interceptor runs before every outgoing request; a response interceptor runs before every response reaches your code.
- Register interceptors with axios.interceptors.request.use() and axios.interceptors.response.use(), each taking a success handler and an error handler.
- Request interceptors run in reverse registration order; response interceptors run in the order they were registered.
- Store the id returned when registering an interceptor and pass it to eject() to remove that one interceptor.
- Interceptors are the standard place for attaching auth tokens, logging, and handling token refresh on 401 responses.
Frequently Asked Questions
Do interceptors run for every request on an Axios instance?
What order do multiple interceptors run in?
Can an interceptor stop a request from being sent?
Does axios interceptors work the same in Node.js as in the browser?
Conclusion
Interceptors turn repeated per-call logic, like attaching a token or logging every response, into a single place that runs automatically for every request. Register them once on an Axios instance, keep the success and error handlers focused, and remove them with eject when they are no longer needed.
More in this topic
Using Reflect and Proxy Together in JavaScript
Proxy traps intercept operations, but Reflect is what makes them behave correctly. Learn why Reflect belongs inside every trap and what breaks when you skip it.
Top JS Array Methods Interview Questions to Know
The array method questions that come up most often in JavaScript interviews, answered directly with short examples: map vs forEach, mutating vs non-mutating methods, reduce, and more.
JavaScript Reflect API: Advanced Architecture
The Reflect object exposes JavaScript's own internal operations as plain functions. Learn every Reflect method, what it returns, and why it exists as its own API.