diff --git a/packages/devtools-api/package.json b/packages/devtools-api/package.json
index 068f0d380..0c150c3cb 100644
--- a/packages/devtools-api/package.json
+++ b/packages/devtools-api/package.json
@@ -25,8 +25,12 @@
     "prepare:type": "tsup --dts-only",
     "stub": "tsup --watch --onSuccess 'tsup --dts-only'"
   },
+  "peerDependencies": {
+    "vue": ">=3.0.0"
+  },
   "dependencies": {
-    "@vue/devtools-kit": "workspace:^"
+    "@vue/devtools-kit": "workspace:^",
+    "@vue/devtools-shared": "workspace:*"
   },
   "publishConfig": {
     "tag": "next"
diff --git a/packages/devtools-api/src/constants.ts b/packages/devtools-api/src/constants.ts
new file mode 100644
index 000000000..41f6614fa
--- /dev/null
+++ b/packages/devtools-api/src/constants.ts
@@ -0,0 +1,8 @@
+/**
+ * - https://vitejs.dev/guide/env-and-mode.html#node-env-and-modes
+ * - https://webpack.js.org/guides/production/#specify-the-mode
+ * - https://www.rspack.dev/config/mode
+ *
+ * Modern bundlers are support NODE_ENV environment variable out-of the box, so we can use it to determine the environment.
+ */
+export const __DEV__ = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'
diff --git a/packages/devtools-api/src/index.ts b/packages/devtools-api/src/index.ts
index f20639d3b..4d5c98deb 100644
--- a/packages/devtools-api/src/index.ts
+++ b/packages/devtools-api/src/index.ts
@@ -13,3 +13,5 @@ export type {
   CustomCommand,
   CustomTab,
 } from '@vue/devtools-kit'
+
+export * from './state'
diff --git a/packages/devtools-api/src/state/index.ts b/packages/devtools-api/src/state/index.ts
new file mode 100644
index 000000000..def32f788
--- /dev/null
+++ b/packages/devtools-api/src/state/index.ts
@@ -0,0 +1,39 @@
+import { getCurrentInstance } from 'vue'
+import { DEVTOOLS_API_INSPECT_STATE_KEY } from '@vue/devtools-shared'
+import { __DEV__ } from '../constants'
+
+/**
+ * Register a setup state on an instance, which will be displayed in the "Component" tab.
+ * This is very useful when using `defineComponent` with setup returning the render function.
+ *
+ * @param state any states you want to see in the vue devtool
+ *
+ * @example
+ * const Component = defineComponent({
+ *   setup() {
+ *     const name = ref('foo')
+ *     inspectSetupState({
+ *       name,
+ *     })
+ *     return h('div', name.value)
+ *   },
+ * })
+ *
+ */
+export const inspectSetupState = __DEV__
+  ? function inspectSetupState(state: Record<string, any>) {
+    const currentInstance = getCurrentInstance()
+    if (!currentInstance) {
+      throw new Error('[Vue Devtools API]: Please using `inspectSetupState()` inside `setup()`.')
+    }
+    // @ts-expect-error internal api
+    currentInstance.devtoolsRawSetupState ??= {}
+    // @ts-expect-error internal api
+    const devtoolsRawSetupState = currentInstance.devtoolsRawSetupState
+    Object.assign(devtoolsRawSetupState, state)
+    devtoolsRawSetupState[DEVTOOLS_API_INSPECT_STATE_KEY] ??= []
+    devtoolsRawSetupState[DEVTOOLS_API_INSPECT_STATE_KEY].push(...Object.keys(state))
+  }
+  : (state: Record<string, any>) => {
+    // do nothing
+    }
diff --git a/packages/devtools-kit/src/core/component/state/process.ts b/packages/devtools-kit/src/core/component/state/process.ts
index aa070ee12..5f3f5bdcd 100644
--- a/packages/devtools-kit/src/core/component/state/process.ts
+++ b/packages/devtools-kit/src/core/component/state/process.ts
@@ -1,4 +1,4 @@
-import { camelize } from '@vue/devtools-shared'
+import { DEVTOOLS_API_INSPECT_STATE_KEY, camelize } from '@vue/devtools-shared'
 import type { VueAppInstance } from '../../../types'
 import type { InspectorState } from '../types'
 import { ensurePropertyExists, returnError } from '../utils'
@@ -132,10 +132,21 @@ function getStateTypeAndName(info: ReturnType<typeof getSetupStateType>) {
 
 function processSetupState(instance: VueAppInstance) {
   const raw = instance.devtoolsRawSetupState || {}
-  return Object.keys(instance.setupState)
+  const customInspectStateKeys = raw[DEVTOOLS_API_INSPECT_STATE_KEY] || []
+
+  // Shallow clone to prevent mutating the original
+  const setupState = {
+    ...instance.setupState,
+    ...customInspectStateKeys.reduce((map, key) => {
+      map[key] = raw[key]
+      return map
+    }, {}),
+  }
+
+  return Object.keys(setupState)
     .filter(key => !vueBuiltins.has(key) && key.split(/(?=[A-Z])/)[0] !== 'use')
     .map((key) => {
-      const value = returnError(() => toRaw(instance.setupState[key])) as unknown as {
+      const value = returnError(() => toRaw(setupState[key])) as unknown as {
         render: Function
         __asyncLoader: Function
 
diff --git a/packages/playground/basic/src/main.ts b/packages/playground/basic/src/main.ts
index 6fa633cdb..9ecdbcd6f 100644
--- a/packages/playground/basic/src/main.ts
+++ b/packages/playground/basic/src/main.ts
@@ -66,6 +66,11 @@ const routes: RouteRecordRaw[] = [
     component: () => import('./pages/IntervalUpdate.vue'),
     name: 'interval-update',
   },
+  {
+    path: '/inspect-custom-state',
+    component: () => import('./pages/InspectCustomState'),
+    name: 'inspect-custom-state',
+  },
 ]
 
 const router = createRouter({
diff --git a/packages/playground/basic/src/pages/InspectCustomState.ts b/packages/playground/basic/src/pages/InspectCustomState.ts
new file mode 100644
index 000000000..690d995fd
--- /dev/null
+++ b/packages/playground/basic/src/pages/InspectCustomState.ts
@@ -0,0 +1,28 @@
+import { defineComponent, h, reactive, ref } from 'vue'
+import { inspectSetupState } from '@vue/devtools-api'
+
+export default defineComponent({
+  name: 'InspectCustomState',
+  setup() {
+    const count = ref(1)
+    const state = reactive({
+      name: 'foo',
+      age: 10,
+    })
+
+    inspectSetupState({
+      count,
+      state,
+    })
+
+    return () => h('div', [
+      h('button', {
+        onClick() {
+          count.value++
+        },
+      }, `count: ${count.value}`),
+      h('div', `name: ${state.name}`),
+      h('div', `age: ${state.age}`),
+    ])
+  },
+})
diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts
index 2a62c99c8..9d38bf6ad 100644
--- a/packages/shared/src/constants.ts
+++ b/packages/shared/src/constants.ts
@@ -2,3 +2,6 @@ export const VIEW_MODE_STORAGE_KEY = '__vue-devtools-view-mode__'
 export const VITE_PLUGIN_DETECTED_STORAGE_KEY = '__vue-devtools-vite-plugin-detected__'
 export const VITE_PLUGIN_CLIENT_URL_STORAGE_KEY = '__vue-devtools-vite-plugin-client-url__'
 export const BROADCAST_CHANNEL_NAME = '__vue-devtools-broadcast-channel__'
+
+// [Devtools API]
+export const DEVTOOLS_API_INSPECT_STATE_KEY = '__vue-devtools-inspect-custom-setup-state'
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8aa8eaada..f07e56e27 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -381,6 +381,12 @@ importers:
       '@vue/devtools-kit':
         specifier: workspace:^
         version: link:../devtools-kit
+      '@vue/devtools-shared':
+        specifier: workspace:*
+        version: link:../shared
+      vue:
+        specifier: '>=3.0.0'
+        version: 3.4.38(typescript@5.5.4)
 
   packages/devtools-kit:
     dependencies: