From 1ec11e90a675ac2ffc6125623d2ff22e2c80fbef Mon Sep 17 00:00:00 2001
From: Eliya Cohen <co.eliya2@gmail.com>
Date: Mon, 30 Mar 2026 22:17:10 +0300
Subject: [PATCH 1/6] feat(eslint-plugin-query): add prefer-query-options rule

---
 docs/config.json                              |   4 +
 docs/eslint/eslint-plugin-query.md            |  24 +
 docs/eslint/prefer-query-options.md           | 121 +++
 .../__tests__/prefer-query-options.test.ts    | 889 ++++++++++++++++++
 packages/eslint-plugin-query/src/index.ts     |  50 +-
 packages/eslint-plugin-query/src/rules.ts     |   2 +
 .../prefer-query-options.rule.ts              | 321 +++++++
 7 files changed, 1394 insertions(+), 17 deletions(-)
 create mode 100644 docs/eslint/prefer-query-options.md
 create mode 100644 packages/eslint-plugin-query/src/__tests__/prefer-query-options.test.ts
 create mode 100644 packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts

diff --git a/docs/config.json b/docs/config.json
index 54eeb77e83..41200ceaed 100644
--- a/docs/config.json
+++ b/docs/config.json
@@ -1293,6 +1293,10 @@
         {
           "label": "Mutation Property Order",
           "to": "eslint/mutation-property-order"
+        },
+        {
+          "label": "Prefer Query Options",
+          "to": "eslint/prefer-query-options"
         }
       ]
     },
