Tanstack Query queryOptions for frontend client #2046

Open
opened 2026-03-13 09:23:00 -05:00 by GiteaMirror · 5 comments
Owner

Originally created by @aidansunbury on GitHub (Sep 30, 2025).

Is this suited for github?

  • Yes, this is suited for github

The fronted auth client is great, but in my own experience, I always end up wrapping the api calls in a tanstack query mutation or query, since I use tanstack query for all other api calls I make to my backends. This allow for accessing additional properties such as isPending for a mutation, and generally makes it easier to take advantage of all the benefits a library like tanstack query provides.

Describe the solution you'd like

Ideally, every available api route callable by the client could instead produce queryOptions or mutationOptions that can get passed directly into a useQuery or useMutation hook.

// List Passkeys, with loading state
const { data: passkeys, isLoading, error } = useQuery(
  authClient.passkey.listUserPasskeys.queryOptions()
);

// Delete Passkey
const { mutate: deletePasskey, isPending } = useMutation(
  authClient.passkey.deletePasskey.mutationOptions({
    onSuccess: () => {
      // Take advantage of tanstack query's built in invalidation functionality
      queryClient.invalidateQueries(
        authClient.passkey.listUserPasskeys.queryOptions()
      );
    }
  })
);

For more examples of how this looks, see the type of query/mutation options generated by trpc

Describe alternatives you've considered

The existing alternative approach is to wrap auth client calls, but this boilerplate could easily be provided by better-auth.

const { data: passkeys, isLoading, error } = useQuery({
  queryKey: ['passkeys', 'list'],
  queryFn: async () => {
    const { data, error } = await authClient.passkey.listUserPasskeys();
    if (error) throw error;
    return data;
  },
});

const { mutate: deletePasskey, isPending } = useMutation({
  mutationFn: async (id: string) => {
    const { data, error } = await authClient.passkey.deletePasskey({ id });
    if (error) throw error;
    return data;
  },
  onSuccess: () => {
    // Invalidate and refetch passkeys list
    queryClient.invalidateQueries({ queryKey: ['passkeys', 'list'] });
  },
});

Additional context

No response

