Skip to content

Request Interception

Request interception allows you to modify outgoing requests before they're sent to the API server.

Overview

Use the onRequest callback to intercept and modify:

  • Headers: Add, modify, or remove headers
  • Query Parameters: Add or modify query params
  • Body: Modify request body (POST/PUT/PATCH)
  • URL: Inspect (but not modify) the URL
typescript
useFetchGetPets({}, {
  onRequest: ({ url, method, headers, body, query }) => {
    // Return modifications to apply them
    return {
      headers: { ...headers, 'X-Custom': 'value' },
      query: { ...query, timestamp: Date.now() }
    }
  }
})

Modifying Headers

Add Headers

typescript
useFetchGetPets({}, {
  onRequest: ({ headers }) => {
    return {
      headers: {
        ...headers,
        'X-Custom-Header': 'value',
        'X-Request-ID': crypto.randomUUID(),
        'X-Client-Version': '1.0.0'
      }
    }
  }
})

Multiple Headers

typescript
useFetchGetPets({}, {
  onRequest: ({ headers }) => {
    return {
      headers: {
        ...headers,
        'X-Platform': 'web',
        'X-Language': 'en',
        'X-Timezone': Intl.DateTimeFormat().resolvedOptions().timeZone,
        'X-Request-Time': Date.now().toString()
      }
    }
  }
})

Conditional Headers

typescript
useFetchGetPets({}, {
  onRequest: ({ headers }) => {
    const isDev = process.env.NODE_ENV === 'development'
    const locale = useI18n().locale.value
    
    return {
      headers: {
        ...headers,
        ...(isDev && { 'X-Debug-Mode': 'true' }),
        'Accept-Language': locale
      }
    }
  }
})

Remove Headers

typescript
useFetchGetPets({}, {
  onRequest: ({ headers }) => {
    const { ['X-Unwanted-Header']: removed, ...rest } = headers || {}
    return {
      headers: rest
    }
  }
})

Modifying Query Parameters

Add Query Params

typescript
useFetchGetPets({}, {
  onRequest: ({ query }) => {
    return {
      query: {
        ...query,
        timestamp: Date.now(),
        version: 'v2',
        format: 'json'
      }
    }
  }
})

Modify Existing Params

typescript
useFetchGetPets(
  { status: 'available', limit: 10 },
  {
    onRequest: ({ query }) => {
      return {
        query: {
          ...query,
          limit: 100,
          includeArchived: true
        }
      }
    }
  }
)

Array to String

typescript
useFetchGetPets(
  { tags: ['dog', 'cat'] },
  {
    onRequest: ({ query }) => {
      return {
        query: {
          ...query,
          tags: Array.isArray(query?.tags) ? query.tags.join(',') : query?.tags
        }
      }
    }
  }
)

Modifying Body

Add Fields to Body

typescript
useFetchCreatePet(
  { body: { name: 'Fluffy', status: 'available' } },
  {
    onRequest: ({ body }) => {
      if (body) {
        return {
          body: {
            ...body,
            createdAt: new Date().toISOString(),
            clientVersion: '1.0.0',
            source: 'web'
          }
        }
      }
    }
  }
)

Transform Body

typescript
useFetchCreatePet(
  { body: { name: 'fluffy' } },
  {
    onRequest: ({ body }) => {
      if (body && body.name) {
        return {
          body: {
            ...body,
            name: body.name.charAt(0).toUpperCase() + body.name.slice(1)
          }
        }
      }
    }
  }
)

Validate Before Send

typescript
useFetchCreatePet(
  { body: formData.value },
  {
    onRequest: ({ body }) => {
      if (body) {
        // Validate
        if (!body.name || body.name.trim() === '') {
          throw new Error('Name is required')
        }
        
        // Return sanitized body
        return {
          body: {
            ...body,
            name: body.name.trim()
          }
        }
      }
    }
  }
)

Common Use Cases

Add Authentication

typescript
useFetchGetPets({}, {
  onRequest: ({ headers }) => {
    const token = useCookie('auth-token').value
    if (token) {
      return {
        headers: {
          ...headers,
          'Authorization': `Bearer ${token}`
        }
      }
    }
  }
})

Request Tracing

typescript
useFetchGetPets({}, {
  onRequest: ({ headers, url, method }) => {
    const traceId = crypto.randomUUID()
    
    console.log(`[Trace ${traceId}] ${method} ${url}`)
    
    return {
      headers: {
        ...headers,
        'X-Trace-ID': traceId,
        'X-Span-ID': crypto.randomUUID()
      }
    }
  }
})

Prevent Caching

typescript
useFetchGetPets({}, {
  onRequest: ({ query }) => {
    return {
      query: {
        ...query,
        _t: Date.now()
      }
    }
  }
})

