N
Nuxt2w ago
t.f.e

Custom useAuthFetch - Not getting types as useFetch

import type { UseFetchOptions } from "nuxt/app";
import { useUserStore } from "@/store/user";

export async function useAPI<T>(
url: string | (() => string),
options?: UseFetchOptions<T>
) {
const userStore = useUserStore();
const accessToken = await userStore.getAccessToken();

const fetchOptions = {
...options,
headers: {
...(options?.headers || {}),
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
}

return useFetch(url, {
...fetchOptions,
$fetch: useNuxtApp().$api as typeof $fetch,
});
}
import type { UseFetchOptions } from "nuxt/app";
import { useUserStore } from "@/store/user";

export async function useAPI<T>(
url: string | (() => string),
options?: UseFetchOptions<T>
) {
const userStore = useUserStore();
const accessToken = await userStore.getAccessToken();

const fetchOptions = {
...options,
headers: {
...(options?.headers || {}),
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
}

return useFetch(url, {
...fetchOptions,
$fetch: useNuxtApp().$api as typeof $fetch,
});
}
Using this composable to inject the access token i do not get the type returned from the api on it. See the images, image one is hovering the "useAPI" custom one. The second is the useFetch. How can i make my custom useAPI have the same typing as useFetch so it changed depending on the url. also the autocomplete does not work on my custom usefetch whereas the useFetch it autocompletes all the API's. (image 3)
No description
No description
No description
9 Replies
kapa.ai
kapa.ai2w ago
Hi @t.f.e. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Your useAPI is async, so it always returns a plain Promise, which strips away the special “augmented promise” type that useFetch (via useAsyncData) normally returns. [Env answer] When you wrap useFetch like this, you must: 1. Not make the wrapper async (or otherwise avoid returning a native Promise), and
2. Preserve the return type of useFetch by forwarding its generics and return type. The Nuxt docs’ “Custom useFetch” recipe (and related fixes) do exactly this: they keep the same signature and return type as useFetch, adding only extra options (like a custom $fetch). [Custom useFetch docs fix; custom $fetch losing event] The Stack Overflow discussion on wrapping useFetch also shows that using async breaks the reactive/typed behavior, and the solution is to move await-logic into onRequest and return useFetch directly. [How to wrap useFetch] Adapting that pattern to your case, you’d do something like:
import type { UseFetchOptions } from 'nuxt/app'
import { defu } from 'defu'
import { useUserStore } from '@/store/user'

export function useAPI<T>(
url: Parameters<typeof useFetch<T>>[0],
options: UseFetchOptions<T> = {}
) {
const userStore = useUserStore()
const nuxtApp = useNuxtApp()

const defaults: UseFetchOptions<T> = {
async onRequest(ctx) {
const accessToken = await userStore.getAccessToken()

ctx.options.headers = {
...(ctx.options.headers || {}),
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
}
},
$fetch: nuxtApp.$api as typeof $fetch,
}

const params = defu(options, defaults)
// Preserve useFetch's return type
return useFetch<T>(url, params)
}
import type { UseFetchOptions } from 'nuxt/app'
import { defu } from 'defu'
import { useUserStore } from '@/store/user'

export function useAPI<T>(
url: Parameters<typeof useFetch<T>>[0],
options: UseFetchOptions<T> = {}
) {
const userStore = useUserStore()
const nuxtApp = useNuxtApp()

const defaults: UseFetchOptions<T> = {
async onRequest(ctx) {
const accessToken = await userStore.getAccessToken()

ctx.options.headers = {
...(ctx.options.headers || {}),
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
}
},
$fetch: nuxtApp.$api as typeof $fetch,
}

const params = defu(options, defaults)
// Preserve useFetch's return type
return useFetch<T>(url, params)
}
Key points: - useAPI is not async and directly returns useFetch, so the return type (with data, error, etc.) and the URL-based inference are preserved. [Env answer; wrap useFetch part 3] - The await for the token is moved into onRequest, which is supported and typed by useFetch/ofetch. [How to wrap useFetch] - $fetch: nuxtApp.$api as typeof $fetch follows the pattern from the “Custom useFetch” docs, including the explicit cast to keep types. [Custom useFetch docs fix] With this, useAPI should have the same typing behavior and autocomplete as useFetch (including inferring the response type and suggesting known API paths). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2w ago
<@260145305754927104> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
t.f.e
t.f.eOP2w ago
@kapa.ai
same as you

export function useAPI<T>(
....same as you

const defaults: UseFetchOptions<T> = {
async onRequest(ctx) {
const accessToken = await userStore.getAccessToken();
const headers = new Headers(ctx.options.headers || {});
headers.set("Authorization", `Bearer ${accessToken}`);
headers.set("Content-Type", "application/json");
ctx.options.headers = headers;
},
$fetch: nuxtApp.$api as typeof $fetch,
};

..same
return useFetch<T>(url, params);
}
same as you

export function useAPI<T>(
....same as you

const defaults: UseFetchOptions<T> = {
async onRequest(ctx) {
const accessToken = await userStore.getAccessToken();
const headers = new Headers(ctx.options.headers || {});
headers.set("Authorization", `Bearer ${accessToken}`);
headers.set("Content-Type", "application/json");
ctx.options.headers = headers;
},
$fetch: nuxtApp.$api as typeof $fetch,
};

..same
return useFetch<T>(url, params);
}
Using this i got the autocompletion to work, but typing is still not showing the correct one. Also params in second last line has a error:
No overload matches this call.
Overload 1 of 2, '(request: NitroFetchRequest | Ref<NitroFetchRequest, NitroFetchRequest> | (() => NitroFetchRequest), opts?: UseFetchOptions<...> | undefined): AsyncData<...>', gave the following error.
Argument of type 'UseFetchOptions<T, T, KeysOf<T>, undefined, string & {}, AvailableRouterMethod<string & {}>>' is not assignable to parameter of type 'UseFetchOptions<T extends void ? unknown : T, T extends void ? unknown : T, KeysOf<T extends void ? unknown : T>, undefined, NitroFetchRequest, T extends void ? "get" : AvailableRouterMethod<...>>'.
Type 'T extends void ? unknown : T' is not assignable to type 'T'.
No overload matches this call.
Overload 1 of 2, '(request: NitroFetchRequest | Ref<NitroFetchRequest, NitroFetchRequest> | (() => NitroFetchRequest), opts?: UseFetchOptions<...> | undefined): AsyncData<...>', gave the following error.
Argument of type 'UseFetchOptions<T, T, KeysOf<T>, undefined, string & {}, AvailableRouterMethod<string & {}>>' is not assignable to parameter of type 'UseFetchOptions<T extends void ? unknown : T, T extends void ? unknown : T, KeysOf<T extends void ? unknown : T>, undefined, NitroFetchRequest, T extends void ? "get" : AvailableRouterMethod<...>>'.
Type 'T extends void ? unknown : T' is not assignable to type 'T'.
kapa.ai
kapa.ai2w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- The type error you’re seeing is about the UseFetchOptions generic parameters not matching what useFetch expects. From the Nuxt docs’ “custom useFetch” example and the related docs fix, the key points are:
- Don’t change the generic parameters of UseFetchOptions yourself.
- Let the function signature mirror useFetch as closely as possible, and just intersect your additions (like $fetch) onto the options type. [Custom useFetch docs] In other words, don’t declare UseFetchOptions<T> manually; instead, accept the same options type that useFetch would, and add $fetch via intersection. Based on the patterns shown in the docs PR, you can structure useAPI like this:
import type { UseFetchOptions } from 'nuxt/app'
import { defu } from 'defu'
import { useUserStore } from '@/store/user'

export function useAPI<T>(
url: Parameters<typeof useFetch<T>>[0],
options: UseFetchOptions<T> = {}
) {
const userStore = useUserStore()
const nuxtApp = useNuxtApp()

const defaults: UseFetchOptions<T> = {
async onRequest(ctx) {
const accessToken = await userStore.getAccessToken()

const headers = new Headers(ctx.options.headers || {})
headers.set('Authorization', `Bearer ${accessToken}`)
headers.set('Content-Type', 'application/json')

ctx.options.headers = headers
},
}

// Merge user options + defaults, then intersect in our custom $fetch
const params: UseFetchOptions<T> & { $fetch: typeof $fetch } = {
...defu(options, defaults),
$fetch: nuxtApp.$api as typeof $fetch,
}

return useFetch<T>(url, params)
}
import type { UseFetchOptions } from 'nuxt/app'
import { defu } from 'defu'
import { useUserStore } from '@/store/user'

export function useAPI<T>(
url: Parameters<typeof useFetch<T>>[0],
options: UseFetchOptions<T> = {}
) {
const userStore = useUserStore()
const nuxtApp = useNuxtApp()

const defaults: UseFetchOptions<T> = {
async onRequest(ctx) {
const accessToken = await userStore.getAccessToken()

const headers = new Headers(ctx.options.headers || {})
headers.set('Authorization', `Bearer ${accessToken}`)
headers.set('Content-Type', 'application/json')

ctx.options.headers = headers
},
}

// Merge user options + defaults, then intersect in our custom $fetch
const params: UseFetchOptions<T> & { $fetch: typeof $fetch } = {
...defu(options, defaults),
$fetch: nuxtApp.$api as typeof $fetch,
}

return useFetch<T>(url, params)
}
Why this fixes the error / improves typing: - options and defaults are plain UseFetchOptions<T>, so they match what useFetch’s overloads expect. [Custom useFetch docs] - The params variable is typed as UseFetchOptions<T> & { $fetch: typeof $fetch }, so $fetch is added without changing the generics that useFetch’s call signature uses. - url is typed using Parameters<typeof useFetch<T>>[0], which matches the “correct type for url in useFetch” fix in the docs. [useFetch url type] With this shape, you keep: - Inference of the response type from T (or from route types, if you use typed routes).
- Autocomplete for URLs and the same return type as useFetch.
If typing is still off after this, then either: - Your Nuxt version predates some of the recent typing fixes for useFetch hooks and $fetch forwarding, or
- There’s a project‑specific type override. The knowledge sources don’t give a more specific workaround beyond matching the docs’ “custom useFetch” pattern and intersecting $fetch into the options. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2w ago
<@260145305754927104> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
t.f.e
t.f.eOP2w ago
@kapa.ai Nope, even copying the one in docs https://nuxt.com/docs/4.x/guide/recipes/custom-usefetch import type { UseFetchOptions } from 'nuxt/app' export function useAPI<T> ( url: string | (() => string), options?: UseFetchOptions<T>, ) { return useFetch(url, { ...options, $fetch: useNuxtApp().$api as typeof $fetch, }) } This one does not return the correct type either
Nuxt
Custom useFetch in Nuxt · Recipes v4
How to create a custom fetcher for calling your external API in Nuxt.
kapa.ai
kapa.ai2w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- You’ve hit a current limitation: the knowledge sources (Nuxt docs + linked PRs/issues/answers) don’t describe any way to make a wrapper composable like useAPI preserve the route‑based response inference that useFetch has. What we do have in the sources: - The official “Custom useFetch” recipe is exactly what you pasted, and it only guarantees that you keep the same useFetch return shape and can plug in a custom $fetch. It doesn’t say that the URL → response‑type inference will still work through that wrapper. [Custom useFetch docs; link to custom example] - Several PRs fix and refine useFetch’s own typings (response hooks, url type, useAsyncData/useFetch types, request fetch typing), but again, none mention preserving that inference when useFetch is wrapped in another function. [useFetch url type; forward response type; update fetch type; useRequestFetch type] - The Stack Overflow Q&A about wrapping useFetch focuses on preserving reactivity and the augmented‑promise behavior; the recommended pattern is “don’t make it async, return useFetch directly, move await into onRequest”, but it does not address route‑based type inference either. [How to wrap useFetch, part 1–3] So: - Using the official recipe:
import type { UseFetchOptions } from 'nuxt/app'

export function useAPI<T>(
url: string | (() => string),
options?: UseFetchOptions<T>,
) {
return useFetch(url, {
...options,
$fetch: useNuxtApp().$api as typeof $fetch,
})
}

import type { UseFetchOptions } from 'nuxt/app'

export function useAPI<T>(
url: string | (() => string),
options?: UseFetchOptions<T>,
) {
return useFetch(url, {
...options,
$fetch: useNuxtApp().$api as typeof $fetch,
})
}

is currently as far as the docs go. [Custom useFetch docs] - The sources do not show any documented way to get the same URL‑specific response typing and autocomplete through useAPI that you see when calling useFetch directly. Given that, the best you can do per the available docs is: - Use the documented pattern above (or the slightly more complex one with onRequest if you need await). [Custom useFetch docs; wrap useFetch part 3] - Manually provide T when you need stronger typing, e.g. useAPI<MyResponse>('/api/foo'). Anything beyond that (e.g. perfectly mirroring Nuxt’s route‑to‑type inference in a wrapper) is not covered in the provided knowledge sources, so I can’t give you a reliable, documented solution for it. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2w ago
<@260145305754927104> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
t.f.e
t.f.eOP2w ago
Anyone else have a good idea? seems like the AI fell short 😉

Did you find this page helpful?