Skip to content
← All questions
Intermediate

What is defineExpose and when is it needed?

Composition APIComponents

Components using <script setup> are closed by default. A parent that grabs a template ref to a child component gets an empty object unless the child explicitly exposes properties with defineExpose. This is a deliberate change from Options API, where this.$refs.child gave full access to the entire instance.

The problem

vue
<!-- Counter.vue -->
<script setup>
import { ref } from 'vue'

const count = ref(0)
function reset() { count.value = 0 }
</script>

<template>
  <span>{{ count }}</span>
</template>
vue
<!-- Parent.vue -->
<script setup>
import { useTemplateRef, onMounted } from 'vue'
const counterRef = useTemplateRef('counter')

onMounted(() => {
  console.log(counterRef.value.count) // undefined
  counterRef.value.reset()            // TypeError: not a function
})
</script>

<template>
  <Counter ref="counter" />
</template>

The parent sees {} because nothing was exposed.

The fix

vue
<!-- Counter.vue -->
<script setup>
import { ref } from 'vue'

const count = ref(0)
const internalState = ref('private')

function reset() { count.value = 0 }

defineExpose({ count, reset })
// internalState stays private
</script>

Now the parent can access count and reset(), but not internalState. You control the public API explicitly.

Common use case: wrapping native elements

Input wrappers often expose imperative methods like focus and blur:

vue
<!-- BaseInput.vue -->
<script setup>
import { ref } from 'vue'

const inputEl = ref<HTMLInputElement | null>(null)

defineExpose({
  focus: () => inputEl.value?.focus(),
  blur: () => inputEl.value?.blur()
})
</script>

<template>
  <input ref="inputEl" v-bind="$attrs" />
</template>
vue
<!-- Parent.vue -->
<script setup>
import { useTemplateRef } from 'vue'

const input = useTemplateRef('search')

function openSearch() {
  input.value?.focus()
}
</script>

<template>
  <BaseInput ref="search" placeholder="Search..." />
  <button @click="openSearch">Search</button>
</template>

When to use defineExpose

SituationUse defineExpose?
Parent needs to call imperative methods (focus, reset, validate)Yes
Parent needs to read child state for coordinationYes, but consider if props/emit is better
Normal parent-child data flowNo, use props and emit
Form library needs to call validate() on child inputsYes

Component refs create tight coupling. Prefer props and emit for data flow, and reserve defineExpose for genuinely imperative actions that don't fit a declarative pattern.

See also: How do template refs work? · What are all the compiler macros in Vue?

References

Released under the MIT License.