Skip to Content
✨ New: Auth Plugin. JWT, OAuth2, API Key, and more authentication strategies out of the box. Read more... 🎉
Plugins NewPlugins

Plugins

Plugins extend endpoint-fetcher with reusable cross-cutting functionality.

Using Plugins

Install an official plugin and add it to the plugins array in your client config:

import { createApiClient, get } from 'endpoint-fetcher'; import { cache } from '@endpoint-fetcher/cache'; import { retryPlugin } from '@endpoint-fetcher/retry'; const api = createApiClient({ getUser: get<{ id: string }, User>((input) => `/users/${input.id}`), }, { baseUrl: 'https://api.example.com', plugins: [ retryPlugin({ maxRetries: 3, strategy: 'exponential' }), cache({ ttl: 300 }), ], });

Plugins run in the order they are listed. Plugin hooks execute before global/group/endpoint hooks.

Creating a Plugin

Use createPlugin(name, factory):

import { createPlugin } from 'endpoint-fetcher'; export const loggingPlugin = createPlugin('logging', () => ({ hooks: { beforeRequest: async (url, init) => { console.log(`→ ${init.method} ${url}`); return { url, init }; }, afterResponse: async (response, url, init) => { console.log(`← ${response.status} ${init.method} ${url}`); return response; }, }, })); // Usage plugins: [loggingPlugin()]

Accept a config object in the factory:

export const authPlugin = createPlugin('auth', (config: { token: string }) => ({ hooks: { beforeRequest: async (url, init) => ({ url, init: { ...init, headers: { ...init.headers, Authorization: `Bearer ${config.token}` } }, }), }, })); // Usage plugins: [authPlugin({ token: 'my-token' })]

Plugin Structure

A plugin factory returns an object with any combination of:

PropertyDescription
hooksbeforeRequest, afterResponse, onError — same interface as client hooks
handlerWrapperWraps each endpoint’s handler function (useful for retry, caching)
methodsFunctions exposed on client.plugins.<pluginName>

handlerWrapper

Wraps the endpoint handler, giving full control over execution (retries, short-circuit caching, etc.):

export const retryPlugin = createPlugin('retry', (config: { maxRetries: number }) => ({ handlerWrapper: (originalHandler) => async (input, context) => { for (let attempt = 0; attempt <= config.maxRetries; attempt++) { try { return await originalHandler(input, context); } catch (err) { const error = err as { status?: number }; if (error.status && error.status < 500) throw err; // don't retry 4xx if (attempt === config.maxRetries) throw err; await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt))); } } }, }));

methods

Expose functions on client.plugins.<pluginName> (fully typed):

export const metricsPlugin = createPlugin('metrics', () => { let requestCount = 0; let errorCount = 0; return { hooks: { beforeRequest: async (url, init) => { requestCount++; return { url, init }; }, onError: async () => { errorCount++; }, }, methods: { getStats: () => ({ requestCount, errorCount }), reset: () => { requestCount = 0; errorCount = 0; }, }, }; }); // Access const stats = api.plugins.metrics.getStats(); // fully typed api.plugins.metrics.reset();

When multiple plugins expose methods, each is namespaced under its plugin name:

api.plugins.metrics.getStats(); api.plugins.cache.clear();

Plugin names must be unique. Registering two plugins with the same name throws at client creation time.

Official Plugins

  • Cache Plugin — Response caching with TTL, LRU eviction, and custom storage
  • Retry Plugin — Automatic retries with fixed, linear, or exponential backoff
  • Auth Plugin — JWT, OAuth2, API Key, Basic, Bearer, HMAC, Digest, and Custom authentication strategies