diff --git a/docs/framework/react/reference/mutationOptions.md b/docs/framework/react/reference/mutationOptions.md
new file mode 100644
index 0000000000..0fa145a890
--- /dev/null
+++ b/docs/framework/react/reference/mutationOptions.md
@@ -0,0 +1,15 @@
+---
+id: mutationOptions
+title: mutationOptions
+---
+
+```tsx
+mutationOptions({
+  mutationFn,
+  ...options,
+})
+```
+
+**Options**
+
+You can generally pass everything to `mutationOptions` that you can also pass to [`useMutation`](./useMutation.md).
diff --git a/docs/framework/react/typescript.md b/docs/framework/react/typescript.md
index baac2cc771..74a07c8101 100644
--- a/docs/framework/react/typescript.md
+++ b/docs/framework/react/typescript.md
@@ -239,6 +239,24 @@ const data = queryClient.getQueryData<Group[]>(['groups'])
 [//]: # 'TypingQueryOptions'
 [//]: # 'Materials'
 
+## Typing Mutation Options
+
+Similarly to `queryOptions`, you can use `mutationOptions` to extract mutation options into a separate function:
+
+```ts
+function useGroupPostMutation() {
+  const queryClient = useQueryClient()
+
+  return mutationOptions({
+    mutationKey: ['groups'],
+    mutationFn: executeGroups,
+    onSuccess: () => {
+      queryClient.invalidateQueries({ queryKey: ['posts'] })
+    },
+  })
+}
+```
+
 ## Further Reading
 
 For tips and tricks around type inference, have a look at [React Query and TypeScript](./community/tkdodos-blog.md#6-react-query-and-typescript) from
diff --git a/packages/react-query/src/__tests__/mutationOptions.test-d.tsx b/packages/react-query/src/__tests__/mutationOptions.test-d.tsx
new file mode 100644
index 0000000000..e16d3ac302
--- /dev/null
+++ b/packages/react-query/src/__tests__/mutationOptions.test-d.tsx
@@ -0,0 +1,51 @@
+import { describe, expectTypeOf, it } from 'vitest'
+import { dataTagSymbol } from '@tanstack/query-core'
+import { mutationOptions } from '../mutationOptions'
+
+describe('mutationOptions', () => {
+  it('should not allow excess properties', () => {
+    return mutationOptions({
+      mutationFn: () => Promise.resolve(5),
+      mutationKey: ['key'],
+      // @ts-expect-error this is a good error, because onMutates does not exist!
+      onMutates: 1000,
+    })
+  })
+
+  it('should infer types for callbacks', () => {
+    return mutationOptions({
+      mutationFn: () => Promise.resolve(5),
+      mutationKey: ['key'],
+      onSuccess: (data) => {
+        expectTypeOf(data).toEqualTypeOf<number>()
+      },
+    })
+  })
+
+  it('should tag the mutationKey with the result type of the MutationFn', () => {
+    const { mutationKey } = mutationOptions({
+      mutationKey: ['key'],
+      mutationFn: () => Promise.resolve(5),
+    })
+
+    expectTypeOf(mutationKey[dataTagSymbol]).toEqualTypeOf<number>()
+  })
+
+  it('should tag the mutationKey with unknown if there is no mutationFn', () => {
+    const { mutationKey } = mutationOptions({
+      mutationKey: ['key'],
+    })
+
+    expectTypeOf(mutationKey[dataTagSymbol]).toEqualTypeOf<unknown>()
+  })
+
+  it('should tag the mutationKey with the result type of the MutationFn if onSuccess is used', () => {
+    const { mutationKey } = mutationOptions({
+      mutationKey: ['key'],
+      mutationFn: () => Promise.resolve(5),
+      onSuccess: () => {},
+    })
+
+    expectTypeOf(mutationKey[dataTagSymbol]).toEqualTypeOf<number>()
+  })
+})
diff --git a/packages/react-query/src/__tests__/mutationOptions.test.tsx b/packages/react-query/src/__tests__/mutationOptions.test.tsx
new file mode 100644
index 0000000000..cea9683491
--- /dev/null
+++ b/packages/react-query/src/__tests__/mutationOptions.test.tsx
@@ -0,0 +1,14 @@
+import { describe, expect, it } from 'vitest'
+import { mutationOptions } from '../mutationOptions'
+import type { UseMutationOptions } from '../types'
+
+describe('mutationOptions', () => {
+  it('should return the object received as a parameter without any modification.', () => {
+    const object: UseMutationOptions = {
+      mutationKey: ['key'],
+      mutationFn: () => Promise.resolve(5),
+    } as const
+
+    expect(mutationOptions(object)).toStrictEqual(object)
+  })
+})
diff --git a/packages/react-query/src/mutationOptions.ts b/packages/react-query/src/mutationOptions.ts
new file mode 100644
index 0000000000..a37c9d745a
--- /dev/null
+++ b/packages/react-query/src/mutationOptions.ts
@@ -0,0 +1,105 @@
+import type {
+  DataTag,
+  DefaultError,
+  InitialDataFunction,
+  MutationFunction,
+  OmitKeyof,
+  SkipToken,
+} from '@tanstack/query-core'
+import type { UseMutationOptions } from './types'
+
+export type UndefinedInitialDataOptions<
+  TMutationFnData = unknown,
+  TError = DefaultError,
+  TData = void,
+  TMutationKey = unknown,
+> = UseMutationOptions<TMutationFnData, TError, TData, TMutationKey> & {
+  initialData?:
+    | undefined
+    | InitialDataFunction<NonUndefinedGuard<TMutationFnData>>
+    | NonUndefinedGuard<TMutationFnData>
+}
+
+export type UnusedSkipTokenOptions<
+  TMutationFnData = unknown,
+  TError = DefaultError,
+  TData = void,
+  TMutationKey = unknown,
+> = OmitKeyof<
+  UseMutationOptions<TMutationFnData, TError, TData, TMutationKey>,
+  'mutationFn'
+> & {
+  mutationFn?: Exclude<
+    UseMutationOptions<
+      TMutationFnData,
+      TError,
+      TData,
+      TMutationKey
+    >['mutationFn'],
+    SkipToken | undefined
+  >
+}
+
+type NonUndefinedGuard<T> = T extends undefined ? never : T
+
+export type DefinedInitialDataOptions<
+  TMutationFnData = unknown,
+  TError = DefaultError,
+  TData = void,
+  TMutationKey = unknown,
+> = Omit<
+  UseMutationOptions<TMutationFnData, TError, TData, TMutationKey>,
+  'mutationFn'
+> & {
+  initialData:
+    | NonUndefinedGuard<TMutationFnData>
+    | (() => NonUndefinedGuard<TMutationFnData>)
+  mutationFn?: MutationFunction<TMutationFnData, TMutationKey>
+}
+
+export function mutationOptions<
+  TMutationFnData = unknown,
+  TError = DefaultError,
+  TData = void,
+  TMutationKey = unknown,
+>(
+  options: DefinedInitialDataOptions<
+    TMutationFnData,
+    TError,
+    TData,
+    TMutationKey
+  >,
+): DefinedInitialDataOptions<TMutationFnData, TError, TData, TMutationKey> & {
+  mutationKey: DataTag<TMutationKey, TMutationFnData, TError>
+}
+
+export function mutationOptions<
+  TMutationFnData = unknown,
+  TError = DefaultError,
+  TData = void,
+  TMutationKey = unknown,
+>(
+  options: UnusedSkipTokenOptions<TMutationFnData, TError, TData, TMutationKey>,
+): UnusedSkipTokenOptions<TMutationFnData, TError, TData, TMutationKey> & {
+  mutationKey: DataTag<TMutationKey, TMutationFnData, TError>
+}
+
+export function mutationOptions<
+  TMutationFnData = unknown,
+  TError = DefaultError,
+  TData = void,
+  TMutationKey = unknown,
+>(
+  options: UndefinedInitialDataOptions<
+    TMutationFnData,
+    TError,
+    TData,
+    TMutationKey
+  >,
+): UndefinedInitialDataOptions<TMutationFnData, TError, TData, TMutationKey> & {
+  mutationKey: DataTag<TMutationKey, TMutationFnData, TError>
+}
+
+export function mutationOptions(options: unknown) {
+  return options
+}