Configuration That Stays Safe and Predictable
Axios configuration answers a deceptively important question: who owns a decision, and how far should that decision travel? This chapter focuses on the three scopes Axios combines—library defaults, an instance, and one request—then applies that model to headers, URLs, query parameters, and TypeScript. The practical result is a client whose behavior can be explained without searching through mutable global state or treating a base URL as a security boundary.
A configuration value is not just a setting. It can decide where a request goes, which credentials accompany it, how data is encoded, how long the call can wait, and how a response is interpreted. The right scope is therefore part of the behavior.
Three scopes, three owners
Library defaults are a baseline, not an application policy
Axios starts with library defaults. They establish the behavior needed for an ordinary request: a default GET method when no method is supplied, JSON-oriented request and response transformations, a default adapter priority, common headers, a zero timeout, and a 2xx status rule. These defaults make the first request short, but they are intentionally broad. They cannot know whether a particular application contacts one trusted API, several unrelated services, or an untrusted endpoint.
That is why a library default should be read as a compatibility baseline rather than a complete production policy. A zero timeout means Axios does not create a timeout unless the caller supplies one. Unlimited body and response lengths preserve legitimate large transfers, but they are not a safe ceiling for every workload. The baseline makes the client usable; the application must decide what “usable” means under its own timing, size, and trust conditions.
An instance gives a destination’s policy a home
An Axios instance is the most useful middle scope. It carries defaults for a group of requests that share an API or trust relationship. A typical instance might have a base URL, an explicit timeout, a response convention, and an interceptor that attaches credentials for that API:
const billingApi = axios.create({
baseURL: 'https://billing.example.com/v2',
timeout: 5000,
});
billingApi.defaults.headers.common.Authorization = 'Bearer token';
The important design choice is not the syntax. It is that the credential and destination now have a visible relationship. If the same application also calls a public catalog service, a separate instance can carry the catalog’s policy. The two clients may share Axios’s request contract while keeping their headers, base URLs, interceptors, and adapter choices distinct.
The global Axios client can reach more than one host. The documentation therefore warns against installing a global common Authorization header when an application talks to multiple domains: the token can be sent to every host contacted through that client, including a third-party service. A custom instance narrows the intended scope, but it does not validate every URL passed to it. The caller still needs to keep requests compatible with the instance’s trust relationship.
An instance is also a useful ownership boundary for behavior that should be consistent but not universal. A service client might use a five-second timeout and a response interceptor that unwraps one API’s response envelope. A file-transfer client might need a different timeout, response type, and size policy. Putting both policies on the global client would make each request depend on whichever code last mutated shared defaults.
A request is the exception layer
The per-request config should express a local difference from the instance policy. A long-running report might use a longer timeout. A download might request a stream rather than JSON. One endpoint might require a custom query serializer or a different content type. These exceptions remain visible at the call site and do not silently change all future calls.
Axios merges these layers with option-specific rules. Many properties inherit the later value from the instance or library defaults. The URL, method, and body are request-specific. Headers receive a deeper merge, but the body does not inherit or combine with a body stored in defaults. That asymmetry is deliberate: a reusable client can define how requests are sent without accidentally adding fields from one endpoint’s payload to another endpoint’s payload.
Imagine an instance used for both /users and /reports. If a shared default body could be deep-merged into every request, a field intended for reports might appear in a user update, or a sensitive value might cross an endpoint boundary. Axios instead treats data as belonging to the request. If an application truly needs shared body fields, it must add them through a deliberately scoped interceptor or transform, where the endpoint condition is visible and testable.
This is the general rule: put a decision at the narrowest scope that still expresses its intended reuse. Use a library default for broad compatibility, an instance for a coherent destination policy, and a request for a one-off exception. Do not use global mutability as a substitute for deciding who owns the behavior.
The scopes interact through precedence, not inheritance of everything
The merge order is library defaults first, instance defaults second, and request config last. Later values normally take precedence, but “later wins” should not be mistaken for “every object is recursively combined.” Axios has distinct strategies for direct values, arrays, headers, and request-specific fields.
That distinction matters when debugging. If an instance timeout is unexpectedly absent, inspect the request override and the option’s merge rule. If a header appears twice with different capitalization, inspect header normalization rather than assuming two independent HTTP headers were intended. If a request body contains only the fields supplied at the call site, that is the documented behavior, not a failed deep merge.
The merged configuration also carries application-specific information when it is supplied as an own enumerable symbol property. This is a narrow extension point for typed interceptors or adapters. It is different from relying on arbitrary inherited properties, which can introduce behavior from outside the configuration the caller intended to provide.
URLs, headers, and parameters are contracts with the server
A base URL constructs a request; it does not authorize one
baseURL is useful because an instance can express a stable host and path prefix once, while individual requests use relative paths. allowAbsoluteUrls controls whether an absolute request URL can replace that base under the configured policy. Both options answer a URL-construction question.
They do not answer the security question “Is this destination approved?” The distinction is easy to miss when an application forwards a path or URL from a user. An absolute URL can point to a different host under the default behavior. A relative path containing .. segments can be normalized by the final URL parser and resolve outside the path prefix a developer had in mind. Even forcing absolute URLs through a base URL does not make arbitrary relative input safe.
A caller that accepts untrusted destinations needs a separate validation policy. It may reject absolute and protocol-relative URLs, reject path traversal segments, resolve the candidate URL, and check its origin and pathname against an allowlist. The right rule depends on the application. Axios can construct and validate the form of an HTTP(S) URL, including rejecting malformed values such as https:example.com that omit the required //, but it cannot infer the set of hosts a business considers safe.
This is an important example of a policy boundary: a convenience option can reduce repetition without becoming an authorization mechanism. Keeping those roles separate prevents an instance’s base URL from creating false confidence.
AxiosHeaders turns header mutation into a controlled operation
Axios uses AxiosHeaders as a case-insensitive, Map-like representation of headers. HTTP header names are case-insensitive, but callers often supply different capitalization at different points in an application. AxiosHeaders can find the same logical header despite those spelling differences, normalize values, and render the final set for a transport.
It also models the difference between “not set” and “deliberately do not send this.” An undefined value is absent. A null or false value is omitted when the headers are rendered; false additionally signals that a normal set operation should not overwrite it unless the caller explicitly requests a rewrite. This lets a caller opt out of a default such as User-Agent or Content-Type without deleting and recreating an entire header object.
The distinction between direct object access and the headers API matters in interceptors. A request interceptor receives a prepared header object, and the supported pattern is to use methods such as set, get, has, and delete. Direct property manipulation remains available for compatibility, but it can create duplicate keys with different capitalization until Axios normalizes them. The class also rejects unsafe header names and values containing line breaks, which gives ordinary header construction a defense against injection. It does not decide whether a valid header should go to a particular destination.
Header state can change at several stages. Instance defaults may provide common headers. Request config may override them. An interceptor may attach a token. A transform may select a content type based on the body. Authentication and FormData handling may add or adjust headers for the selected representation. Seeing headers as a contract that evolves through the request lifecycle is more accurate than treating them as a static object copied directly to the wire.
Query parameters are part of the API contract
Axios keeps query parameters separate from the base URL so a call can express data as structured values:
api.get('/users', {
params: { status: 'active', page: 2 },
});
The default serializer turns those values into a query string, including nested entries and arrays according to Axios’s conventions. That output is not universally interchangeable with every server parser. Some backends expect bracket notation, some expect dots, and some require a particular percent-encoding policy. Axios therefore supports a custom whole-parameter serializer and a custom encoder.
By default, Axios renders a few encoded characters—such as colons, dollar signs, commas, and spaces—as readable query characters. That is valid for a query component under the documented RFC 3986 interpretation, but a backend may require strict percent encoding. In that case, the caller can provide an encoder such as encodeURIComponent on an instance or a single request. This is not cosmetic tuning: it is a choice about the language the client and server use to describe the same filter.
The same principle applies to null and undefined values, arrays, and nested objects. A serializer decides which values are omitted, repeated, bracketed, or encoded. If the server’s parser does not share those conventions, the request may be syntactically valid but semantically different from what the application intended. The right fix is an explicit serializer policy, not a claim that Axios’s default must fit every backend.
TypeScript can preserve the scope of intent
Axios’s TypeScript definitions distinguish response data from request data and query parameters. A request can be typed so that the response body has one shape, the request body has another, and params has a third. Those types are carried through request config, response config, errors, adapters, and typed instances.
That separation mirrors the configuration model. The response type describes what the application expects to receive. The request-body type describes what it is allowed to send. The query type describes the values the serializer receives. A typed instance can then make an interceptor’s configuration and an adapter’s input agree with the caller’s intent.
Types do not prove that a remote server returned truthful JSON, and they do not make a custom transform safe. Runtime validation is still needed at an external trust boundary. What the types provide is a clearer contract inside the application: when a request or query shape changes, the code that owns that meaning has a chance to fail at compile time rather than silently changing the wire representation.
Configuration becomes predictable when ownership is visible
A well-scoped Axios client is not the one with the most defaults. It is the one for which a reader can answer three questions: which destination does this policy belong to, which requests are allowed to override it, and which behavior still depends on the adapter or server?
Use an instance for a coherent API and its credentials. Keep per-request overrides close to the operation they explain. Treat request bodies as local data, not reusable ambient state. Use AxiosHeaders when a header must be added, removed, or protected from later defaults. Treat baseURL as URL construction and validate untrusted destinations separately. Treat params serialization as an agreement with the server. Use TypeScript to preserve the distinction between what is sent, what is queried, and what is received.
With those ownership rules in place, the configuration is no longer a pile of flags. It is a map of responsibility. The remaining question is whether that map still holds when the runtime and transport change—and what the reader should retain before choosing a data representation or adapter.