---
url: https://axios-cache-interceptor.js.org/config/request-specifics.md
---
# Request specifics

Each request can customize its caching through the `cache` property.

::: tip

Every request-specific property can also be passed to `setupCache`, the same way as the
[global options](../config.md), to change the default for all requests.

:::

## id

* Type: `string`
* Default: *(generated by the current
  [key generator](../guide/request-id.md#custom-generator))*

A custom [Request ID](../guide/request-id.md) for this request. When it is not set, the key
generator creates one from the request properties.

## cache

* Type: `Partial<CacheProperties<R, D>> | false`
* Default: `{}` *(inherits from the global configuration)*

::: tip

Any property you leave out inherits its value from the global configuration.

:::

The `cache` property is where all per-request cache customization happens. Pass an object
with the cache properties you want to change for this request.

To disable caching for a specific request, use `cache: { enabled: false }`. Disabling the
cache does not remove an existing entry from the storage:

```ts
// Make a request with cache disabled
const { id: requestId } = await axios.get('url', {
  cache: { enabled: false }
});

// Delete the cache entry for this request if needed
await axios.storage.remove(requestId);
```

`cache: false` still works for backward compatibility, but it will be removed in the next
major release. Use `cache: { enabled: false }` instead.

## cache.enabled

* Type: `boolean`
* Default: `true`

Whether the cache is enabled for this request. When set to `false`, the request skips the
cache entirely.

Set it to `false` globally and `true` per request for an **opt-in cache**.

### Example: opt-in cache

Disable the cache by default and enable it only for specific endpoints:

```ts
import { setupCache } from 'axios-cache-interceptor';

// Cache is disabled by default
const axios = setupCache(axiosInstance, {
  enabled: false
});

// Most requests won't use the cache
await axios.get('/api/realtime-data'); // Not cached

// Enable the cache for specific heavy or expensive requests
await axios.get('/api/heavy-computation', {
  cache: {
    enabled: true,
    ttl: 1000 * 60 * 10 // Cache for 10 minutes
  }
}); // Cached
```

### Example: opt-out cache

The cache is enabled by default and disabled for specific endpoints:

```ts
import { setupCache } from 'axios-cache-interceptor';

// Cache is enabled by default (you can omit this option)
const axios = setupCache(axiosInstance, {
  enabled: true
});

// Most requests use the cache
await axios.get('/api/user-profile'); // Cached

// Disable the cache for specific real-time endpoints
await axios.get('/api/live-stock-prices', {
  cache: { enabled: false }
}); // Not cached
```

## cache.ttl

* Type: `number | ((response: CacheAxiosResponse) => number | Promise<number>)`
* Default: `1000 * 60 * 5` *(5 minutes)*

::: warning

When [**interpretHeader**](#cache-interpretheader) is enabled, this value is only used when
the response headers don't determine a TTL.

:::

The time, in milliseconds, until the cached value expires.

If you pass a function, it receives the complete response and must return the TTL.

The `ttl` is only applied when the response is **first cached**; changing it on later
requests has no effect on the existing entry. See
[#1024](https://github.com/arthurfiorette/axios-cache-interceptor/issues/1024) for
workarounds.

## cache.interpretHeader

* Type: `boolean`
* Default: `true`

When enabled, the `ttl` is inferred from the response headers, following the MDN docs and
the HTTP specification. Responses with `Cache-Control: no-cache` or `no-store` are not cached.

See the
[`interpretHeader` implementation](https://github.com/arthurfiorette/axios-cache-interceptor/blob/main/src/header/interpreter.ts)
for more information.

## cache.cacheTakeover

* Type: `boolean`
* Default: `true`

Most of this library's caching depends on standard HTTP headers, and browsers use the same
headers for their own cache.

::: details You can see the browser cache in the network tab of your browser's dev tools.

![Network tab in Chrome DevTools](../assets/disk-cache-screenshot.png "A network disk-cache example")

:::

When your routes include `Cache-Control` in their responses, both the library and your
browser may cache the response, resulting in a **double layer of cache**.

This option adds headers to the request that tell any client or adapter not to cache the
response, so only the library caches it. Headers you already set on the request are not
overwritten.

**These headers are only added to this request and don't affect any other request or
response the server handles.**

Headers included:

* `Cache-Control: no-cache, no-store, must-revalidate, max-age=0`
* `Pragma: no-cache`
* `Expires: 0`

::: info Safari compatibility

The `max-age=0` directive is included for Safari (including iOS Safari), which has
historically cached more aggressively and may ignore `no-cache` on its own.

:::

::: tip Alternative

`cacheTakeover` works in most browsers, according to
[this StackOverflow answer](https://stackoverflow.com/a/2068407), but it may be unreliable in
rare cases because of browser-specific behavior or network intermediaries.

For maximum reliability, add a random query parameter instead:

```ts
axios.get(
  `/api/data?cachebuster=${Math.random().toString(36).slice(2)}`,
  {
    id: 'api-data-endpoint' // Keeps the same cache key even though the URL changes
  }
);
```

Your backend can ignore the `cachebuster` value. This **guarantees** the browser won't cache
the response while axios-cache-interceptor keeps working as usual.

:::

::: warning CORS considerations

This option does not work for **CORS** requests when the server doesn't allow these headers.
The browser throws:
`Request header field Pragma is not allowed by Access-Control-Allow-Headers in preflight response.`

To fix it, add `Cache-Control`, `Pragma` and `Expires` to the `Access-Control-Allow-Headers`
of your server's CORS configuration.

If you cannot change the CORS configuration, you can:

1. Disable this option (`cacheTakeover: false`).
2. Use the query parameter approach described above.

Learn more about why this option is enabled by default in
[#437](https://github.com/arthurfiorette/axios-cache-interceptor/issues/437#issuecomment-1361262194)
and in this [StackOverflow answer](https://stackoverflow.com/a/2068407).

:::

## cache.methods

* Type: `Method[]`
* Default: `["get", "head"]`

The HTTP methods that should be cached. The default only caches `GET` and `HEAD`; use this
option to enable caching for `POST`, `PUT`, `DELETE` and other methods.

To enable caching for `POST` requests:

```ts
// Enables caching for POST requests globally
const axios = setupCache(instance, {
  methods: ['get', 'post']
});

// Or just for this request
axios.post('url', data, {
  cache: {
    methods: ['post']
  }
});
```

`methods` is a per-request option because some requests are exceptions to the global rule.

## cache.cachePredicate

* Type: `CachePredicate<R, D>`
* Default:
  `{ statusCheck: (status) => [200, 203, 300, 301, 302, 404, 405, 410, 414, 501].includes(status) }`
  *(the cacheable status codes defined by RFC 7231)*

An object, or a function that receives the response, that decides whether a response can be
cached. The object form accepts:

* `statusCheck`: caches only if this function returns `true` for the response status.
* `containsHeaders`: caches only if every function returns `true` for its response header.
  Header names are lowercase in axios; a missing header calls the function with `undefined`.
* `responseMatch`: caches only if this function returns `true` for the response.
* `ignoreUrls`: never caches requests whose URL matches any of these patterns.
* `allowUrls`: only caches requests whose URL matches at least one of these patterns.

`ignoreUrls` and `allowUrls` are tested against `config.url` only (`baseURL` is not
included) before the request is sent. A string pattern matches when the URL contains it. If a
URL matches both lists, `ignoreUrls` takes precedence. When the request has no `url` (only a
`baseURL`), both lists are ignored.

```ts
axios.get<{ auth: { status: string } }>('url', {
  cache: {
    cachePredicate: {
      // Only cache if the response comes with a "good" status code
      statusCheck: (status) => true, // some calculation

      // Tests against any header present in the response
      containsHeaders: {
        'x-custom-header-3': (value) => true // some calculation
      },

      // Checks the response body
      responseMatch: ({ data }) => {
        // Only caches authenticated responses
        return data.auth.status === 'authenticated';
      },

      // Never caches requests whose URL starts with "/api"
      ignoreUrls: [/^\/api/],

      // Only caches requests whose URL includes "weekly"
      allowUrls: ['weekly']
    }
  }
});
```

## cache.update

* Type: `CacheUpdater<R, D>`
* Default: `{}`

When this request succeeds with a network response (not a cache hit), it updates or deletes
the cache entries of other requests. It is a `Record` keyed by request ID, where each value
is either:

* `'delete'`: removes that cache entry.
* a function that receives the current cache entry and this request's response, and returns
  the new cache entry, `'delete'` to remove it or `'ignore'` to leave it unchanged. Entries
  that are still loading are skipped.

You can also pass a single function instead of a record. It receives the response and can do
anything, but this is not recommended: it is simpler to consume the response normally and
write your own code after it.

```ts
// Request IDs used by other requests
let profileInfoId: string;
let userInfoId: string;

axios.post<{ auth: { user: User } }>(
  'login',
  { username, password },
  {
    cache: {
      update: {
        // The user is now authenticated, so the profile info must be fetched again
        [profileInfoId]: 'delete',

        // Writes the logged-in user into the cached user info response
        [userInfoId]: (cachedValue, response) => {
          if (cachedValue.state !== 'cached') {
            // Only update entries that are cached
            return 'ignore';
          }

          cachedValue.data.data = response.data.auth.user;

          // This value is stored and returned by later requests to this cache entry
          return cachedValue;
        }
      }
    }
  }
);
```

See [Invalidating cache](../guide/invalidating-cache.md) for more examples.

## cache.etag

* Type: `string | boolean`
* Default: `true`

Configures [`ETag`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) and
[`If-None-Match`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match)
handling for cache revalidation.

* `true`: stores the response's `ETag` and sends it in `If-None-Match` when revalidating a
  stale entry.
* `string`: sends this static value in `If-None-Match` instead.
* `false`: disables `ETag` handling.

A cached entry with an `ETag` becomes stale instead of being removed when its TTL expires. A
`304 Not Modified` response reuses the stored data and refreshes the entry.

## cache.modifiedSince

* Type: `Date | boolean`
* Default: `false` *(`true` when `etag: false` is passed to `setupCache`)*

Sends the
[`If-Modified-Since`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since)
header when revalidating a stale entry. Use a `Date` to send a custom static value, or `true`
to derive it from the cached response.

If the request was never cached before, the header is not set.

With `true`, the value of the response's
[`Last-Modified`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified)
header is used when present; otherwise, the cache creation timestamp is sent.

## cache.staleIfError

* Type: `number | boolean | StaleIfErrorPredicate<R, D>`
* Default: `true`

When a request that revalidates a stale cache entry fails, the library returns the stale data
instead of the error. This covers network errors, rejected status codes and similar failures. The
returned response has `cached: true` and `stale: true`.

* `true`: returns the stale data. If the error response has a
  `Cache-Control: stale-if-error=N` header, only within `N` seconds after the entry was
  cached.
* `number`: the maximum age, in milliseconds, of the stale data that can be returned.
* `false`: disables this behavior.
* a function: receives the error response (if any), the current cache entry (in the
  `loading` state, holding the stale data) and the error, and returns one of the values
  above.

::: warning

A response is treated as an error when its status is rejected by axios's
[`validateStatus`](https://axios-http.com/docs/handling_errors). In that case, if
`staleIfError` resolves to `true` (or to a number that still covers the entry), the stale
cache is returned instead of the server's "invalid" response.

To keep the server's response, use a predicate that only allows stale data when there is no
response:

:::

```ts
const customPredicate = (response, cache, error) => {
  // Only returns stale data when there is no response (network errors)
  return !response;

  // Requests with a response still follow axios's default behavior
  // and throw an error, but the error keeps the response
};

axios.get('/api/data', {
  cache: {
    staleIfError: customPredicate
  }
});
```

## cache.override

* Type: `boolean`
* Default: `false`

Bypasses the current cache and always makes a new HTTP request. The current cache entry is
not deleted; it is replaced when the response arrives.

Unlike `cache: { enabled: false }`, this does not disable the cache. It only skips the
checks made before the request, so all post-request options still work as expected. An
overriding request does not wait for concurrent requests to the same key and does not send
`If-None-Match` or `If-Modified-Since` headers.

## cache.vary

* Type: `string[] | boolean`
* Default: `true`

Configures how the HTTP `Vary` header is handled.

* `true`: handles `Vary` automatically (default, recommended).
* `false`: disables `Vary` checks (**WARNING: can cause cache poisoning**).
* `string[]`: uses these request headers instead of the server's `Vary` list. Their values
  are always part of the cache key, whether set globally or per request, even when the
  response has no `Vary` header.

When the server responds with a `Vary` header, the library stores the values of the listed
request headers with the cache entry. A later request with different values for those
headers gets its own cache entry:

```ts
// Request with an Authorization header
await axios.get('/api/users', {
  headers: { authorization: 'Bearer token-A' }
});
// Server responds with: Vary: Authorization
// The entry stores { authorization: 'Bearer token-A' } alongside the response

// Different authorization = different cache entry
await axios.get('/api/users', {
  headers: { authorization: 'Bearer token-B' }
});
// The Authorization value does not match, so this request switches to a
// Vary-aware ID that includes its header value and makes its own request
```

A response with `Vary: *` is stored as stale right away, so every later request revalidates
it with the server.

## cache.hydrate

* Type:
  `undefined | ((cache: CachedStorageValue | StaleStorageValue | LoadingStorageValue) => void | Promise<void>)`
* Default: `undefined`

Called when a network request is needed but an older, probably expired, cache entry exists.
It receives the current cache entry **BEFORE** the network request starts, so you can update
your UI with the old data in the meantime. The request waits for the returned promise, so
keep this function fast.

This avoids *flickering* the UI, especially on slow networks or with a persisted cache in
mobile apps.

::: warning

If the request is answered from a valid cache, no network request is made and `hydrate`
**IS NOT CALLED**, as the axios promise resolves right away. It is also not called for
entries that must be revalidated (`Cache-Control: must-revalidate`).

:::

```ts {7,13}
// Example of a function that renders data on the screen
function render(data) {}

const response = await axios.get(/* [!code focus:10] */ 'url', {
  // Called right away if axios needs to make a network request
  cache: {
    hydrate: (cache) => render(cache.data.data)
  }
});

// After the network request ends, re-render the UI
// with the fresh data
render(response.data);
```
