Documentation Index
Fetch the complete documentation index at: https://mintlify.com/unjs/ofetch/llms.txt
Use this file to discover all available pages before exploring further.
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'
})