> ## 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.

# Interceptors

> Request and response lifecycle hooks including onRequest, onRequestError, onResponse, and onResponseError

ofetch provides powerful lifecycle hooks (interceptors) that allow you to intercept and modify requests and responses at different stages.

## Available Hooks

There are four lifecycle hooks available:

* **`onRequest`** - Called before the request is sent
* **`onRequestError`** - Called when the request fails (network error, invalid URL, etc.)
* **`onResponse`** - Called after a successful response is received
* **`onResponseError`** - Called when the response status is 4xx or 5xx

## Hook Signatures

All hooks receive a `FetchContext` object with the following properties:

```typescript theme={null}
interface FetchContext<T = any> {
  request: FetchRequest          // URL string or Request object
  options: FetchOptions          // Resolved request options
  response?: FetchResponse<T>    // Response object (in onResponse/onResponseError)
  error?: Error                  // Error object (in onRequestError)
}
```

## onRequest

Called before the request is sent. Use this to modify headers, add authentication, log requests, etc.

<ParamField path="onRequest" type="(context: FetchContext) => void | Promise<void>">
  Hook called before the request is sent. Can be a single function or an array of functions.
</ParamField>

### Adding Authentication

```typescript theme={null}
import { ofetch } from 'ofetch'

const api = ofetch.create({
  baseURL: 'https://api.example.com',
  onRequest: ({ options }) => {
    // Add authorization header
    const token = localStorage.getItem('token')
    if (token) {
      options.headers.set('Authorization', `Bearer ${token}`)
    }
  }
})

await api('/user/profile')
```

### Logging Requests

```typescript theme={null}
import { ofetch } from 'ofetch'

const data = await ofetch('/api/data', {
  onRequest: ({ request, options }) => {
    console.log(`Making ${options.method} request to:`, request)
    console.log('Headers:', options.headers)
    console.log('Body:', options.body)
  }
})
```

### Modifying Request Body

```typescript theme={null}
import { ofetch } from 'ofetch'

const result = await ofetch('/api/users', {
  method: 'POST',
  body: { name: 'John' },
  onRequest: ({ options }) => {
    // Add timestamp to all POST requests
    if (options.method === 'POST' && options.body) {
      options.body = {
        ...options.body,
        timestamp: new Date().toISOString()
      }
    }
  }
})
```

### Multiple Request Hooks

```typescript theme={null}
import { ofetch } from 'ofetch'

const data = await ofetch('/api/data', {
  onRequest: [
    ({ options }) => {
      // First hook: Add auth
      options.headers.set('Authorization', 'Bearer token')
    },
    ({ options }) => {
      // Second hook: Add tracking ID
      options.headers.set('X-Request-ID', crypto.randomUUID())
    },
    ({ request }) => {
      // Third hook: Log
      console.log('Sending request to:', request)
    }
  ]
})
```

## onRequestError

Called when the request fails before receiving a response (network errors, invalid URLs, etc.).

<ParamField path="onRequestError" type="(context: FetchContext & { error: Error }) => void | Promise<void>">
  Hook called when the request fails. The context includes an `error` property with the error details.
</ParamField>

```typescript theme={null}
import { ofetch } from 'ofetch'

try {
  await ofetch('https://invalid-domain-that-does-not-exist.com', {
    onRequestError: ({ request, error }) => {
      console.error(`Request to ${request} failed:`, error.message)
      
      // Log to error tracking service
      errorTracker.log({
        type: 'network_error',
        url: request,
        error: error.message
      })
    }
  })
} catch (error) {
  // Error is still thrown after onRequestError is called
  console.error('Caught error:', error)
}
```

## onResponse

Called after a successful response is received (status \< 400). Use this to transform responses, cache data, or log responses.

<ParamField path="onResponse" type="(context: FetchContext & { response: FetchResponse }) => void | Promise<void>">
  Hook called after receiving a successful response. The context includes a `response` property with the Response object.
</ParamField>

### Logging Responses

```typescript theme={null}
import { ofetch } from 'ofetch'

const data = await ofetch('/api/data', {
  onResponse: ({ response }) => {
    console.log('Response status:', response.status)
    console.log('Response headers:', response.headers)
    console.log('Response data:', response._data)
  }
})
```

### Caching Responses

```typescript theme={null}
import { ofetch } from 'ofetch'

const cache = new Map()

const api = ofetch.create({
  onResponse: ({ request, response }) => {
    // Cache GET requests
    if (response.status === 200) {
      cache.set(request.toString(), response._data)
    }
  }
})

await api('/api/user/123')
```

### Transforming Response Data

```typescript theme={null}
import { ofetch } from 'ofetch'

const data = await ofetch('/api/dates', {
  onResponse: ({ response }) => {
    // Transform ISO date strings to Date objects
    if (response._data?.createdAt) {
      response._data.createdAt = new Date(response._data.createdAt)
    }
    if (response._data?.updatedAt) {
      response._data.updatedAt = new Date(response._data.updatedAt)
    }
  }
})

console.log(data.createdAt instanceof Date) // true
```

### Performance Monitoring

```typescript theme={null}
import { ofetch } from 'ofetch'

const data = await ofetch('/api/data', {
  async onRequest: ({ request }) => {
    // Store start time
    request._startTime = Date.now()
  },
  async onResponse: ({ request, response }) => {
    const duration = Date.now() - request._startTime
    console.log(`Request took ${duration}ms`)
    
    // Send to analytics
    analytics.track('api_request', {
      url: request,
      duration,
      status: response.status
    })
  }
})
```

