getCurrentInstance() returns the internal component instance during setup(). It exposes internals like $el, $parent, $refs, $emit, the component's proxy, and the entire internal state. It's an escape hatch for library authors, not application code. The Vue team explicitly marks it as internal API, meaning it can change between minor versions without warning. If you use it in your app, you're coupling to implementation details that may break on any Vue update.
What it returns
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance()
// instance.proxy — the public interface (like `this` in Options API)
// instance.type — the component options/definition
// instance.parent — parent component instance
// instance.appContext — app-level context (plugins, directives, provides)
// instance.emit — the emit function
// instance.props — current propsIt only works inside setup() or lifecycle hooks. Calling it in a setTimeout, Promise.then, or any async callback after setup completes returns null:
onMounted(() => {
const instance = getCurrentInstance() // works
setTimeout(() => {
const instance = getCurrentInstance() // null
}, 1000)
})Why people reach for it
Most uses fall into three categories, all of which have proper alternatives:
1. Accessing the component's DOM element
// BAD: using getCurrentInstance
const instance = getCurrentInstance()
const el = instance?.proxy?.$el
// GOOD: template ref
const elementRef = ref<HTMLElement>()
onMounted(() => {
elementRef.value // the DOM element
})2. Accessing app-level context (router, i18n, custom plugins)
// BAD: digging through appContext
const instance = getCurrentInstance()
const router = instance?.appContext.config.globalProperties.$router
// GOOD: use the composable the library provides
const router = useRouter()
const i18n = useI18n()3. Accessing parent/child instances
// BAD: traversing the component tree
const instance = getCurrentInstance()
const parentData = instance?.parent?.proxy?.someData
// GOOD: props, emit, provide/inject
const someData = inject<string>('someData')When library authors use it legitimately
Libraries like VueUse, Vue Router, and Pinia use getCurrentInstance internally because they need to:
- Hook into the component lifecycle from outside
<script setup> - Access the app context to register plugins
- Read the component tree for devtools integration
For example, useRouter() internally calls getCurrentInstance() to access the router instance that was installed via app.use(router). But the library wraps this in a stable public API so your code never touches the instance directly.
The risks
No type safety: the internal instance type is not part of Vue's public TypeScript API. You'll be casting to
anyeverywhere.Breaks on updates: Vue's internal component structure has changed between 3.0, 3.2, 3.3, and 3.4. Code that accessed
instance.setupStateorinstance.ctxhas broken across versions.Doesn't work in async contexts: if your composable does anything async,
getCurrentInstance()returnsnullafter the firstawait, leading to intermittent bugs.SSR differences: the instance structure differs between client and server rendering, so code that works in the browser may crash during SSR.
Summary
| Need | Use instead |
|---|---|
| DOM element | Template ref |
| Router, i18n, custom plugins | Their composables (useRouter, useI18n) |
| Parent data | inject() |
| Emit from composable | Pass emit as a parameter |
| App context in a library | getCurrentInstance() is acceptable (wrap it) |
| App code | Never use getCurrentInstance() |
See also: What is Provide/Inject? · How do template refs work?
References
- getCurrentInstance - Vue.js docs
- provide - Vue.js docs
- Template Refs - Vue.js docs