Add Metadata

typescript
useFetchCreateOrder(
  { body: orderData.value },
  {
    onRequest: ({ body, headers }) => {
      return {
        body: body ? {
          ...body,
          metadata: {
            clientVersion: '1.0.0',
            platform: 'web',
            userAgent: navigator.userAgent,
            timestamp: Date.now()
          }
        } : body,
        headers: {
          ...headers,
          'X-Correlation-ID': crypto.randomUUID()
        }
      }
    }
  }
)

API Versioning

typescript
useFetchGetPets({}, {
  onRequest: ({ headers, query }) => {
    return {
      headers: {
        ...headers,
        'X-API-Version': '2.0'
      },
      query: {
        ...query,
        api_version: '2.0'
      }
    }
  }
})

Locale/i18n

typescript
useFetchGetPets({}, {
  onRequest: ({ headers, query }) => {
    const { locale } = useI18n()
    
    return {
      headers: {
        ...headers,
        'Accept-Language': locale.value
      },
      query: {
        ...query,
        locale: locale.value
      }
    }
  }
})

Global Interception

Use global callbacks for app-wide interception:

typescript
// plugins/api-interceptor.ts
export default defineNuxtPlugin(() => {
  useGlobalCallbacks({
    onRequest: ({ headers, query }) => {
      const token = useCookie('auth-token').value
      
      return {
        headers: {
          ...headers,
          ...(token && { 'Authorization': `Bearer ${token}` }),
          'X-Client-Version': '1.0.0'
        },
        query: {
          ...query,
          _t: Date.now()
        }
      }
    }
  })
})

Advanced Patterns

Retry with Backoff

vue
<script setup lang="ts">
const retryCount = ref(0)
const maxRetries = 3

const { refresh } = useFetchGetPets({}, {
  onRequest: ({ headers }) => {
    if (retryCount.value > 0) {
      return {
        headers: {
          ...headers,
          'X-Retry-Count': retryCount.value.toString()
        }
      }
    }
  },
  onError: async (error) => {
    if (error.status >= 500 && retryCount.value < maxRetries) {
      retryCount.value++
      const delay = Math.pow(2, retryCount.value) * 1000
      await new Promise(resolve => setTimeout(resolve, delay))
      refresh()
    }
  }
})
</script>

Feature Flags

typescript
const featureFlags = useFeatureFlags()

useFetchGetPets({}, {
  onRequest: ({ headers }) => {
    return {
      headers: {
        ...headers,
        'X-Features': Object.keys(featureFlags)
          .filter(key => featureFlags[key])
          .join(',')
      }
    }
  }
})

A/B Testing

typescript
useFetchGetPets({}, {
  onRequest: ({ headers }) => {
    const variant = useCookie('ab-test-variant').value || 'A'
    return {
      headers: {
        ...headers,
        'X-AB-Variant': variant
      }
    }
  }
})

TypeScript Support

Full type safety:

typescript
useFetchGetPets({}, {
  onRequest: ({ url, method, headers, body, query }) => {
    url         // string (readonly)
    method      // string (readonly)
    headers     // Record<string, string> | undefined
    body        // any | undefined
    query       // Record<string, any> | undefined
    
    // Return modifications:
    return {
      headers: { ...headers, 'X-Custom': 'value' },
      query: { ...query, timestamp: Date.now() },
      body: body ? { ...body, extra: 'data' } : undefined
    }
  }
})

Best Practices

✅ Do

typescript
// ✅ Return modified headers
onRequest: ({ headers }) => {
  return {
    headers: { ...headers, 'X-Custom': 'value' }
  }
}

// ✅ Return modified query
onRequest: ({ query }) => {
  return {
    query: { ...query, timestamp: Date.now() }
  }
}

// ✅ Return modified body
onRequest: ({ body }) => {
  if (body) {
    return {
      body: { ...body, processed: true }
    }
  }
}

// ✅ Use for auth
onRequest: ({ headers }) => {
  return {
    headers: { ...headers, 'Authorization': `Bearer ${token}` }
  }
}

❌ Don't

typescript
// ❌ Don't modify directly (won't work!)
onRequest: ({ headers }) => {
  headers['X-Custom'] = 'value' // ❌ Direct mutation doesn't work!
}

// ❌ Don't try to modify url/method
onRequest: () => {
  return {
    url: '/different-url',  // ❌ url/method cannot be modified!
    method: 'POST'
  }
}

// ❌ Don't make API calls
onRequest: async () => {
  await $fetch('/other-endpoint')  // ❌ Race conditions!
}

// ❌ Don't do heavy processing
onRequest: ({ body }) => {
  // Don't: complex algorithms
  // Don't: large data processing
}

Next Steps

Released under the Apache-2.0 License.