diff --git a/docs/eslint/eslint-plugin-query.md b/docs/eslint/eslint-plugin-query.md
index 8b6ae637de..8a1d726884 100644
--- a/docs/eslint/eslint-plugin-query.md
+++ b/docs/eslint/eslint-plugin-query.md
@@ -46,6 +46,19 @@ export default [
 ]
 ```
 
+### Recommended strict setup
+
+The `flat/recommended-strict` config extends `flat/recommended` with additional opinionated rules that enforce best practices more aggressively.
+
+```js
+import pluginQuery from '@tanstack/eslint-plugin-query'
+
+export default [
+  ...pluginQuery.configs['flat/recommended-strict'],
+  // Any other config...
+]
+```
+
 ### Custom setup
 
 Alternatively, you can load the plugin and configure only the rules you want to use:
@@ -78,6 +91,16 @@ To enable all of the recommended rules for our plugin, add `plugin:@tanstack/que
 }
 ```
 
+### Recommended strict setup
+
+The `recommendedStrict` config extends `recommended` with additional opinionated rules:
+
+```json
+{
+  "extends": ["plugin:@tanstack/query/recommendedStrict"]
+}
+```
+
 ### Custom setup
 
 Alternatively, add `@tanstack/query` to the plugins section, and configure the rules you want to use:
@@ -100,3 +123,4 @@ Alternatively, add `@tanstack/query` to the plugins section, and configure the r
 - [@tanstack/query/infinite-query-property-order](./infinite-query-property-order.md)
 - [@tanstack/query/no-void-query-fn](./no-void-query-fn.md)
 - [@tanstack/query/mutation-property-order](./mutation-property-order.md)
+- [@tanstack/query/prefer-query-options](./prefer-query-options.md)
diff --git a/docs/eslint/prefer-query-options.md b/docs/eslint/prefer-query-options.md
new file mode 100644
index 0000000000..f93f8adbf5
--- /dev/null
+++ b/docs/eslint/prefer-query-options.md
@@ -0,0 +1,121 @@
+---
+id: prefer-query-options
+title: Prefer the use of queryOptions
+---
+
+Separating `queryKey` and `queryFn` can cause unexpected runtime issues when the same query key is accidentally used with more than one `queryFn`. Wrapping them in `queryOptions` (or `infiniteQueryOptions`) co-locates the key and function, making queries safer and easier to reuse.
+
+## Rule Details
+
+Examples of **incorrect** code for this rule:
+
+```tsx
+/* eslint "@tanstack/query/prefer-query-options": "error" */
+
+function Component({ id }) {
+  const query = useQuery({
+    queryKey: ['get', id],
+    queryFn: () => Api.get(`/foo/${id}`),
+  })
+  // ...
+}
+```
+
+```tsx
+/* eslint "@tanstack/query/prefer-query-options": "error" */
+
+function useFooQuery() {
+  return useQuery({
+    queryKey: ['get', id],
+    queryFn: () => Api.get(`/foo/${id}`),
+  })
+}
+```
+
+Examples of **correct** code for this rule:
+
+```tsx
+/* eslint "@tanstack/query/prefer-query-options": "error" */
+
+const options = queryOptions({
+  queryKey: ['get', id],
+  queryFn: () => Api.get(`/foo/${id}`),
+})
+
+function Component({ id }) {
+  const query = useQuery(options)
+  // ...
+}
+```
+
+```tsx
+/* eslint "@tanstack/query/prefer-query-options": "error" */
+
+const options = queryOptions({
+  queryKey: ['get', id],
+  queryFn: () => Api.get(`/foo/${id}`),
+})
+
+function useFooQuery() {
+  return useQuery({ ...options, select: (data) => data.foo })
+}
+```
+
+The rule also enforces reusing `queryKey` from a `queryOptions` result instead of typing it manually in `QueryClient` methods or filters.
+
+Examples of **incorrect** `queryKey` references for this rule:
+
+```tsx
+/* eslint "@tanstack/query/prefer-query-options": "error" */
+
+const todoOptions = queryOptions({
+  queryKey: ['todo', id],
+  queryFn: () => api.getTodo(id),
+})
+
+queryClient.getQueryData(['todo', id])
+```
+
+```tsx
+/* eslint "@tanstack/query/prefer-query-options": "error" */
+
+const todoOptions = queryOptions({
+  queryKey: ['todo', id],
+  queryFn: () => api.getTodo(id),
+})
+
+queryClient.invalidateQueries({ queryKey: ['todo', id] })
+```
+
+Examples of **correct** `queryKey` references for this rule:
+
+```tsx
+/* eslint "@tanstack/query/prefer-query-options": "error" */
+
+const todoOptions = queryOptions({
+  queryKey: ['todo', id],
+  queryFn: () => api.getTodo(id),
+})
+
+queryClient.getQueryData(todoOptions.queryKey)
+```
+
+```tsx
+/* eslint "@tanstack/query/prefer-query-options": "error" */
+
+const todoOptions = queryOptions({
+  queryKey: ['todo', id],
+  queryFn: () => api.getTodo(id),
+})
+
+queryClient.invalidateQueries({ queryKey: todoOptions.queryKey })
+```
+
+## When Not To Use It
+
+If you do not want to enforce the use of `queryOptions` in your codebase, you will not need this rule.
+
+## Attributes
+
+- [x] ✅ Recommended (strict)
+- [ ] 🔧 Fixable
diff --git a/packages/eslint-plugin-query/src/__tests__/prefer-query-options.test.ts b/packages/eslint-plugin-query/src/__tests__/prefer-query-options.test.ts
new file mode 100644
index 0000000000..1261d0e9bb
--- /dev/null
+++ b/packages/eslint-plugin-query/src/__tests__/prefer-query-options.test.ts
@@ -0,0 +1,889 @@
+import { RuleTester } from '@typescript-eslint/rule-tester'
+import { afterAll, describe, it } from 'vitest'
+import { rule } from '../rules/prefer-query-options/prefer-query-options.rule'
+import { normalizeIndent } from './test-utils'
+
+RuleTester.afterAll = afterAll
+RuleTester.describe = describe
+RuleTester.it = it
+
+const ruleTester = new RuleTester()
+
+describe('prefer-query-options', () => {
+  describe('queryOptions / infiniteQueryOptions builders', () => {
+    ruleTester.run('prefer-query-options', rule, {
+      valid: [
+        {
+          name: 'queryOptions builder is allowed',
+          code: normalizeIndent`
+            import { queryOptions } from '@tanstack/react-query'
+
+            const todosOptions = queryOptions({
+              queryKey: ['todos'],
+              queryFn: () => fetchTodos(),
+            })
+          `,
+        },
+        {
+          name: 'infiniteQueryOptions builder is allowed',
+          code: normalizeIndent`
+            import { infiniteQueryOptions } from '@tanstack/react-query'
+
+            const todosOptions = infiniteQueryOptions({
+              queryKey: ['todos'],
+              queryFn: ({ pageParam }) => fetchTodos(pageParam),
+              initialPageParam: 0,
+              getNextPageParam: (lastPage) => lastPage.nextCursor,
+            })
+          `,
+        },
+      ],
+      invalid: [],
+    })
+  })
+
+  describe('hooks consuming queryOptions result', () => {
+    ruleTester.run('prefer-query-options', rule, {
+      valid: [
+        {
+          name: 'useQuery with queryOptions result is allowed',
+          code: normalizeIndent`
+            import { useQuery, queryOptions } from '@tanstack/react-query'
+
+            const todosOptions = queryOptions({
+              queryKey: ['todos'],
+              queryFn: () => fetchTodos(),
+            })
+
+            function Component() {
+              const query = useQuery(todosOptions)
+              return null
+            }
+          `,
+        },
+        {
+          name: 'useQuery with queryOptions function call result is allowed',
+          code: normalizeIndent`
+            import { useQuery } from '@tanstack/react-query'
+
+            function Component({ id }) {
+              const query = useQuery(todosOptions(id))
+              return null
+            }
+          `,
+        },
+        {
+          name: 'useQuery spreading queryOptions result is allowed',
+          code: normalizeIndent`
+            import { useQuery, queryOptions } from '@tanstack/react-query'
+
+            const todosOptions = queryOptions({
+              queryKey: ['todos'],
+              queryFn: () => fetchTodos(),
+            })
+
+            function Component() {
+              const query = useQuery({ ...todosOptions, select: (data) => data.items })
+              return null
+            }
+          `,
+        },
+        {
+          name: 'useQuery spreading queryOptions function call result is allowed',
+          code: normalizeIndent`
+            import { useQuery } from '@tanstack/react-query'
+
+            function Component({ id }) {
+              const query = useQuery({ ...todosOptions(id), select: (data) => data.items })
+              return null
+            }
+          `,
+        },
+        {
+          name: 'useQueries with all entries from queryOptions is allowed',
+          code: normalizeIndent`
+            import { useQueries } from '@tanstack/react-query'
+
+            function Component() {
+              const queries = useQueries({
+                queries: [todosOptions, usersOptions],
+              })
+              return null
+            }
+          `,
+        },
+      ],
+      invalid: [],
+    })
+  })
+
+  describe('queryClient methods referencing queryKey from options', () => {
+    ruleTester.run('prefer-query-options', rule, {
+      valid: [
+        {
+          name: 'queryClient.getQueryData with options.queryKey is allowed',
+          code: normalizeIndent`
+            import { useQueryClient, queryOptions } from '@tanstack/react-query'
+
+            const todosOptions = queryOptions({
+              queryKey: ['todos'],
+              queryFn: () => fetchTodos(),
+            })
+
+            function Component() {
+              const queryClient = useQueryClient()
+              const data = queryClient.getQueryData(todosOptions.queryKey)
+              return null
+            }
+          `,
+        },
+        {
+          name: 'queryClient.setQueryData with options.queryKey is allowed',
+          code: normalizeIndent`
+            import { useQueryClient, queryOptions } from '@tanstack/react-query'
+
+            const todosOptions = queryOptions({
+              queryKey: ['todos'],
+              queryFn: () => fetchTodos(),
+            })
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.setQueryData(todosOptions.queryKey, [])
+              return null
+            }
+          `,
+        },
+        {
+          name: 'queryClient.invalidateQueries with options.queryKey is allowed',
+          code: normalizeIndent`
+            import { useQueryClient, queryOptions } from '@tanstack/react-query'
+
+            const todosOptions = queryOptions({
+              queryKey: ['todos'],
+              queryFn: () => fetchTodos(),
+            })
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.invalidateQueries({ queryKey: todosOptions.queryKey })
+              return null
+            }
+          `,
+        },
+        {
+          name: 'queryClient.invalidateQueries with options.queryKey and extra filter props is allowed',
+          code: normalizeIndent`
+            import { useQueryClient, queryOptions } from '@tanstack/react-query'
+
+            const todosOptions = queryOptions({
+              queryKey: ['todos'],
+              queryFn: () => fetchTodos(),
+            })
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.invalidateQueries({ queryKey: todosOptions.queryKey, exact: true })
+              return null
+            }
+          `,
+        },
+        {
+          name: 'queryClient.getQueryData with variable queryKey is allowed',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component({ queryKey }) {
+              const queryClient = useQueryClient()
+              const data = queryClient.getQueryData(queryKey)
+              return null
+            }
+          `,
+        },
+        {
+          name: 'non-queryClient fetchQuery call is ignored',
+          code: normalizeIndent`
+            import { useQuery } from '@tanstack/react-query'
+
+            const analytics = {
+              fetchQuery(options) {
+                return options
+              },
+            }
+
+            function Component() {
+              useQuery(todosOptions)
+
+              analytics.fetchQuery({
+                queryKey: ['todos'],
+                queryFn: () => fetchTodos(),
+              })
+
+              return null
+            }
+          `,
+        },
+      ],
+      invalid: [],
+    })
+  })
+
+  describe('non-tanstack imports', () => {
+    ruleTester.run('prefer-query-options', rule, {
+      valid: [
+        {
+          name: 'non-tanstack useQuery is ignored',
+          code: normalizeIndent`
+            import { useQuery } from 'other-library'
+
+            function Component() {
+              const query = useQuery({
+                queryKey: ['todos'],
+                queryFn: () => fetchTodos(),
+              })
+              return null
+            }
+          `,
+        },
+      ],
+      invalid: [],
+    })
+  })
+
+  describe('inline lone queryKey or queryFn in hooks', () => {
+    ruleTester.run('prefer-query-options', rule, {
+      valid: [],
+      invalid: [
+        {
+          name: 'useQuery with only inline queryKey (no queryFn)',
+          code: normalizeIndent`
+            import { useQuery } from '@tanstack/react-query'
+
+            function Component() {
+              const query = useQuery({ queryKey: ['todos'] })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'useQuery with only inline queryFn (no queryKey)',
+          code: normalizeIndent`
+            import { useQuery } from '@tanstack/react-query'
+
+            function Component() {
+              const query = useQuery({ queryFn: () => fetchTodos() })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+      ],
+    })
+  })
+
+  describe('spread with inline queryKey or queryFn override in hooks', () => {
+    ruleTester.run('prefer-query-options', rule, {
+      valid: [],
+      invalid: [
+        {
+          name: 'useQuery spreading options but overriding queryKey inline',
+          code: normalizeIndent`
+            import { useQuery } from '@tanstack/react-query'
+
+            function Component() {
+              const query = useQuery({ ...options, queryKey: ['override'] })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'useQuery spreading options but overriding queryFn inline',
+          code: normalizeIndent`
+            import { useQuery } from '@tanstack/react-query'
+
+            function Component() {
+              const query = useQuery({ ...options, queryFn: () => fetchOverride() })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+      ],
+    })
+  })
+
+  describe('inline queryKey + queryFn in hooks', () => {
+    ruleTester.run('prefer-query-options', rule, {
+      valid: [],
+      invalid: [
+        {
+          name: 'useQuery with inline queryKey + queryFn',
+          code: normalizeIndent`
+            import { useQuery } from '@tanstack/react-query'
+
+            function Component() {
+              const query = useQuery({
+                queryKey: ['todos'],
+                queryFn: () => fetchTodos(),
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'useInfiniteQuery with inline queryKey + queryFn',
+          code: normalizeIndent`
+            import { useInfiniteQuery } from '@tanstack/react-query'
+
+            function Component() {
+              const query = useInfiniteQuery({
+                queryKey: ['todos'],
+                queryFn: ({ pageParam }) => fetchTodos(pageParam),
+                initialPageParam: 0,
+                getNextPageParam: (lastPage) => lastPage.nextCursor,
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'useSuspenseQuery with inline queryKey + queryFn',
+          code: normalizeIndent`
+            import { useSuspenseQuery } from '@tanstack/react-query'
+
+            function Component() {
+              const query = useSuspenseQuery({
+                queryKey: ['todos'],
+                queryFn: () => fetchTodos(),
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'useSuspenseInfiniteQuery with inline queryKey + queryFn',
+          code: normalizeIndent`
+            import { useSuspenseInfiniteQuery } from '@tanstack/react-query'
+
+            function Component() {
+              const query = useSuspenseInfiniteQuery({
+                queryKey: ['todos'],
+                queryFn: ({ pageParam }) => fetchTodos(pageParam),
+                initialPageParam: 0,
+                getNextPageParam: (lastPage) => lastPage.nextCursor,
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'useQueries with inline queryKey + queryFn in queries array',
+          code: normalizeIndent`
+            import { useQueries } from '@tanstack/react-query'
+
+            function Component() {
+              const queries = useQueries({
+                queries: [
+                  {
+                    queryKey: ['todos'],
+                    queryFn: () => fetchTodos(),
+                  },
+                ],
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'useQueries with multiple inline entries reports multiple errors',
+          code: normalizeIndent`
+            import { useQueries } from '@tanstack/react-query'
+
+            function Component() {
+              const queries = useQueries({
+                queries: [
+                  {
+                    queryKey: ['todos'],
+                    queryFn: () => fetchTodos(),
+                  },
+                  {
+                    queryKey: ['users'],
+                    queryFn: () => fetchUsers(),
+                  },
+                ],
+              })
+              return null
+            }
+          `,
+          errors: [
+            { messageId: 'preferQueryOptions' },
+            { messageId: 'preferQueryOptions' },
+          ],
+        },
+        {
+          name: 'useQueries with mapped inline query objects',
+          code: normalizeIndent`
+            import { useQueries } from '@tanstack/react-query'
+
+            function Component({ ids }) {
+              const queries = useQueries({
+                queries: ids.map((id) => ({
+                  queryKey: ['todos', id],
+                  queryFn: () => fetchTodo(id),
+                })),
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'useSuspenseQueries with inline queryKey + queryFn in queries array',
+          code: normalizeIndent`
+            import { useSuspenseQueries } from '@tanstack/react-query'
+
+            function Component() {
+              const queries = useSuspenseQueries({
+                queries: [
+                  {
+                    queryKey: ['todos'],
+                    queryFn: () => fetchTodos(),
+                  },
+                ],
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'useSuspenseQueries with mapped inline query objects',
+          code: normalizeIndent`
+            import { useSuspenseQueries } from '@tanstack/react-query'
+
+            function Component({ ids }) {
+              const queries = useSuspenseQueries({
+                queries: ids.map((id) => ({
+                  queryKey: ['todos', id],
+                  queryFn: () => fetchTodo(id),
+                })),
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'usePrefetchQuery with inline queryKey + queryFn',
+          code: normalizeIndent`
+            import { usePrefetchQuery } from '@tanstack/react-query'
+
+            function Component() {
+              usePrefetchQuery({
+                queryKey: ['todos'],
+                queryFn: () => fetchTodos(),
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'usePrefetchInfiniteQuery with inline queryKey + queryFn',
+          code: normalizeIndent`
+            import { usePrefetchInfiniteQuery } from '@tanstack/react-query'
+
+            function Component() {
+              usePrefetchInfiniteQuery({
+                queryKey: ['todos'],
+                queryFn: ({ pageParam }) => fetchTodos(pageParam),
+                initialPageParam: 0,
+                getNextPageParam: (lastPage) => lastPage.nextCursor,
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'inline queryKey + queryFn inside a custom hook',
+          code: normalizeIndent`
+            import { useQuery } from '@tanstack/react-query'
+
+            function useTodos() {
+              return useQuery({
+                queryKey: ['todos'],
+                queryFn: () => fetchTodos(),
+              })
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+      ],
+    })
+  })
+
+  describe('queryClient with alternate variable names', () => {
+    ruleTester.run('prefer-query-options', rule, {
+      valid: [],
+      invalid: [
+        {
+          name: 'client.fetchQuery with inline queryKey + queryFn',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const client = useQueryClient()
+              client.fetchQuery({
+                queryKey: ['todos'],
+                queryFn: () => fetchTodos(),
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'qc.getQueryData with inline queryKey',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const qc = useQueryClient()
+              const data = qc.getQueryData(['todos'])
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+        {
+          name: 'client.invalidateQueries with inline queryKey in filters',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const client = useQueryClient()
+              client.invalidateQueries({ queryKey: ['todos'] })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+      ],
+    })
+  })
+
+  describe('inline queryKey + queryFn in queryClient methods', () => {
+    ruleTester.run('prefer-query-options', rule, {
+      valid: [],
+      invalid: [
+        {
+          name: 'queryClient.fetchQuery with inline queryKey + queryFn',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.fetchQuery({
+                queryKey: ['todos'],
+                queryFn: () => fetchTodos(),
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'queryClient.prefetchQuery with inline queryKey + queryFn',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.prefetchQuery({
+                queryKey: ['todos'],
+                queryFn: () => fetchTodos(),
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'queryClient.fetchInfiniteQuery with inline queryKey + queryFn',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.fetchInfiniteQuery({
+                queryKey: ['todos'],
+                queryFn: ({ pageParam }) => fetchTodos(pageParam),
+                initialPageParam: 0,
+                getNextPageParam: (lastPage) => lastPage.nextCursor,
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'queryClient.prefetchInfiniteQuery with inline queryKey + queryFn',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.prefetchInfiniteQuery({
+                queryKey: ['todos'],
+                queryFn: ({ pageParam }) => fetchTodos(pageParam),
+                initialPageParam: 0,
+                getNextPageParam: (lastPage) => lastPage.nextCursor,
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'queryClient.ensureQueryData with inline queryKey + queryFn',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.ensureQueryData({
+                queryKey: ['todos'],
+                queryFn: () => fetchTodos(),
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'queryClient.ensureInfiniteQueryData with inline queryKey + queryFn',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.ensureInfiniteQueryData({
+                queryKey: ['todos'],
+                queryFn: ({ pageParam }) => fetchTodos(pageParam),
+                initialPageParam: 0,
+                getNextPageParam: (lastPage) => lastPage.nextCursor,
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+      ],
+    })
+  })
+
+  describe('inline queryKey as direct parameter', () => {
+    ruleTester.run('prefer-query-options', rule, {
+      valid: [],
+      invalid: [
+        {
+          name: 'queryClient.getQueryData with inline queryKey',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              const data = queryClient.getQueryData(['todos'])
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+        {
+          name: 'queryClient.setQueryData with inline queryKey',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.setQueryData(['todos'], [])
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+        {
+          name: 'queryClient.getQueryState with inline queryKey',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              const state = queryClient.getQueryState(['todos'])
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+        {
+          name: 'queryClient.setQueryDefaults with inline queryKey',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.setQueryDefaults(['todos'], { staleTime: 1000 })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+        {
+          name: 'queryClient.getQueryDefaults with inline queryKey',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              const defaults = queryClient.getQueryDefaults(['todos'])
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+      ],
+    })
+  })
+
+  describe('inline queryKey in filter objects', () => {
+    ruleTester.run('prefer-query-options', rule, {
+      valid: [],
+      invalid: [
+        {
+          name: 'queryClient.invalidateQueries with inline queryKey in filters',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.invalidateQueries({ queryKey: ['todos'] })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+        {
+          name: 'queryClient.cancelQueries with inline queryKey in filters',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.cancelQueries({ queryKey: ['todos'] })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+        {
+          name: 'queryClient.refetchQueries with inline queryKey in filters',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.refetchQueries({ queryKey: ['todos'] })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+        {
+          name: 'queryClient.removeQueries with inline queryKey in filters',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.removeQueries({ queryKey: ['todos'] })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+        {
+          name: 'queryClient.resetQueries with inline queryKey in filters',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.resetQueries({ queryKey: ['todos'] })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+        {
+          name: 'queryClient.isFetching with inline queryKey in filters',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              const count = queryClient.isFetching({ queryKey: ['todos'] })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+        {
+          name: 'queryClient.getQueriesData with inline queryKey in filters',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              const data = queryClient.getQueriesData({ queryKey: ['todos'] })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+        {
+          name: 'queryClient.setQueriesData with inline queryKey in filters',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.setQueriesData({ queryKey: ['todos'] }, [])
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+        {
+          name: 'useIsFetching with inline queryKey in filters',
+          code: normalizeIndent`
+            import { useIsFetching } from '@tanstack/react-query'
+
+            function Component() {
+              const count = useIsFetching({ queryKey: ['todos'] })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+      ],
+    })
+  })
+})
diff --git a/packages/eslint-plugin-query/src/index.ts b/packages/eslint-plugin-query/src/index.ts
index 452bc0f5a4..085ba7b9f3 100644
--- a/packages/eslint-plugin-query/src/index.ts
+++ b/packages/eslint-plugin-query/src/index.ts
@@ -8,10 +8,27 @@ export interface Plugin extends Omit<ESLint.Plugin, 'rules'> {
   rules: Record<RuleKey, RuleModule<any, any, any>>
   configs: {
     recommended: ESLint.ConfigData
+    recommendedStrict: ESLint.ConfigData
     'flat/recommended': Array<Linter.Config>
+    'flat/recommended-strict': Array<Linter.Config>
   }
 }
 
+const recommendedRules = {
+  '@tanstack/query/exhaustive-deps': 'error',
+  '@tanstack/query/no-rest-destructuring': 'warn',
+  '@tanstack/query/stable-query-client': 'error',
+  '@tanstack/query/no-unstable-deps': 'error',
+  '@tanstack/query/infinite-query-property-order': 'error',
+  '@tanstack/query/no-void-query-fn': 'error',
+  '@tanstack/query/mutation-property-order': 'error',
+} as const
+
+const recommendedStrictRules = {
+  ...recommendedRules,
+  '@tanstack/query/prefer-query-options': 'error',
+} as const
+
 export const plugin = {
   meta: {
     name: '@tanstack/eslint-plugin-query',
@@ -19,15 +36,11 @@ export const plugin = {
   configs: {
     recommended: {
       plugins: ['@tanstack/query'],
-      rules: {
-        '@tanstack/query/exhaustive-deps': 'error',
-        '@tanstack/query/no-rest-destructuring': 'warn',
-        '@tanstack/query/stable-query-client': 'error',
-        '@tanstack/query/no-unstable-deps': 'error',
-        '@tanstack/query/infinite-query-property-order': 'error',
-        '@tanstack/query/no-void-query-fn': 'error',
-        '@tanstack/query/mutation-property-order': 'error',
-      },
+      rules: recommendedRules,
+    },
+    recommendedStrict: {
+      plugins: ['@tanstack/query'],
+      rules: recommendedStrictRules,
     },
     'flat/recommended': [
       {
@@ -35,15 +48,16 @@ export const plugin = {
         plugins: {
           '@tanstack/query': {}, // Assigned after plugin object created
         },
-        rules: {
-          '@tanstack/query/exhaustive-deps': 'error',
-          '@tanstack/query/no-rest-destructuring': 'warn',
-          '@tanstack/query/stable-query-client': 'error',
-          '@tanstack/query/no-unstable-deps': 'error',
-          '@tanstack/query/infinite-query-property-order': 'error',
-          '@tanstack/query/no-void-query-fn': 'error',
-          '@tanstack/query/mutation-property-order': 'error',
+        rules: recommendedRules,
+      },
+    ],
+    'flat/recommended-strict': [
+      {
+        name: 'tanstack/query/flat/recommended-strict',
+        plugins: {
+          '@tanstack/query': {}, // Assigned after plugin object created
         },
+        rules: recommendedStrictRules,
       },
     ],
   },
@@ -51,5 +65,7 @@ export const plugin = {
 } satisfies Plugin
 
 plugin.configs['flat/recommended'][0]!.plugins['@tanstack/query'] = plugin
+plugin.configs['flat/recommended-strict'][0]!.plugins['@tanstack/query'] =
+  plugin
 
 export default plugin
diff --git a/packages/eslint-plugin-query/src/rules.ts b/packages/eslint-plugin-query/src/rules.ts
index d527768ec1..cbae59eb9c 100644
--- a/packages/eslint-plugin-query/src/rules.ts
+++ b/packages/eslint-plugin-query/src/rules.ts
@@ -5,6 +5,7 @@ import * as noUnstableDeps from './rules/no-unstable-deps/no-unstable-deps.rule'
 import * as infiniteQueryPropertyOrder from './rules/infinite-query-property-order/infinite-query-property-order.rule'
 import * as noVoidQueryFn from './rules/no-void-query-fn/no-void-query-fn.rule'
 import * as mutationPropertyOrder from './rules/mutation-property-order/mutation-property-order.rule'
+import * as preferQueryOptions from './rules/prefer-query-options/prefer-query-options.rule'
 import type { ESLintUtils } from '@typescript-eslint/utils'
 import type { ExtraRuleDocs } from './types'
 
@@ -24,4 +25,5 @@ export const rules: Record<
   [infiniteQueryPropertyOrder.name]: infiniteQueryPropertyOrder.rule,
   [noVoidQueryFn.name]: noVoidQueryFn.rule,
   [mutationPropertyOrder.name]: mutationPropertyOrder.rule,
+  [preferQueryOptions.name]: preferQueryOptions.rule,
 }
diff --git a/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts b/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts
new file mode 100644
index 0000000000..051b5565a2
--- /dev/null
+++ b/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts
@@ -0,0 +1,321 @@
+import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'
+import { ASTUtils } from '../../utils/ast-utils'
+import { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'
+import { getDocsUrl } from '../../utils/get-docs-url'
+import type { TSESTree } from '@typescript-eslint/utils'
+import type { ExtraRuleDocs } from '../../types'
+
+export const name = 'prefer-query-options'
+
+const queryHooks = [
+  'useQuery',
+  'useInfiniteQuery',
+  'useSuspenseQuery',
+  'useSuspenseInfiniteQuery',
+  'usePrefetchQuery',
+  'usePrefetchInfiniteQuery',
+]
+
+const queriesHooks = ['useQueries', 'useSuspenseQueries']
+
+const filterHooks = ['useIsFetching']
+
+const queryClientOptionMethods = [
+  'fetchQuery',
+  'prefetchQuery',
+  'fetchInfiniteQuery',
+  'prefetchInfiniteQuery',
+  'ensureQueryData',
+  'ensureInfiniteQueryData',
+]
+
+const queryClientQueryKeyMethods = [
+  'getQueryData',
+  'setQueryData',
+  'getQueryState',
+  'setQueryDefaults',
+  'getQueryDefaults',
+]
+
+const queryClientFilterMethods = [
+  'invalidateQueries',
+  'cancelQueries',
+  'refetchQueries',
+  'removeQueries',
+  'resetQueries',
+  'isFetching',
+  'getQueriesData',
+  'setQueriesData',
+]
+
+const queryOptionsBuilders = ['queryOptions', 'infiniteQueryOptions']
+
+const createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl)
+
+export const rule = createRule({
+  name,
+  meta: {
+    type: 'problem',
+    docs: {
+      description:
+        'Prefer using queryOptions() to co-locate queryKey and queryFn',
+      recommended: 'strict',
+    },
+    messages: {
+      preferQueryOptions:
+        'Prefer using queryOptions() or infiniteQueryOptions() to co-locate queryKey and queryFn.',
+      preferQueryOptionsQueryKey:
+        'Prefer referencing a queryKey from a queryOptions() result instead of typing it manually.',
+    },
+    schema: [],
+  },
+  defaultOptions: [],
+
+  create: detectTanstackQueryImports((context, _, helpers) => {
+    const queryClientVariables = new Set<string>()
+
+    function reportInlineQueryOptions(node: TSESTree.Node): void {
+      if (ASTUtils.isObjectExpression(node) && hasInlineQueryOptions(node)) {
+        context.report({
+          node,
+          messageId: 'preferQueryOptions',
+        })
+      }
+    }
+
+    function reportInlineFilterQueryKey(node: TSESTree.Node): void {
+      if (ASTUtils.isObjectExpression(node) && hasInlineFilterQueryKey(node)) {
+        context.report({
+          node,
+          messageId: 'preferQueryOptionsQueryKey',
+        })
+      }
+    }
+
+    return {
+      VariableDeclarator: (node) => {
+        if (
+          node.id.type === AST_NODE_TYPES.Identifier &&
+          node.init !== null &&
+          isTanstackQueryClient(node.init, helpers, queryClientVariables)
+        ) {
+          queryClientVariables.add(node.id.name)
+        }
+      },
+
+      CallExpression: (node) => {
+        if (ASTUtils.isIdentifierWithOneOfNames(node.callee, queryOptionsBuilders)) {
+          return
+        }
+
+        if (
+          ASTUtils.isIdentifier(node.callee) &&
+          helpers.isTanstackQueryImport(node.callee)
+        ) {
+          const options = node.arguments[0]
+
+          if (options === undefined) {
+            return
+          }
+
+          if (ASTUtils.isIdentifierWithOneOfNames(node.callee, queryHooks)) {
+            reportInlineQueryOptions(options)
+            return
+          }
+
+          if (
+            ASTUtils.isIdentifierWithOneOfNames(node.callee, queriesHooks) &&
+            ASTUtils.isObjectExpression(options)
+          ) {
+            const queries = ASTUtils.findPropertyWithIdentifierKey(
+              options.properties,
+              'queries',
+            )?.value
+
+            if (queries !== undefined) {
+              getQueryObjects(queries).forEach((query) => {
+                reportInlineQueryOptions(query)
+              })
+            }
+
+            return
+          }
+
+          if (ASTUtils.isIdentifierWithOneOfNames(node.callee, filterHooks)) {
+            reportInlineFilterQueryKey(options)
+          }
+
+          return
+        }
+
+        if (
+          node.callee.type !== AST_NODE_TYPES.MemberExpression ||
+          !ASTUtils.isIdentifier(node.callee.property) ||
+          !isTanstackQueryClient(
+            node.callee.object,
+            helpers,
+            queryClientVariables,
+          )
+        ) {
+          return
+        }
+
+        const method = node.callee.property.name
+        const options = node.arguments[0]
+
+        if (options === undefined) {
+          return
+        }
+
+        if (queryClientOptionMethods.includes(method)) {
+          reportInlineQueryOptions(options)
+          return
+        }
+
+        if (
+          queryClientQueryKeyMethods.includes(method) &&
+          options.type === AST_NODE_TYPES.ArrayExpression
+        ) {
+          context.report({
+            node: options,
+            messageId: 'preferQueryOptionsQueryKey',
+          })
+          return
+        }
+
+        if (queryClientFilterMethods.includes(method)) {
+          reportInlineFilterQueryKey(options)
+        }
+      },
+    }
+  }),
+})
+
+function hasInlineQueryOptions(node: TSESTree.ObjectExpression): boolean {
+  return (
+    ASTUtils.findPropertyWithIdentifierKey(node.properties, 'queryKey') !==
+      undefined ||
+    ASTUtils.findPropertyWithIdentifierKey(node.properties, 'queryFn') !==
+      undefined
+  )
+}
+
+function hasInlineFilterQueryKey(node: TSESTree.ObjectExpression): boolean {
+  return (
+    ASTUtils.findPropertyWithIdentifierKey(node.properties, 'queryKey')?.value
+      .type === AST_NODE_TYPES.ArrayExpression
+  )
+}
+
+function getReturnedObjectExpressions(
+  node: TSESTree.Node,
+): Array<TSESTree.ObjectExpression> {
+  if (ASTUtils.isObjectExpression(node)) {
+    return [node]
+  }
+
+  if (
+    node.type === AST_NODE_TYPES.ArrowFunctionExpression ||
+    node.type === AST_NODE_TYPES.FunctionExpression
+  ) {
+    return getReturnedObjectExpressions(node.body)
+  }
+
+  if (node.type === AST_NODE_TYPES.BlockStatement) {
+    return node.body.flatMap((statement) => {
+      if (
+        statement.type === AST_NODE_TYPES.ReturnStatement &&
+        statement.argument !== null
+      ) {
+        return getReturnedObjectExpressions(statement.argument)
+      }
+
+      return []
+    })
+  }
+
+  if (node.type === AST_NODE_TYPES.ConditionalExpression) {
+    return [
+      ...getReturnedObjectExpressions(node.consequent),
+      ...getReturnedObjectExpressions(node.alternate),
+    ]
+  }
+
+  if (node.type === AST_NODE_TYPES.LogicalExpression) {
+    return [
+      ...getReturnedObjectExpressions(node.left),
+      ...getReturnedObjectExpressions(node.right),
+    ]
+  }
+
+  if (node.type === AST_NODE_TYPES.SequenceExpression) {
+    return node.expressions.flatMap((expression) =>
+      getReturnedObjectExpressions(expression),
+    )
+  }
+
+  return []
+}
+
+function getQueryObjects(
+  node: TSESTree.Node,
+): Array<TSESTree.ObjectExpression> {
+  if (node.type === AST_NODE_TYPES.ArrayExpression) {
+    return node.elements.flatMap((element) => {
+      if (element !== null && ASTUtils.isObjectExpression(element)) {
+        return [element]
+      }
+
+      return []
+    })
+  }
+
+  if (
+    node.type === AST_NODE_TYPES.CallExpression &&
+    node.callee.type === AST_NODE_TYPES.MemberExpression &&
+    ASTUtils.isIdentifierWithName(node.callee.property, 'map')
+  ) {
+    const mapper = node.arguments[0]
+
+    if (
+      mapper?.type === AST_NODE_TYPES.ArrowFunctionExpression ||
+      mapper?.type === AST_NODE_TYPES.FunctionExpression
+    ) {
+      return getReturnedObjectExpressions(mapper)
+    }
+  }
+
+  return []
+}
+
+function isTanstackQueryClient(
+  node: TSESTree.Node,
+  helpers: {
+    isTanstackQueryImport: (node: TSESTree.Identifier) => boolean
+  },
+  queryClientVariables: Set<string>,
+): boolean {
+  if (node.type === AST_NODE_TYPES.Identifier) {
+    return queryClientVariables.has(node.name)
+  }
+
+  if (
+    node.type === AST_NODE_TYPES.CallExpression &&
+    ASTUtils.isIdentifierWithName(node.callee, 'useQueryClient')
+  ) {
+    return helpers.isTanstackQueryImport(node.callee)
+  }
+
+  if (
+    node.type === AST_NODE_TYPES.NewExpression &&
+    ASTUtils.isIdentifierWithName(node.callee, 'QueryClient')
+  ) {
+    return helpers.isTanstackQueryImport(node.callee)
+  }
+
+  if (node.type === AST_NODE_TYPES.ChainExpression) {
+    return isTanstackQueryClient(node.expression, helpers, queryClientVariables)
+  }
+
+  return false
+}

From 7d51d462787db5ea2b128958486aaba5678517b4 Mon Sep 17 00:00:00 2001
From: Eliya Cohen <co.eliya2@gmail.com>
Date: Mon, 30 Mar 2026 22:29:04 +0300
Subject: [PATCH 2/6] add changeset

---
 .changeset/prefer-query-options-rule.md | 5 +++++
 1 file changed, 5 insertions(+)
 create mode 100644 .changeset/prefer-query-options-rule.md

diff --git a/.changeset/prefer-query-options-rule.md b/.changeset/prefer-query-options-rule.md
new file mode 100644
index 0000000000..bd33094aec
--- /dev/null
+++ b/.changeset/prefer-query-options-rule.md
@@ -0,0 +1,5 @@
+---
+'@tanstack/eslint-plugin-query': minor
+---
+
+Add `prefer-query-options` rule and `recommendedStrict` config

From 3a8e0f47bac088f32cbbc8b5b4fd38dbdc252427 Mon Sep 17 00:00:00 2001
From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com>
Date: Mon, 30 Mar 2026 19:31:02 +0000
Subject: [PATCH 3/6] ci: apply automated fixes

---
 .../rules/prefer-query-options/prefer-query-options.rule.ts   | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts b/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts
index 051b5565a2..428ba5f262 100644
--- a/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts
+++ b/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts
@@ -104,7 +104,9 @@ export const rule = createRule({
       },
 
       CallExpression: (node) => {
-        if (ASTUtils.isIdentifierWithOneOfNames(node.callee, queryOptionsBuilders)) {
+        if (
+          ASTUtils.isIdentifierWithOneOfNames(node.callee, queryOptionsBuilders)
+        ) {
           return
         }
 

From afd00a973cb3020daaf2795c604b8848dc33e321 Mon Sep 17 00:00:00 2001
From: Eliya Cohen <co.eliya2@gmail.com>
Date: Mon, 30 Mar 2026 23:22:37 +0300
Subject: [PATCH 4/6] address review comments

---
 docs/eslint/prefer-query-options.md           |  88 ++++++----
 .../__tests__/prefer-query-options.test.ts    | 118 +++++++++++++
 .../prefer-query-options.rule.ts              | 162 ++++++++++++------
 3 files changed, 285 insertions(+), 83 deletions(-)

diff --git a/docs/eslint/prefer-query-options.md b/docs/eslint/prefer-query-options.md
index f93f8adbf5..3228774bf7 100644
--- a/docs/eslint/prefer-query-options.md
+++ b/docs/eslint/prefer-query-options.md
@@ -24,7 +24,7 @@ function Component({ id }) {
 ```tsx
 /* eslint "@tanstack/query/prefer-query-options": "error" */
 
-function useFooQuery() {
+function useFooQuery(id) {
   return useQuery({
     queryKey: ['get', id],
     queryFn: () => Api.get(`/foo/${id}`),
@@ -37,13 +37,15 @@ Examples of **correct** code for this rule:
 ```tsx
 /* eslint "@tanstack/query/prefer-query-options": "error" */
 
-const options = queryOptions({
-  queryKey: ['get', id],
-  queryFn: () => Api.get(`/foo/${id}`),
-})
+function getFooOptions(id) {
+  return queryOptions({
+    queryKey: ['get', id],
+    queryFn: () => Api.get(`/foo/${id}`),
+  })
+}
 
 function Component({ id }) {
-  const query = useQuery(options)
+  const query = useQuery(getFooOptions(id))
   // ...
 }
 ```
@@ -51,13 +53,15 @@ function Component({ id }) {
 ```tsx
 /* eslint "@tanstack/query/prefer-query-options": "error" */
 
-const options = queryOptions({
-  queryKey: ['get', id],
-  queryFn: () => Api.get(`/foo/${id}`),
-})
+function getFooOptions(id) {
+  return queryOptions({
+    queryKey: ['get', id],
+    queryFn: () => Api.get(`/foo/${id}`),
+  })
+}
 
-function useFooQuery() {
-  return useQuery({ ...options, select: (data) => data.foo })
+function useFooQuery(id) {
+  return useQuery({ ...getFooOptions(id), select: (data) => data.foo })
 }
 ```
 
@@ -68,23 +72,33 @@ Examples of **incorrect** `queryKey` references for this rule:
 ```tsx
 /* eslint "@tanstack/query/prefer-query-options": "error" */
 
-const todoOptions = queryOptions({
-  queryKey: ['todo', id],
-  queryFn: () => api.getTodo(id),
-})
+function todoOptions(id) {
+  return queryOptions({
+    queryKey: ['todo', id],
+    queryFn: () => api.getTodo(id),
+  })
+}
 
-queryClient.getQueryData(['todo', id])
+function Component({ id }) {
+  const queryClient = useQueryClient()
+  return queryClient.getQueryData(['todo', id])
+}
 ```
 
 ```tsx
 /* eslint "@tanstack/query/prefer-query-options": "error" */
 
-const todoOptions = queryOptions({
-  queryKey: ['todo', id],
-  queryFn: () => api.getTodo(id),
-})
+function todoOptions(id) {
+  return queryOptions({
+    queryKey: ['todo', id],
+    queryFn: () => api.getTodo(id),
+  })
+}
 
-queryClient.invalidateQueries({ queryKey: ['todo', id] })
+function Component({ id }) {
+  const queryClient = useQueryClient()
+  return queryClient.invalidateQueries({ queryKey: ['todo', id] })
+}
 ```
 
 Examples of **correct** `queryKey` references for this rule:
@@ -92,23 +106,33 @@ Examples of **correct** `queryKey` references for this rule:
 ```tsx
 /* eslint "@tanstack/query/prefer-query-options": "error" */
 
-const todoOptions = queryOptions({
-  queryKey: ['todo', id],
-  queryFn: () => api.getTodo(id),
-})
+function todoOptions(id) {
+  return queryOptions({
+    queryKey: ['todo', id],
+    queryFn: () => api.getTodo(id),
+  })
+}
 
-queryClient.getQueryData(todoOptions.queryKey)
+function Component({ id }) {
+  const queryClient = useQueryClient()
+  return queryClient.getQueryData(todoOptions(id).queryKey)
+}
 ```
 
 ```tsx
 /* eslint "@tanstack/query/prefer-query-options": "error" */
 
-const todoOptions = queryOptions({
-  queryKey: ['todo', id],
-  queryFn: () => api.getTodo(id),
-})
+function todoOptions(id) {
+  return queryOptions({
+    queryKey: ['todo', id],
+    queryFn: () => api.getTodo(id),
+  })
+}
 
-queryClient.invalidateQueries({ queryKey: todoOptions.queryKey })
+function Component({ id }) {
+  const queryClient = useQueryClient()
+  return queryClient.invalidateQueries({ queryKey: todoOptions(id).queryKey })
+}
 ```
 
 ## When Not To Use It
diff --git a/packages/eslint-plugin-query/src/__tests__/prefer-query-options.test.ts b/packages/eslint-plugin-query/src/__tests__/prefer-query-options.test.ts
index 1261d0e9bb..396be9da8b 100644
--- a/packages/eslint-plugin-query/src/__tests__/prefer-query-options.test.ts
+++ b/packages/eslint-plugin-query/src/__tests__/prefer-query-options.test.ts
@@ -200,6 +200,25 @@ describe('prefer-query-options', () => {
             }
           `,
         },
+        {
+          name: 'shadowed queryClient parameter is ignored',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+
+              function run(queryClient) {
+                queryClient.fetchQuery({
+                  queryKey: ['todos'],
+                  queryFn: () => fetchTodos(),
+                })
+              }
+
+              return null
+            }
+          `,
+        },
         {
           name: 'non-queryClient fetchQuery call is ignored',
           code: normalizeIndent`
@@ -333,6 +352,21 @@ describe('prefer-query-options', () => {
           `,
           errors: [{ messageId: 'preferQueryOptions' }],
         },
+        {
+          name: 'aliased useQuery with inline queryKey + queryFn',
+          code: normalizeIndent`
+            import { useQuery as useTanstackQuery } from '@tanstack/react-query'
+
+            function Component() {
+              const query = useTanstackQuery({
+                queryKey: ['todos'],
+                queryFn: () => fetchTodos(),
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
         {
           name: 'useInfiniteQuery with inline queryKey + queryFn',
           code: normalizeIndent`
@@ -550,6 +584,36 @@ describe('prefer-query-options', () => {
           `,
           errors: [{ messageId: 'preferQueryOptions' }],
         },
+        {
+          name: 'aliased useQueryClient tracks query client variables',
+          code: normalizeIndent`
+            import { useQueryClient as getClient } from '@tanstack/react-query'
+
+            function Component() {
+              const client = getClient()
+              client.fetchQuery({
+                queryKey: ['todos'],
+                queryFn: () => fetchTodos(),
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
+        {
+          name: 'aliased QueryClient tracks query client instances',
+          code: normalizeIndent`
+            import { QueryClient as Client } from '@tanstack/react-query'
+
+            const queryClient = new Client()
+
+            queryClient.fetchQuery({
+              queryKey: ['todos'],
+              queryFn: () => fetchTodos(),
+            })
+          `,
+          errors: [{ messageId: 'preferQueryOptions' }],
+        },
         {
           name: 'qc.getQueryData with inline queryKey',
           code: normalizeIndent`
@@ -707,6 +771,32 @@ describe('prefer-query-options', () => {
           `,
           errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
         },
+        {
+          name: 'queryClient.getQueryData with inline queryKey as const',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              const data = queryClient.getQueryData(['todos'] as const)
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+        {
+          name: 'queryClient.getQueryData with inline queryKey satisfies',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              const data = queryClient.getQueryData((['todos']) satisfies readonly string[])
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
         {
           name: 'queryClient.setQueryData with inline queryKey',
           code: normalizeIndent`
@@ -780,6 +870,34 @@ describe('prefer-query-options', () => {
           `,
           errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
         },
+        {
+          name: 'queryClient.invalidateQueries with inline queryKey as const in filters',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.invalidateQueries({ queryKey: ['todos'] as const })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
+        {
+          name: 'queryClient.invalidateQueries with inline queryKey satisfies in filters',
+          code: normalizeIndent`
+            import { useQueryClient } from '@tanstack/react-query'
+
+            function Component() {
+              const queryClient = useQueryClient()
+              queryClient.invalidateQueries({
+                queryKey: (['todos']) satisfies readonly string[],
+              })
+              return null
+            }
+          `,
+          errors: [{ messageId: 'preferQueryOptionsQueryKey' }],
+        },
         {
           name: 'queryClient.cancelQueries with inline queryKey in filters',
           code: normalizeIndent`
diff --git a/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts b/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts
index 051b5565a2..1dc92da0ed 100644
--- a/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts
+++ b/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts
@@ -2,7 +2,7 @@ import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'
 import { ASTUtils } from '../../utils/ast-utils'
 import { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'
 import { getDocsUrl } from '../../utils/get-docs-url'
-import type { TSESTree } from '@typescript-eslint/utils'
+import type { TSESLint, TSESTree } from '@typescript-eslint/utils'
 import type { ExtraRuleDocs } from '../../types'
 
 export const name = 'prefer-query-options'
@@ -51,6 +51,9 @@ const queryClientFilterMethods = [
 const queryOptionsBuilders = ['queryOptions', 'infiniteQueryOptions']
 
 const createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl)
+type Helpers = {
+  isTanstackQueryImport: (node: TSESTree.Identifier) => boolean
+}
 
 export const rule = createRule({
   name,
@@ -72,8 +75,6 @@ export const rule = createRule({
   defaultOptions: [],
 
   create: detectTanstackQueryImports((context, _, helpers) => {
-    const queryClientVariables = new Set<string>()
-
     function reportInlineQueryOptions(node: TSESTree.Node): void {
       if (ASTUtils.isObjectExpression(node) && hasInlineQueryOptions(node)) {
         context.report({
@@ -93,40 +94,34 @@ export const rule = createRule({
     }
 
     return {
-      VariableDeclarator: (node) => {
-        if (
-          node.id.type === AST_NODE_TYPES.Identifier &&
-          node.init !== null &&
-          isTanstackQueryClient(node.init, helpers, queryClientVariables)
-        ) {
-          queryClientVariables.add(node.id.name)
-        }
-      },
-
       CallExpression: (node) => {
-        if (ASTUtils.isIdentifierWithOneOfNames(node.callee, queryOptionsBuilders)) {
-          return
-        }
+        if (ASTUtils.isIdentifier(node.callee)) {
+          const importedName = getTanstackImportName(
+            context,
+            helpers,
+            node.callee,
+          )
+
+          if (importedName === null) {
+            return
+          }
+
+          if (queryOptionsBuilders.includes(importedName)) {
+            return
+          }
 
-        if (
-          ASTUtils.isIdentifier(node.callee) &&
-          helpers.isTanstackQueryImport(node.callee)
-        ) {
           const options = node.arguments[0]
 
           if (options === undefined) {
             return
           }
 
-          if (ASTUtils.isIdentifierWithOneOfNames(node.callee, queryHooks)) {
+          if (queryHooks.includes(importedName)) {
             reportInlineQueryOptions(options)
             return
           }
 
-          if (
-            ASTUtils.isIdentifierWithOneOfNames(node.callee, queriesHooks) &&
-            ASTUtils.isObjectExpression(options)
-          ) {
+          if (queriesHooks.includes(importedName) && ASTUtils.isObjectExpression(options)) {
             const queries = ASTUtils.findPropertyWithIdentifierKey(
               options.properties,
               'queries',
@@ -141,7 +136,7 @@ export const rule = createRule({
             return
           }
 
-          if (ASTUtils.isIdentifierWithOneOfNames(node.callee, filterHooks)) {
+          if (filterHooks.includes(importedName)) {
             reportInlineFilterQueryKey(options)
           }
 
@@ -151,11 +146,7 @@ export const rule = createRule({
         if (
           node.callee.type !== AST_NODE_TYPES.MemberExpression ||
           !ASTUtils.isIdentifier(node.callee.property) ||
-          !isTanstackQueryClient(
-            node.callee.object,
-            helpers,
-            queryClientVariables,
-          )
+          !isTanstackQueryClient(node.callee.object, context, helpers)
         ) {
           return
         }
@@ -174,7 +165,7 @@ export const rule = createRule({
 
         if (
           queryClientQueryKeyMethods.includes(method) &&
-          options.type === AST_NODE_TYPES.ArrayExpression
+          isInlineArrayExpression(options)
         ) {
           context.report({
             node: options,
@@ -201,10 +192,16 @@ function hasInlineQueryOptions(node: TSESTree.ObjectExpression): boolean {
 }
 
 function hasInlineFilterQueryKey(node: TSESTree.ObjectExpression): boolean {
-  return (
-    ASTUtils.findPropertyWithIdentifierKey(node.properties, 'queryKey')?.value
-      .type === AST_NODE_TYPES.ArrayExpression
-  )
+  const queryKey = ASTUtils.findPropertyWithIdentifierKey(
+    node.properties,
+    'queryKey',
+  )?.value
+
+  return queryKey !== undefined && isInlineArrayExpression(queryKey)
+}
+
+function isInlineArrayExpression(node: TSESTree.Node): boolean {
+  return unwrapTypeAssertions(node).type === AST_NODE_TYPES.ArrayExpression
 }
 
 function getReturnedObjectExpressions(
@@ -290,32 +287,95 @@ function getQueryObjects(
 
 function isTanstackQueryClient(
   node: TSESTree.Node,
-  helpers: {
-    isTanstackQueryImport: (node: TSESTree.Identifier) => boolean
-  },
-  queryClientVariables: Set<string>,
+  context: Readonly<TSESLint.RuleContext<string, ReadonlyArray<unknown>>>,
+  helpers: Helpers,
 ): boolean {
-  if (node.type === AST_NODE_TYPES.Identifier) {
-    return queryClientVariables.has(node.name)
+  const source = resolveQueryClientSource(node, context)
+
+  if (
+    source.type === AST_NODE_TYPES.CallExpression &&
+    ASTUtils.isIdentifier(source.callee)
+  ) {
+    return getTanstackImportName(context, helpers, source.callee) === 'useQueryClient'
   }
 
   if (
-    node.type === AST_NODE_TYPES.CallExpression &&
-    ASTUtils.isIdentifierWithName(node.callee, 'useQueryClient')
+    source.type === AST_NODE_TYPES.NewExpression &&
+    ASTUtils.isIdentifier(source.callee)
   ) {
-    return helpers.isTanstackQueryImport(node.callee)
+    return getTanstackImportName(context, helpers, source.callee) === 'QueryClient'
+  }
+
+  return false
+}
+
+function getTanstackImportName(
+  context: Readonly<TSESLint.RuleContext<string, ReadonlyArray<unknown>>>,
+  helpers: Helpers,
+  node: TSESTree.Identifier,
+): string | null {
+  if (!helpers.isTanstackQueryImport(node)) {
+    return null
   }
 
+  const definition = context.sourceCode
+    .getScope(node)
+    .references.find((reference) => reference.identifier === node)
+    ?.resolved?.defs[0]?.node
+
   if (
-    node.type === AST_NODE_TYPES.NewExpression &&
-    ASTUtils.isIdentifierWithName(node.callee, 'QueryClient')
+    definition?.type !== AST_NODE_TYPES.ImportSpecifier ||
+    definition.imported.type !== AST_NODE_TYPES.Identifier
   ) {
-    return helpers.isTanstackQueryImport(node.callee)
+    return null
+  }
+
+  return definition.imported.name
+}
+
+function resolveQueryClientSource(
+  node: TSESTree.Node,
+  context: Readonly<TSESLint.RuleContext<string, ReadonlyArray<unknown>>>,
+): TSESTree.Node {
+  const visitedNodes = new Set<TSESTree.Node>()
+
+  while (!visitedNodes.has(node)) {
+    visitedNodes.add(node)
+
+    if (node.type === AST_NODE_TYPES.ChainExpression) {
+      node = node.expression
+      continue
+    }
+
+    node = unwrapTypeAssertions(node)
+
+    if (node.type !== AST_NODE_TYPES.Identifier) {
+      return node
+    }
+
+    const expression = ASTUtils.getReferencedExpressionByIdentifier({
+      context,
+      node,
+    })
+
+    if (expression === null) {
+      return node
+    }
+
+    node = expression
   }
 
-  if (node.type === AST_NODE_TYPES.ChainExpression) {
-    return isTanstackQueryClient(node.expression, helpers, queryClientVariables)
+  return node
+}
+
+function unwrapTypeAssertions(node: TSESTree.Node): TSESTree.Node {
+  while (
+    node.type === AST_NODE_TYPES.TSAsExpression ||
+    node.type === AST_NODE_TYPES.TSSatisfiesExpression ||
+    node.type === AST_NODE_TYPES.TSTypeAssertion
+  ) {
+    node = node.expression
   }
 
-  return false
+  return node
 }

From aec9ed516d05aa0d86ebc1df20eedd74e8d45158 Mon Sep 17 00:00:00 2001
From: Eliya Cohen <co.eliya2@gmail.com>
Date: Tue, 31 Mar 2026 12:12:06 +0300
Subject: [PATCH 5/6] update example

---
 .../react/eslint-plugin-demo/eslint.config.js |  2 +-
 .../src/prefer-query-options-demo.tsx         | 49 +++++++++++++++++++
 .../react/eslint-plugin-demo/src/queries.tsx  | 23 +++++++++
 3 files changed, 73 insertions(+), 1 deletion(-)
 create mode 100644 examples/react/eslint-plugin-demo/src/prefer-query-options-demo.tsx
 create mode 100644 examples/react/eslint-plugin-demo/src/queries.tsx

diff --git a/examples/react/eslint-plugin-demo/eslint.config.js b/examples/react/eslint-plugin-demo/eslint.config.js
index 1e9a5820b5..85fa95bb76 100644
--- a/examples/react/eslint-plugin-demo/eslint.config.js
+++ b/examples/react/eslint-plugin-demo/eslint.config.js
@@ -3,7 +3,7 @@ import tseslint from 'typescript-eslint'
 
 export default [
   ...tseslint.configs.recommended,
-  ...pluginQuery.configs['flat/recommended'],
+  ...pluginQuery.configs['flat/recommended-strict'],
   {
     files: ['src/**/*.ts', 'src/**/*.tsx'],
     rules: {
diff --git a/examples/react/eslint-plugin-demo/src/prefer-query-options-demo.tsx b/examples/react/eslint-plugin-demo/src/prefer-query-options-demo.tsx
new file mode 100644
index 0000000000..d992faa55a
--- /dev/null
+++ b/examples/react/eslint-plugin-demo/src/prefer-query-options-demo.tsx
@@ -0,0 +1,49 @@
+import { useQuery, useQueryClient } from '@tanstack/react-query'
+import { todoOptions, todosOptions } from './queries'
+
+// ✅ passes: consuming imported queryOptions result directly
+export function Todos() {
+  const query = useQuery(todosOptions)
+  return query.data
+}
+
+// ✅ passes: spreading imported queryOptions result with additional options
+export function Todo({ id }: { id: string }) {
+  const query = useQuery({
+    ...todoOptions(id),
+    select: (data) => data.title,
+  })
+  return query.data
+}
+
+// ✅ passes: referencing queryKey from imported queryOptions result
+export function invalidateTodos() {
+  const queryClient = useQueryClient()
+  queryClient.invalidateQueries({ queryKey: todosOptions.queryKey })
+}
+
+// ❌ fails: inline queryKey + queryFn should use queryOptions()
+export function TodosBad() {
+  const query = useQuery(
+    // eslint-disable-next-line @tanstack/query/prefer-query-options -- Prefer using queryOptions() or infiniteQueryOptions() to co-locate queryKey and queryFn.
+    {
+      queryKey: ['todos'],
+      queryFn: () => fetch('/api/todos').then((r) => r.json()),
+    },
+  )
+  return query.data
+}
+
+// ❌ fails: inline queryKey should reference queryOptions result
+export function InvalidateTodosBad() {
+  const queryClient = useQueryClient()
+  // eslint-disable-next-line @tanstack/query/prefer-query-options -- Prefer referencing a queryKey from a queryOptions() result instead of typing it manually.
+  queryClient.invalidateQueries({ queryKey: ['todos'] })
+}
+
+// ❌ fails: inline queryKey as direct parameter
+export function GetTodosDataBad() {
+  const queryClient = useQueryClient()
+  // eslint-disable-next-line @tanstack/query/prefer-query-options -- Prefer referencing a queryKey from a queryOptions() result instead of typing it manually.
+  return queryClient.getQueryData(['todos'])
+}
diff --git a/examples/react/eslint-plugin-demo/src/queries.tsx b/examples/react/eslint-plugin-demo/src/queries.tsx
new file mode 100644
index 0000000000..46a86734eb
--- /dev/null
+++ b/examples/react/eslint-plugin-demo/src/queries.tsx
@@ -0,0 +1,23 @@
+import { queryOptions } from '@tanstack/react-query'
+
+// ✅ passes: queryKey and queryFn are co-located via queryOptions()
+export const todosOptions = queryOptions({
+  queryKey: ['todos'],
+  queryFn: () => fetchTodos(),
+})
+
+// ✅ passes: factory function wrapping queryOptions()
+export const todoOptions = (id: string) =>
+  queryOptions({
+    queryKey: ['todo', id],
+    queryFn: () => fetchTodo(id),
+  })
+
+function fetchTodos(): Promise<Array<{ id: string; title: string }>> {
+  throw new Error('not implemented')
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+function fetchTodo(id: string): Promise<{ id: string; title: string }> {
+  throw new Error('not implemented')
+}

From c46f6fea037500025c542a7a15043ca5cdf89fef Mon Sep 17 00:00:00 2001
From: Eliya Cohen <co.eliya2@gmail.com>
Date: Tue, 31 Mar 2026 12:26:23 +0300
Subject: [PATCH 6/6] add coverage

---
 .../src/__tests__/prefer-query-options.test.ts       | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/packages/eslint-plugin-query/src/__tests__/prefer-query-options.test.ts b/packages/eslint-plugin-query/src/__tests__/prefer-query-options.test.ts
index 396be9da8b..ff5962623b 100644
--- a/packages/eslint-plugin-query/src/__tests__/prefer-query-options.test.ts
+++ b/packages/eslint-plugin-query/src/__tests__/prefer-query-options.test.ts
@@ -72,6 +72,18 @@ describe('prefer-query-options', () => {
             }
           `,
         },
+        {
+          name: 'useQuery with imported queryOptions function call is allowed',
+          code: normalizeIndent`
+            import { useQuery } from '@tanstack/react-query'
+            import { getFooOptions } from './foo'
+
+            function Component({ id }) {
+              const query = useQuery(getFooOptions(id))
+              return null
+            }
+          `,
+        },
         {
           name: 'useQuery spreading queryOptions result is allowed',
           code: normalizeIndent`
