Skip to content

Branded queryKey in queryOptions causes TS error (TS2769) in useQuery #9920

Open
byungsker/query
#2

Description

@p-harelik

Describe the bug

I’m encountering a TypeScript overload mismatch (TS2769) when calling useQuery() with the result of a queryOptions(...) function whose returned object contains a queryKey with a branded type. The error occurs specifically when useQuery() receives the output of queryOptions(...) containing a branded value in queryKey.

Passing the same options to queryClient.fetchQuery(queryOptions(queryKey: brandedKey, queryFn)) — works fine.
Passing the branded key inline to useQuery (i.e. useQuery({ queryKey: brandedKey, queryFn})) — works fine.

Your minimal, reproducible example

https://codesandbox.io/p/devbox/tanstack-query-ts-error-2769-forked-spc8jh?file=%2Fsrc%2FPost.vue

Steps to reproduce

1. Define a branded type in your code:

type PostId = string & { readonly __brand: "PostId" };

2. Create a helper function that returns queryOptions where the queryKey includes a value using this branded type:

const getPostQueryOptions = (
  params: MaybeRefOrGetter<{ postId: PostId }>
) => {
  return queryOptions({
    queryKey: ["post", params],
    queryFn: () => fetcher(toValue(params).postId),
  });
};

3. Call useQuery() using the result of this helper:

useQuery(getPostQueryOptions({ postId }));

4. Observe the TypeScript error:
No overload matches this call.
Overload 1 of 3, '(options: DefinedInitialQueryOptions<Post, Error, Post, MaybeRefDeep<string | (() => { postId: PostId; }) | { postId: PostId; }>[]>, queryClient?: QueryClient | undefined): UseQueryDefinedReturnType<...>', gave the following error.... ts(2769)

Expected behavior

useQuery() should correctly accept a QueryOptions object whose queryKey contains a branded (nominal) type.

In other words:
Passing the result of queryOptions(...) or any helper function that returns a QueryOptions object should not trigger a TypeScript overload error, even if the queryKey includes branded values.

How often does this bug happen?

Every time

Screenshots or Videos

Image Image

Platform

  • OS [Windows]
  • Browser [Chrome]

Tanstack Query adapter

vue-query

TanStack Query version

v5.85.3

TypeScript version

v5.9.2

Additional context

No response

Activity

  1. DamianOsipiuk commented on Nov 27, 2025

    @DamianOsipiuk
    Contributor

    Hmm it seems that branded type is conflicting with DataTag. If i remove it from queryOptions signature then TS error goes away.

    Not sure about the fix.

  2. added a commit that references this issue on Apr 25, 2026
    2b87d91
  3. xerox0213 commented on May 14, 2026

    @xerox0213

    I'm experiencing a similar TS2769 error when spreading queryOptions with an extra enabled property inside a Vue computed():

    useQuery(
      computed(() => ({
        ...myQueryOptions(params),
        enabled: someCondition,
      }))
    )

    Error: Type '(string | number)[]' is missing the following properties from type '... & { [dataTagSymbol]: ...; [dataTagErrorSymbol]: ...; }': [dataTagSymbol], [dataTagErrorSymbol]

  4. KIM-DONGJU commented on May 20, 2026

    @KIM-DONGJU

    Additional production data point that may help prioritize this:

    We just attempted to upgrade @tanstack/vue-query from 5.97.0 → 5.100.11 on a Nuxt 4 codebase (~24 files using the spread-and-override pattern). The upgrade is blocked specifically by this regression, and the impact is narrow but real:

    Impact in our codebase: 3 out of 24 spread-and-override hooks fail TS2769. The other 21 happen to pass because their type shape doesn't trigger the issue.

    Pattern that fails:

    useQuery({
      ...someQueries.x(),     // queryOptions(...) return value
      staleTime: 5 * MINUTE,
      enabled,
    })

    Failing cases share at least one of these traits (based on bisecting which 3 of 24 fail):

    1. Response data is a primitive (e.g. boolean) — dataTagSymbol: boolean brand on the queryKey seems particularly fragile through MaybeRefDeep mapping
    2. refetchInterval is set alongside the spread — its generic instantiation re-references the branded queryKey shape, triggering the mismatch

    Workaround we shipped: dropping the spread in favor of explicit field forwarding works around it:

    const opts = someQueries.x()
    useQuery({
      queryKey: opts.queryKey,
      queryFn: opts.queryFn,
      staleTime: 5 * MINUTE,
      enabled,
    })

    Property access preserves the branded queryKey type, while spread + extra props loses it through MaybeRefDeep. This matches the root cause #10573 identifies.

    We're unblocked via the workaround, but having #10573 (or equivalent) land would let us delete the explicit-field workaround and restore the idiomatic spread pattern — which is what the official TanStack Vue docs recommend.

    Happy to share the failing file paths / a minimal repro if it helps. The reproduction in the issue body already covers the essential case; this is mostly a "+1, real-world frequency" data point.

  5. added a commit that references this issue on Jul 22, 2026
    af629b6
  6. basuneko commented on Sep 22, 2026

    @basuneko

    I'm upgrading from 5.92 and seeing something similar.

    Although I don't use branded types; my issue manifests when using keys containing a ref/computed.
    The issue first appeared on 5.95 and it still occurs on 5.102.8.

    For me, wrapping the whole query key in a computed fixes and unwrapping the param seems to have helped 🤞

    // before: 
    function myQueryOptions(foo: MaybeRef<string>) {
      queryOptions({
        queryKey: ['bar', toRef(foo)] // ❌ this fails
        ...
      })
    }
    
    //after
    function myQueryOptions(foo: MaybeRef<string>) {
      queryOptions({
        queryKey: computed(() => ['bar', toValue(foo)]) // ✅ this seems works
        ...
      })
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions