Docs Plugins

ManualRetryPlugin

Stores terminal failures in MANUAL mode and lets you replay them on demand — on reconnect, after user action, or on app resume. Built-in idempotency safeguards and age limits prevent unsafe replay.

Install

import { createManualRetryPlugin } from 'axios-retryer/plugins/ManualRetryPlugin';

Basic usage

import { createRetryer, RETRY_MODES } from 'axios-retryer';
import { createManualRetryPlugin } from 'axios-retryer/plugins/ManualRetryPlugin';

const manualRetry = createManualRetryPlugin({
  maxRequestsToStore: 50,
  manualRetryMaxAge: 5 * 60_000,   // Drop requests older than 5 min
});

const retryer = createRetryer({ mode: RETRY_MODES.MANUAL }).use(manualRetry);

// Request fails in manual mode → stored
try {
  await retryer.axiosInstance.post('/api/order', orderData);
} catch {
  showToast('Order saved — will retry when online');
}

// Later — replay on reconnect
window.addEventListener('online', async () => {
  const results = await manualRetry.retryFailedRequests();
  console.log(`Replayed ${results.length} request(s)`);
});

All options

createManualRetryPlugin({
  maxRequestsToStore: 100,       // Max stored failures (oldest evicted first; default: 100)
  manualRetryMaxAge: 300_000,    // ms before stored requests are discarded (default: none)
  storeNonIdempotent: false,     // Store POST/PUT/PATCH/DELETE without idempotency key (default: false)
  storeAuthRequests: false,      // Store requests with auth headers (default: false — safer)
  
  // Mutate or redact a request before storing
  prepareRequestForStore: (config) => ({
    ...config,
    data: { redacted: true },   // Strip sensitive payload
  }),

  // Re-attach fresh auth before replay
  rehydrateAuth: (config) => {
    config.headers ??= {};
    config.headers['Authorization'] = `Bearer ${store.getAccessToken()}`;
    return config;
  },

  // Filter which requests to replay
  beforeRetry: (config) => {
    if (config.url?.includes('/non-critical')) return null;  // Skip
    return config;
  },
})

Plugin methods

// Replay all stored failures
const results = await manualRetry.retryFailedRequests();

// Inspect what's stored without replaying
const pending = manualRetry.getStoredRequests();
console.log(`${pending.length} pending requests`);

// Drop stored requests without replaying
manualRetry.clearStoredRequests();

Offline-first SPA pattern

import { createRetryer, RETRY_MODES } from 'axios-retryer';
import { createManualRetryPlugin } from 'axios-retryer/plugins/ManualRetryPlugin';

const manualRetry = createManualRetryPlugin({
  manualRetryMaxAge: 60_000,
  storeNonIdempotent: false,         // Only safe idempotent requests
  rehydrateAuth: (config) => {
    // Always use the latest token when replaying
    config.headers ??= {};
    config.headers['Authorization'] = `Bearer ${auth.getToken()}`;
    return config;
  },
});

const retryer = createRetryer({ mode: RETRY_MODES.MANUAL }).use(manualRetry);

async function submitForm(data: FormData) {
  try {
    await retryer.axiosInstance.put('/api/profile', data, {
      headers: { 'Idempotency-Key': crypto.randomUUID() },
    });
    showSuccess('Saved');
  } catch {
    showWarning('Offline — will sync when reconnected');
  }
}

// Sync on reconnect
navigator.connection?.addEventListener('change', async () => {
  if (navigator.onLine) {
    const synced = await manualRetry.retryFailedRequests();
    showToast(`Synced ${synced.length} pending changes`);
  }
});

Security defaults

DefaultWhat it prevents
storeAuthRequests: falseStoring requests with auth headers; prevents replaying expired credentials
storeNonIdempotent: falseStoring POST/PUT/PATCH/DELETE without idempotency key; prevents duplicate side effects
No rehydrateAuth by defaultReplayed requests carry no auth unless you explicitly provide fresh tokens
MetricsPlugin → All Plugins