diff --git a/docs/framework/react/devtools.md b/docs/framework/react/devtools.md
index d836367cd0..0208442911 100644
--- a/docs/framework/react/devtools.md
+++ b/docs/framework/react/devtools.md
@@ -7,11 +7,7 @@ Wave your hands in the air and shout hooray because React Query comes with dedic
 
 When you begin your React Query journey, you'll want these devtools by your side. They help visualize all the inner workings of React Query and will likely save you hours of debugging if you find yourself in a pinch!
 
-> Please note that for now, the devtools **do not support React Native**. If you would like to help us make the devtools platform-agnostic, please let us know!
-
-> Exciting News: We now have a separate package for React Native React Query DevTools! This new addition brings native support, allowing you to integrate DevTools directly into your React Native projects. Check it out and contribute here: [react-native-react-query-devtools](https://github.com/LovesWorking/react-native-react-query-devtools)
-
-> An external tool is also available that enables the use of React Query DevTools via an external dashboard. Find out more and contribute on [react-query-external-sync](https://github.com/LovesWorking/react-query-external-sync)
+> For React Native users: A third-party native macOS app is available for debugging React Query in ANY js-based application. Monitor queries across devices in real-time. Check it out here: [rn-better-dev-tools](https://github.com/LovesWorking/rn-better-dev-tools)
 
 > Note that since version 5, the dev tools support observing mutations as well.
 
diff --git a/docs/framework/react/react-native.md b/docs/framework/react/react-native.md
index b920ea54df..6936b0b13a 100644
--- a/docs/framework/react/react-native.md
+++ b/docs/framework/react/react-native.md
@@ -3,15 +3,20 @@ id: react-native
 title: React Native
 ---
 
-React Query is designed to work out of the box with React Native, with the exception of the devtools, which are only supported with React DOM at this time.
+React Query is designed to work out of the box with React Native.
 
-There is a 3rd party [Expo](https://docs.expo.dev/) plugin which you can try: https://github.com/expo/dev-plugins/tree/main/packages/react-query
+## DevTools Support
 
-There is a 3rd party [Flipper](https://fbflipper.com/docs/getting-started/react-native/) plugin which you can try: https://github.com/bgaleotti/react-query-native-devtools
+There are several options available for React Native DevTools integration:
 
-There is a 3rd party [Reactotron](https://github.com/infinitered/reactotron/) plugin which you can try: https://github.com/hsndmr/reactotron-react-query
+1. **Native macOS App**: A 3rd party app for debugging React Query in any js-based application:
+   https://github.com/LovesWorking/rn-better-dev-tools
 
-If you would like to help us make the built-in devtools platform agnostic, please let us know!
+2. **Flipper Plugin**: A 3rd party plugin for Flipper users:
+   https://github.com/bgaleotti/react-query-native-devtools
+
+3. **Reactotron Plugin**: A 3rd party plugin for Reactotron users:
+   https://github.com/hsndmr/reactotron-react-query
 
 ## Online status management
 
diff --git a/packages/query-devtools/src/Devtools.tsx b/packages/query-devtools/src/Devtools.tsx
index 962497275c..29883b77e5 100644
--- a/packages/query-devtools/src/Devtools.tsx
+++ b/packages/query-devtools/src/Devtools.tsx
@@ -120,6 +120,18 @@ export const Devtools: Component<DevtoolsPanelProps> = (props) => {
   const styles = createMemo(() => {
     return theme() === 'dark' ? darkStyles(css) : lightStyles(css)
   })
+  const onlineManager = createMemo(
+    () => useQueryDevtoolsContext().onlineManager,
+  )
+  onMount(() => {
+    const unsubscribe = onlineManager().subscribe((online) => {
+      setOffline(!online)
+    })
+
+    onCleanup(() => {
+      unsubscribe()
+    })
+  })
 
   const pip = usePiPWindow()
 
@@ -922,8 +934,10 @@ export const ContentView: Component<ContentViewProps> = (props) => {
             <button
               onClick={() => {
                 if (selectedView() === 'queries') {
+                  sendDevToolsEvent({ type: 'CLEAR_QUERY_CACHE' })
                   query_cache().clear()
                 } else {
+                  sendDevToolsEvent({ type: 'CLEAR_MUTATION_CACHE' })
                   mutation_cache().clear()
                 }
               }}
@@ -939,13 +953,7 @@ export const ContentView: Component<ContentViewProps> = (props) => {
             </button>
             <button
               onClick={() => {
-                if (offline()) {
-                  onlineManager().setOnline(true)
-                  setOffline(false)
-                } else {
-                  onlineManager().setOnline(false)
-                  setOffline(true)
-                }
+                onlineManager().setOnline(!onlineManager().isOnline())
               }}
               class={cx(
                 styles().actionsBtn,
@@ -1768,29 +1776,43 @@ const QueryDetails = () => {
   const color = createMemo(() => getQueryStatusColorByLabel(statusLabel()))
 
   const handleRefetch = () => {
+    sendDevToolsEvent({ type: 'REFETCH', queryHash: activeQuery()?.queryHash })
     const promise = activeQuery()?.fetch()
     promise?.catch(() => {})
   }
 
   const triggerError = (errorType?: DevtoolsErrorType) => {
+    const activeQueryVal = activeQuery()
+    if (!activeQueryVal) return
+    sendDevToolsEvent({
+      type: 'TRIGGER_ERROR',
+      queryHash: activeQueryVal.queryHash,
+      metadata: { error: errorType?.name },
+    })
     const error =
-      errorType?.initializer(activeQuery()!) ??
+      errorType?.initializer(activeQueryVal) ??
       new Error('Unknown error from devtools')
 
-    const __previousQueryOptions = activeQuery()!.options
+    const __previousQueryOptions = activeQueryVal.options
 
-    activeQuery()!.setState({
+    activeQueryVal.setState({
       status: 'error',
       error,
       fetchMeta: {
-        ...activeQuery()!.state.fetchMeta,
+        ...activeQueryVal.state.fetchMeta,
         __previousQueryOptions,
       } as any,
     } as QueryState<unknown, Error>)
   }
 
   const restoreQueryAfterLoadingOrError = () => {
-    const activeQueryVal = activeQuery()!
+    const activeQueryVal = activeQuery()
+    if (!activeQueryVal) return
+
+    sendDevToolsEvent({
+      type: 'RESTORE_LOADING',
+      queryHash: activeQueryVal.queryHash,
+    })
     const previousState = activeQueryVal.state
     const previousOptions = activeQueryVal.state.fetchMeta
       ? (activeQueryVal.state.fetchMeta as any).__previousQueryOptions
@@ -1899,7 +1921,13 @@ const QueryDetails = () => {
               'tsqd-query-details-actions-btn',
               'tsqd-query-details-action-invalidate',
             )}
-            onClick={() => queryClient.invalidateQueries(activeQuery())}
+            onClick={() => {
+              sendDevToolsEvent({
+                type: 'INVALIDATE',
+                queryHash: activeQuery()?.queryHash,
+              })
+              queryClient.invalidateQueries(activeQuery())
+            }}
             disabled={queryStatus() === 'pending'}
           >
             <span
@@ -1917,7 +1945,13 @@ const QueryDetails = () => {
               'tsqd-query-details-actions-btn',
               'tsqd-query-details-action-reset',
             )}
-            onClick={() => queryClient.resetQueries(activeQuery())}
+            onClick={() => {
+              sendDevToolsEvent({
+                type: 'RESET',
+                queryHash: activeQuery()?.queryHash,
+              })
+              queryClient.resetQueries(activeQuery())
+            }}
             disabled={queryStatus() === 'pending'}
           >
             <span
@@ -1936,6 +1970,10 @@ const QueryDetails = () => {
               'tsqd-query-details-action-remove',
             )}
             onClick={() => {
+              sendDevToolsEvent({
+                type: 'REMOVE',
+                queryHash: activeQuery()?.queryHash,
+              })
               queryClient.removeQueries(activeQuery())
               setSelectedQueryHash(null)
             }}
@@ -1964,6 +2002,10 @@ const QueryDetails = () => {
               } else {
                 const activeQueryVal = activeQuery()
                 if (!activeQueryVal) return
+                sendDevToolsEvent({
+                  type: 'TRIGGER_LOADING',
+                  queryHash: activeQueryVal.queryHash,
+                })
                 const __previousQueryOptions = activeQueryVal.options
                 // Trigger a fetch in order to trigger suspense as well.
                 activeQueryVal.fetch({
@@ -2006,6 +2048,10 @@ const QueryDetails = () => {
                 if (!activeQuery()!.state.error) {
                   triggerError()
                 } else {
+                  sendDevToolsEvent({
+                    type: 'RESTORE_ERROR',
+                    queryHash: activeQuery()?.queryHash,
+                  })
                   queryClient.resetQueries(activeQuery())
                 }
               }}
@@ -2438,6 +2484,37 @@ const createSubscribeToMutationCacheBatcher = <T,>(
   return value
 }
 
+type DevToolsActionType =
+  | 'REFETCH'
+  | 'INVALIDATE'
+  | 'RESET'
+  | 'REMOVE'
+  | 'TRIGGER_ERROR'
+  | 'RESTORE_ERROR'
+  | 'TRIGGER_LOADING'
+  | 'RESTORE_LOADING'
+  | 'CLEAR_MUTATION_CACHE'
+  | 'CLEAR_QUERY_CACHE'
+
+const DEV_TOOLS_EVENT = '@tanstack/query-devtools-event'
+
+const sendDevToolsEvent = ({
+  type,
+  queryHash,
+  metadata,
+}: {
+  type: DevToolsActionType
+  queryHash?: string
+  metadata?: Record<string, unknown>
+}) => {
+  const event = new CustomEvent(DEV_TOOLS_EVENT, {
+    detail: { type, queryHash, metadata },
+    bubbles: true,
+    cancelable: true,
+  })
+  window.dispatchEvent(event)
+}
+
 const stylesFactory = (
   theme: 'light' | 'dark',
   css: (typeof goober)['css'],