Originally created by @aidansunbury on GitHub (Sep 30, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### Is your feature request related to a problem? Please describe. The fronted auth client is great, but in my own experience, I always end up wrapping the api calls in a tanstack query mutation or query, since I use tanstack query for all other api calls I make to my backends. This allow for accessing additional properties such as `isPending` for a mutation, and generally makes it easier to take advantage of all the benefits a library like tanstack query provides. ### Describe the solution you'd like Ideally, every available api route callable by the client could instead produce `queryOptions` or `mutationOptions` that can get passed directly into a `useQuery` or `useMutation` hook. ```ts // List Passkeys, with loading state const { data: passkeys, isLoading, error } = useQuery( authClient.passkey.listUserPasskeys.queryOptions() ); // Delete Passkey const { mutate: deletePasskey, isPending } = useMutation( authClient.passkey.deletePasskey.mutationOptions({ onSuccess: () => { // Take advantage of tanstack query's built in invalidation functionality queryClient.invalidateQueries( authClient.passkey.listUserPasskeys.queryOptions() ); } }) ); ``` For more examples of how this looks, [see the type of query/mutation options generated by trpc](https://trpc.io/docs/client/tanstack-react-query/usage) ### Describe alternatives you've considered The existing alternative approach is to wrap auth client calls, but this boilerplate could easily be provided by better-auth. ```ts const { data: passkeys, isLoading, error } = useQuery({ queryKey: ['passkeys', 'list'], queryFn: async () => { const { data, error } = await authClient.passkey.listUserPasskeys(); if (error) throw error; return data; }, }); const { mutate: deletePasskey, isPending } = useMutation({ mutationFn: async (id: string) => { const { data, error } = await authClient.passkey.deletePasskey({ id }); if (error) throw error; return data; }, onSuccess: () => { // Invalidate and refetch passkeys list queryClient.invalidateQueries({ queryKey: ['passkeys', 'list'] }); }, }); ``` ### Additional context _No response_
Author
Owner

@brachkow commented on GitHub (Sep 30, 2025):

There are a ton of query libraries apart of tanstack query:

And tanstack query itself is not a single package, but a bunch of packages for different frameworks.

So I believe this change is more like fine-tuning library for specific project.

This is different from adding db drivers/adapters as better-auth needs to execute sql on all these databases. For integrating with frontend, delivering just methods is great.

@brachkow commented on GitHub (Sep 30, 2025): There are a ton of query libraries apart of tanstack query: - https://github.com/posva/pinia-colada - https://swr.vercel.app/ And tanstack query itself is not a single package, but a bunch of packages for different frameworks. So I believe this change is more like fine-tuning library for specific project. This is different from adding db drivers/adapters as better-auth needs to execute sql on all these databases. For integrating with frontend, delivering just methods is great.
Author
Owner

@aidansunbury commented on GitHub (Sep 30, 2025):

There are alternatives, but tanstack is by far the most widely used. The fact that tanstack query is framework agnostic means that if better-auth provided queryOptions, all frameworks could use those queryOptions with their specific framework adapter.

This may be out of scope for better-auth to implement directly, but I just wanted to propose adding it to the core of better-auth before looking into writing a community plugin.

@aidansunbury commented on GitHub (Sep 30, 2025): There are alternatives, but tanstack is by far the most widely used. The fact that tanstack query is framework agnostic means that if better-auth provided `queryOptions`, all frameworks could use those `queryOptions` with their specific framework adapter. This may be out of scope for better-auth to implement directly, but I just wanted to propose adding it to the core of better-auth before looking into writing a community plugin.
Author
Owner

@brachkow commented on GitHub (Sep 30, 2025):

I believe what you want can be achieved via using OpenAPI https://www.better-auth.com/docs/plugins/open-api, and OpenAPI codegen tool like https://kubb.dev/plugins/plugin-react-query/

@brachkow commented on GitHub (Sep 30, 2025): I believe what you want can be achieved via using OpenAPI https://www.better-auth.com/docs/plugins/open-api, and OpenAPI codegen tool like https://kubb.dev/plugins/plugin-react-query/
Author
Owner

@aidansunbury commented on GitHub (Sep 30, 2025):

Very true! Not sure why I didn't think of that 😅

@aidansunbury commented on GitHub (Sep 30, 2025): Very true! Not sure why I didn't think of that 😅
Author
Owner

@Pagebakers commented on GitHub (Dec 28, 2025):

@aidansunbury i needed this too and wrote a little library for it. Using codegen for this is completely overkill.

npm i better-auth-react-query

Usage:

import { createAuthQueryClient } from 'better-auth-react-query'
import { authClient } from './auth' // your better auth client

const auth = createAuthQueryClient(authClient)

const { data } = useQuery(auth.apiKey.list.queryOptions({}))

const revokeApiKey = useMutation({
  ...auth.apiKey.delete.mutationOptions(),
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: auth.apiKey.list.queryKey({}) })
  },
})

Source code: https://github.com/saas-js/saas-js/tree/main/packages/better-auth-react-query

@Pagebakers commented on GitHub (Dec 28, 2025): @aidansunbury i needed this too and wrote a little library for it. Using codegen for this is completely overkill. ```bash npm i better-auth-react-query ``` Usage: ```tsx import { createAuthQueryClient } from 'better-auth-react-query' import { authClient } from './auth' // your better auth client const auth = createAuthQueryClient(authClient) const { data } = useQuery(auth.apiKey.list.queryOptions({})) const revokeApiKey = useMutation({ ...auth.apiKey.delete.mutationOptions(), onSuccess: () => { queryClient.invalidateQueries({ queryKey: auth.apiKey.list.queryKey({}) }) }, }) ``` Source code: https://github.com/saas-js/saas-js/tree/main/packages/better-auth-react-query
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2046