## onResponseError

Called when the response status is 4xx or 5xx. Use this to handle errors globally, refresh tokens, or log errors.

<ParamField path="onResponseError" type="(context: FetchContext & { response: FetchResponse }) => void | Promise<void>">
  Hook called when the response status indicates an error (400-599). The context includes a `response` property with the Response object.
</ParamField>

### Global Error Handling

```typescript theme={null}
import { ofetch } from 'ofetch'

const api = ofetch.create({
  baseURL: 'https://api.example.com',
  onResponseError: ({ response }) => {
    // Show toast notification for errors
    if (response.status === 401) {
      showToast('Please log in again')
    } else if (response.status === 403) {
      showToast('You do not have permission')
    } else if (response.status >= 500) {
      showToast('Server error, please try again later')
    }
  }
})
```

### Token Refresh on 401

```typescript theme={null}
import { ofetch } from 'ofetch'

const api = ofetch.create({
  baseURL: 'https://api.example.com',
  async onResponseError({ response, options }) {
    if (response.status === 401) {
      // Try to refresh token
      const refreshToken = localStorage.getItem('refreshToken')
      if (refreshToken) {
        try {
          const { token } = await ofetch('/auth/refresh', {
            method: 'POST',
            body: { refreshToken }
          })
          
          // Store new token
          localStorage.setItem('token', token)
          
          // Retry original request with new token
          options.headers.set('Authorization', `Bearer ${token}`)
        } catch (error) {
          // Refresh failed, redirect to login
          window.location.href = '/login'
        }
      } else {
        // No refresh token, redirect to login
        window.location.href = '/login'
      }
    }
  }
})
```

### Error Logging

```typescript theme={null}
import { ofetch } from 'ofetch'

const data = await ofetch('/api/data', {
  onResponseError: ({ request, response }) => {
    // Log error to tracking service
    errorTracker.log({
      type: 'api_error',
      url: request,
      status: response.status,
      statusText: response.statusText,
      data: response._data
    })
  }
})
```

## Hook Execution Order

Hooks are executed in this order:

1. **`onRequest`** hooks (if request is valid)
2. Fetch request is made
3. **`onRequestError`** (if request fails) → throws error
4. **`onResponse`** (if status \< 400)
5. **`onResponseError`** (if status >= 400) → throws error

```typescript theme={null}
import { ofetch } from 'ofetch'

await ofetch('/api/data', {
  onRequest: () => console.log('1. onRequest'),
  onRequestError: () => console.log('2. onRequestError (only if network error)'),
  onResponse: () => console.log('3. onResponse (only if status < 400)'),
  onResponseError: () => console.log('4. onResponseError (only if status >= 400)')
})
```

## Hook Error Handling

If a hook throws an error, the request is aborted and the error is thrown:

```typescript theme={null}
import { ofetch } from 'ofetch'

try {
  await ofetch('/api/data', {
    onRequest: () => {
      throw new Error('Invalid request')
    }
  })
} catch (error) {
  console.error(error.message) // "Invalid request"
}
```

<Warning>
  If you throw an error in a hook, the request will be aborted and the error will be propagated to the caller.
</Warning>

## Complete Example: API Client with Interceptors

```typescript theme={null}
import { $fetch } from 'ofetch'

// Create a configured API client
const api = $fetch.create({
  baseURL: 'https://api.example.com',
  
  // Add auth token to all requests
  onRequest: ({ options }) => {
    const token = localStorage.getItem('token')
    if (token) {
      options.headers.set('Authorization', `Bearer ${token}`)
    }
    
    // Add request ID for tracking
    options.headers.set('X-Request-ID', crypto.randomUUID())
    
    // Log request
    console.log(`[${options.method}] ${options.url}`)
  },
  
  // Handle request errors (network issues)
  onRequestError: ({ error }) => {
    console.error('Network error:', error.message)
    showToast('Network error, please check your connection')
  },
  
  // Process successful responses
  onResponse: ({ response }) => {
    console.log(`Response: ${response.status}`)
    
    // Parse dates
    if (response._data) {
      transformDates(response._data)
    }
  },
  
  // Handle API errors
  async onResponseError({ response, options }) {
    if (response.status === 401) {
      // Try to refresh token
      const newToken = await refreshToken()
      if (newToken) {
        options.headers.set('Authorization', `Bearer ${newToken}`)
      } else {
        window.location.href = '/login'
      }
    } else if (response.status === 403) {
      showToast('You do not have permission to access this resource')
    } else if (response.status >= 500) {
      showToast('Server error, please try again later')
      
      // Log to error tracking
      errorTracker.log({
        url: response.url,
        status: response.status,
        data: response._data
      })
    }
  }
})

// Helper functions
function transformDates(obj: any) {
  for (const key in obj) {
    if (typeof obj[key] === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(obj[key])) {
      obj[key] = new Date(obj[key])
    } else if (typeof obj[key] === 'object') {
      transformDates(obj[key])
    }
  }
}

async function refreshToken() {
  const refreshToken = localStorage.getItem('refreshToken')
  if (!refreshToken) return null
  
  try {
    const { token } = await $fetch('/auth/refresh', {
      method: 'POST',
      body: { refreshToken }
    })
    localStorage.setItem('token', token)
    return token
  } catch {
    return null
  }
}

function showToast(message: string) {
  console.log('Toast:', message)
}

const errorTracker = {
  log: (data: any) => console.error('Error:', data)
}

// Usage
export default api
```
