Overview
You can augment the FetchOptions interface to add custom properties with full TypeScript support. This is useful for adding domain-specific options that work throughout your application.
Basic Usage
Place this in any .ts or .d.ts file in your project:
// types.d.ts or custom.d.ts
declare module 'ofetch' {
interface FetchOptions {
// Add your custom properties
requiresAuth?: boolean
}
}
export {}
Ensure the file is included in your tsconfig.json “files” or covered by “include” patterns.
Using Augmented Types
Once augmented, your custom properties are available everywhere:
import { ofetch } from 'ofetch'
// TypeScript knows about requiresAuth
await ofetch('/api/protected', {
requiresAuth: true // ✅ Type-safe
})
Example from README
From README.md:356-384:
// Place this in any `.ts` or `.d.ts` file.
// Ensure it's included in the project's tsconfig.json "files".
declare module "ofetch" {
interface FetchOptions {
// Custom properties
requiresAuth?: boolean;
}
}
export {};
This lets you pass and use those properties with full type safety throughout ofetch calls:
const myFetch = ofetch.create({
onRequest(context) {
// ^? { ..., options: {..., requiresAuth?: boolean }}
console.log(context.options.requiresAuth);
},
});
myFetch("/foo", { requiresAuth: true });
Common Use Cases
Authentication Requirements
// types.d.ts
declare module 'ofetch' {
interface FetchOptions {
requiresAuth?: boolean
authType?: 'bearer' | 'basic' | 'apiKey'
}
}
export {}
// api-client.ts
import { ofetch } from 'ofetch'
const api = ofetch.create({
baseURL: '/api',
async onRequest({ options }) {
if (options.requiresAuth) {
const token = await getAuthToken()
if (options.authType === 'bearer') {
options.headers.set('Authorization', `Bearer ${token}`)
} else if (options.authType === 'apiKey') {
options.headers.set('X-API-Key', token)
}
}
}
})
// Usage with type safety
await api('/protected/resource', {
requiresAuth: true,
authType: 'bearer'
})
Cache Control
// types.d.ts
declare module 'ofetch' {
interface FetchOptions {
cacheStrategy?: 'no-cache' | 'force-cache' | 'revalidate'
cacheTTL?: number
}
}
export {}
const api = ofetch.create({
async onRequest({ options }) {
if (options.cacheStrategy === 'no-cache') {
options.headers.set('Cache-Control', 'no-cache')
} else if (options.cacheStrategy === 'force-cache') {
options.headers.set('Cache-Control', `max-age=${options.cacheTTL || 3600}`)
}
}
})
await api('/api/data', {
cacheStrategy: 'force-cache',
cacheTTL: 7200
})
Rate Limiting
// types.d.ts
declare module 'ofetch' {
interface FetchOptions {
rateLimit?: {
maxRequests: number
perSeconds: number
}
}
}
export {}
const rateLimiters = new Map<string, RateLimiter>()
const api = ofetch.create({
async onRequest({ request, options }) {
if (options.rateLimit) {
const limiter = getRateLimiter(
request.toString(),
options.rateLimit
)
await limiter.acquire()
}
}
})
await api('/api/endpoint', {
rateLimit: { maxRequests: 10, perSeconds: 60 }
})
API Versioning
// types.d.ts
declare module 'ofetch' {
interface FetchOptions {
apiVersion?: 'v1' | 'v2' | 'v3'
}
}
export {}
const api = ofetch.create({
baseURL: '/api',
async onRequest({ options }) {
const version = options.apiVersion || 'v2'
options.headers.set('X-API-Version', version)
}
})
await api('/users', { apiVersion: 'v3' })
Request Tracking
// types.d.ts
declare module 'ofetch' {
interface FetchOptions {
trackingId?: string
analytics?: boolean
}
}
export {}
const api = ofetch.create({
async onRequest({ options }) {
if (options.analytics !== false) {
const trackingId = options.trackingId || generateId()
options.headers.set('X-Request-ID', trackingId)
trackRequest(trackingId)
}
},
async onResponse({ options, response }) {
if (options.analytics !== false && options.trackingId) {
trackResponse(options.trackingId, response.status)
}
}
})
await api('/api/action', {
trackingId: 'user-action-123',
analytics: true
})
Multiple Augmentations
You can augment the interface multiple times:
// auth.d.ts
declare module 'ofetch' {
interface FetchOptions {
requiresAuth?: boolean
}
}
export {}
// cache.d.ts
declare module 'ofetch' {
interface FetchOptions {
cacheKey?: string
}
}
export {}
Both properties will be available:
await ofetch('/api/data', {
requiresAuth: true,
cacheKey: 'my-data'
})
Context Types
From src/types.ts:94-99:
export interface FetchContext<T = any, R extends ResponseType = ResponseType> {
request: FetchRequest;
options: ResolvedFetchOptions<R>;
response?: FetchResponse<T>;
error?: Error;
}
Your augmented properties are available in interceptors through context.options:
declare module 'ofetch' {
interface FetchOptions {
customProperty?: string
}
}
const api = ofetch.create({
async onRequest(context) {
// TypeScript knows about customProperty
console.log(context.options.customProperty)
}
})
Best Practices
1. Use Optional Properties
// ✅ Good - optional properties
declare module 'ofetch' {
interface FetchOptions {
myOption?: string
}
}
// ❌ Avoid - required properties (breaks existing code)
declare module 'ofetch' {
interface FetchOptions {
myOption: string
}
}
2. Document Your Properties
declare module 'ofetch' {
interface FetchOptions {
/**
* Requires authentication for this request.
* @default false
*/
requiresAuth?: boolean
/**
* Custom timeout in milliseconds for this specific request.
* Overrides the global timeout setting.
*/
customTimeout?: number
}
}
export {}
3. Keep Type Files Organized
// types/ofetch.d.ts
import 'ofetch'
declare module 'ofetch' {
interface FetchOptions {
// Group related properties
// Authentication
requiresAuth?: boolean
authType?: 'bearer' | 'basic'
// Caching
cacheStrategy?: 'no-cache' | 'force-cache'
cacheTTL?: number
// Tracking
trackingId?: string
analytics?: boolean
}
}
export {}
4. Export Empty Object
// ✅ Required - makes this a module
declare module 'ofetch' {
interface FetchOptions {
myOption?: string
}
}
export {} // Don't forget this!
tsconfig.json Setup
Make sure your type declaration files are included:
{
"compilerOptions": {
"types": ["ofetch"]
},
"include": [
"src/**/*",
"types/**/*" // Include your .d.ts files
]
}
Complete Example
// types/ofetch.d.ts
declare module 'ofetch' {
interface FetchOptions {
/** Require authentication for this request */
requiresAuth?: boolean
/** API version to use */
apiVersion?: 'v1' | 'v2'
/** Custom request ID for tracking */
requestId?: string
}
}
export {}
// api/client.ts
import { ofetch } from 'ofetch'
export const api = ofetch.create({
baseURL: '/api',
async onRequest({ options }) {
// Add version header
if (options.apiVersion) {
options.headers.set('X-API-Version', options.apiVersion)
}
// Add auth token
if (options.requiresAuth) {
const token = await getAuthToken()
options.headers.set('Authorization', `Bearer ${token}`)
}
// Add request ID
const requestId = options.requestId || crypto.randomUUID()
options.headers.set('X-Request-ID', requestId)
}
})
// Usage with full type safety
const user = await api('/users/me', {
requiresAuth: true,
apiVersion: 'v2',
requestId: 'custom-id-123'
})