diff --git a/packages/runtime-core/__tests__/debug.spec.ts b/packages/runtime-core/__tests__/debug.spec.ts
new file mode 100644
index 000000000..3a4c4b56d
--- /dev/null
+++ b/packages/runtime-core/__tests__/debug.spec.ts
@@ -0,0 +1,65 @@
+import { debug } from '../src/debug'
+import {
+ type ComponentInternalInstance,
+ defineComponent,
+ getCurrentInstance,
+ h,
+ serializeInner as inner,
+ nodeOps,
+ ref,
+ render,
+} from '@vue/runtime-test'
+
+describe('debug', () => {
+ test('watching states', () => {
+ const root = nodeOps.createElement('div')
+ let instance: ComponentInternalInstance
+ const Comp = defineComponent({
+ setup() {
+ const name = ref('foo')
+ debug({
+ name,
+ })
+ return () => h('div', name.value)
+ },
+ mounted() {
+ instance = getCurrentInstance()!
+ },
+ })
+ render(h(Comp), root)
+ expect(inner(root)).toBe('
foo
')
+ expect(instance!.setupState).toEqual({
+ name: 'foo',
+ })
+ })
+ test('watching states with calling the debug function multiple times', () => {
+ const root = nodeOps.createElement('div')
+ let instance: ComponentInternalInstance
+ const Comp = defineComponent({
+ setup() {
+ const name = ref('foo')
+ const age = ref(100)
+ debug({
+ name,
+ })
+ debug({
+ age,
+ name,
+ })
+ debug({
+ name,
+ })
+ return () => h('div', name.value)
+ },
+ mounted() {
+ instance = getCurrentInstance()!
+ },
+ })
+ render(h(Comp), root)
+ expect(inner(root)).toBe('foo
')
+ expect(instance!.setupState).toEqual({
+ name: 'foo',
+ age: 100,
+ })
+ })
+})
diff --git a/packages/runtime-core/src/debug.ts b/packages/runtime-core/src/debug.ts
new file mode 100644
index 000000000..8ce522694
--- /dev/null
+++ b/packages/runtime-core/src/debug.ts
@@ -0,0 +1,30 @@
+import { reactive } from '@vue/reactivity'
+import { getCurrentInstance } from './component'
+
+/**
+ * this debug function is a helper for watching states in the vue devtool (it runs only in dev mode)
+ * @example
+ * const Component = defineComponent({
+ * setup() {
+ * const name = ref('foo')
+ * debug({
+ * // watch states in the vue devtool
+ * name,
+ * })
+ * return h('div', name.value)
+ * },
+ * })
+ * @param states any states you want to see in the vue devtool
+ */
+export const debug = __DEV__
+ ? (states: Record) => {
+ const instance = getCurrentInstance()
+ if (instance) {
+ instance.setupState = reactive(
+ Object.assign({}, states, instance.setupState),
+ )
+ }
+ }
+ : (states: Record) => {
+ // empty
+ }
diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts
index 9910f8210..f149dfec5 100644
--- a/packages/runtime-core/src/index.ts
+++ b/packages/runtime-core/src/index.ts
@@ -338,6 +338,7 @@ export type {
AsyncComponentOptions,
AsyncComponentLoader,
} from './apiAsyncComponent'
+export { debug } from './debug'
export type {
HydrationStrategy,
HydrationStrategyFactory,