Migration Guide: 1.x → 2.0
v2.0 makes the core-vs-plugin boundary explicit. Most changes are additive — the primary work is moving configuration from core options into the relevant plugins.
Upgrade checklist
- Upgrade
axiosto>= 1.7.4 - Move sanitization config into
DebugSanitizationPlugin - Install
MetricsPluginif you rely on live metric counters oronMetricsUpdated - Prefer documented imports from
axios-retryer/plugins/<Plugin>(or the barrel for convenience) - Update TypeScript listeners for plugin events: widen the manager type via
use()orcreateRetryer<PluginEvents>() - Re-check
maxRefreshAttemptsif you useTokenRefreshPlugin - Move manual replay to
ManualRetryPlugin(root replay surface removed) - Build the browser bundle locally if you need it (
pnpm build:browser) - Replace
hooks: { ... }oncreateRetryer()withretryer.on(...)after construction - Remove
RequestDependencyPlugin; useblockingPriorityThreshold/cancelPendingOnDependencyFailureon the core manager
Sanitization → DebugSanitizationPlugin
Before (1.x)
const retryer = createRetryer({
debug: true,
enableSanitization: true,
sanitizeOptions: {
sensitiveHeaders: ['X-API-Key'],
},
}); After (2.0)
import { createRetryer } from 'axios-retryer';
import { createDebugSanitizationPlugin } from 'axios-retryer/plugins/DebugSanitizationPlugin';
const retryer = createRetryer({ debug: true }).use(
createDebugSanitizationPlugin({
sanitizeOptions: { sensitiveHeaders: ['X-API-Key'] },
}),
); Metrics → MetricsPlugin
The core still exposes getMetrics(), but counters stay at zero without the plugin. onRetryProcessFinished remains a core lifecycle event but no longer carries a metrics payload — use onMetricsUpdated with MetricsPlugin when you need snapshots.
Before (1.x)
const retryer = createRetryer();
retryer.on('onMetricsUpdated', (metrics) => {
console.log(metrics.successfulRetries);
}); After (2.0)
import { createRetryer } from 'axios-retryer';
import { createMetricsPlugin } from 'axios-retryer/plugins/MetricsPlugin';
const retryer = createRetryer().use(createMetricsPlugin());
retryer.on('onMetricsUpdated', (metrics) => {
console.log(metrics.successfulRetries);
}); Manual replay → ManualRetryPlugin
Before (1.x)
const retryer = createRetryer({
mode: RETRY_MODES.MANUAL,
maxRequestsToStore: 100,
hooks: {
beforeManualRetry: (config) => config,
},
});
const responses = await retryer.retryFailedRequests(); After (2.0)
import { createRetryer, RETRY_MODES } from 'axios-retryer';
import { createManualRetryPlugin } from 'axios-retryer/plugins/ManualRetryPlugin';
const manualRetry = createManualRetryPlugin({
maxRequestsToStore: 100,
beforeRetry: (config) => config,
});
const retryer = createRetryer({ mode: RETRY_MODES.MANUAL }).use(manualRetry);
const responses = await manualRetry.retryFailedRequests(); maxRefreshAttempts off-by-one fix
In 1.x, maxRefreshAttempts: 3 produced 4 total attempts due to a loop bug. In 2.0 it means exactly 3.
// If you relied on the old behaviour (4 attempts):
createTokenRefreshPlugin(refreshFn, { maxRefreshAttempts: 4 }); // Now truly 4 attempts
// If your value was already tuned:
createTokenRefreshPlugin(refreshFn, { maxRefreshAttempts: 3 }); // Now truly 3 attempts (was effectively 4)
// → decrease by 1 if you want to keep the same total attempt count Constructor hooks removed
In 2.0, RetryManagerOptions.hooks is gone. Register core and plugin listeners with retryer.on(...) on the instance (after use() when the event is plugin-specific).
Plugin event typing
Plugin-specific events are typed on the manager returned from use(). You can pass a generic to createRetryer<Events>() for early core listeners, but plugin events are still registered with .on() after attaching the plugin.
Before (1.x)
createRetryer({
hooks: {
onTokenRefreshed: (token) => console.log(token),
},
}); After (2.0) — use() then on()
import { createRetryer } from 'axios-retryer';
import { createTokenRefreshPlugin } from 'axios-retryer/plugins/TokenRefreshPlugin';
const withRefresh = createRetryer().use(
createTokenRefreshPlugin(async (axiosInstance) => {
const { data } = await axiosInstance.post('/auth/refresh');
return { token: data.accessToken };
}),
);
withRefresh.on('onTokenRefreshed', (token) => console.log(token)); After (2.0) — listener after use() only
import { createRetryer } from 'axios-retryer';
import { createTokenRefreshPlugin } from 'axios-retryer/plugins/TokenRefreshPlugin';
// Or assign the widened manager from an existing instance:
const retryer = createRetryer();
const withRefresh = retryer.use(
createTokenRefreshPlugin(async (axiosInstance) => {
const { data } = await axiosInstance.post('/auth/refresh');
return { token: data.accessToken };
}),
);
withRefresh.on('onTokenRefreshed', (token) => console.log(token)); Import guidance
Per-plugin subpaths are preferred for tree-shaking; the barrel is still supported.
import { createTokenRefreshPlugin } from 'axios-retryer/plugins/TokenRefreshPlugin';
import { createMetricsPlugin } from 'axios-retryer/plugins/MetricsPlugin'; import { createTokenRefreshPlugin, createMetricsPlugin } from 'axios-retryer/plugins'; Avoid deep imports from src/ or undocumented paths.
Legacy plugin.hooks removed
Plugins can no longer declare hooks via a plugin.hooks property. Use retryer.on() inside initialize():
// Inside a custom plugin's initialize():
plugin.initialize = (retryer) => {
retryer.on('beforeRetry', (config) => {
// Custom logic
});
}; Caching invalidation
CachingPlugin.invalidateCache() uses explicit semantics: a bare string matches the exact cache key (not a substring). Prefer exact / prefix object matchers or a RegExp (see example below).
const cachePlugin = createCachePlugin();
const userKey = cachePlugin.buildCacheKey({ method: 'get', url: '/users/1' });
cachePlugin.invalidateCache({ exact: userKey });
cachePlugin.invalidateCache({ prefix: 'GET|/users/' }); Typical end state
Most 1.x apps end up close to this shape:
import { createRetryer, RETRY_MODES } from 'axios-retryer';
import {
createDebugSanitizationPlugin,
createMetricsPlugin,
createTokenRefreshPlugin,
} from 'axios-retryer/plugins';
const retryer = createRetryer({
mode: RETRY_MODES.AUTOMATIC,
retries: 3,
debug: true,
})
.use(createMetricsPlugin())
.use(
createDebugSanitizationPlugin({
sanitizeOptions: {
sensitiveHeaders: ['Authorization'],
},
}),
)
.use(
createTokenRefreshPlugin(async (axiosInstance) => {
const { data } = await axiosInstance.post('/auth/refresh');
return { token: data.accessToken };
}),
); 2.1.6 behavior notes
Applies when upgrading to 2.1.6 from an earlier 2.0.x or 2.1.x release (for example 2.0.5 → 2.1.6). Extends the checklist above — not a second migration from 1.x.
- Queue teardown error class: On
RetryManagerdestroy, waiters still in the queue getQueueDestroyedError. Earlier2.0.x/2.1.xcould surfaceQueueClearedErroron that path — handleQueueDestroyedErroror both if you branch oninstanceof. - Circuit breaker default scope without host: Default
host+urlno longer collapses relative URLs into one__global__bucket; keys use the normalized path (orunknown). Use a customscopecallback if you need the old aggregation. - TokenRefreshPlugin
customErrorDetector: If it throws on a success response, the error is logged and the response is returned (no body-triggered refresh). - Internal errors: Automatic retries are skipped for internal queue/cancel outcomes (
QUEUE_DESTROYED,QUEUE_CLEARED,QUEUE_FULL,REQUEST_CANCELED,EREQUEST_ABORTED). Retry-After: Read in a header-tolerant, case-insensitive way — correctness fix, no API change.- Manual replay: Failed manual replays are not re-stored (avoids recursive store loops).
Browser bundle
The UMD browser bundle is no longer published as a built artifact. Generate it locally:
pnpm build:browser
# Output: dist/browser/ Notes
1.5.4was prepared but not published; its planned fixes ship in2.0.0.- Dependency-style gating (block lower-priority work until blocking requests finish) is core:
blockingPriorityThresholdandcancelPendingOnDependencyFailure. See the Concurrency guide. - The
axios-retryer/pluginsbarrel is deprecated for tree-shaking; it may be removed in v3.
axios-retryer/plugins barrel — v3 roadmap
The barrel pulls every plugin into one chunk (~52 KB raw / ~14 KB gzip). Subpath imports let bundlers drop unused plugins.
Migration: replace
import { CachingPlugin, TokenRefreshPlugin } from 'axios-retryer/plugins'; with focused imports:
import { CachingPlugin } from 'axios-retryer/plugins/CachingPlugin';
import { TokenRefreshPlugin } from 'axios-retryer/plugins/TokenRefreshPlugin';