Fix: provide() Can Only Be Used Inside setup() in Vue.js

Error message:
provide() can only be used inside setup().
Composition API 2025-01-25

What Causes This Error?

This error occurs when you call provide() outside of a component’s setup() function. Like inject(), the provide() function requires an active component instance to register the provided value.

The Problem

import { provide } from 'vue'

// ❌ Called outside setup
provide('theme', { color: 'dark' }) // Error!

export default {
  setup() {
    // ...
  }
}

The Fix

Call provide() Inside setup()

import { provide, reactive } from 'vue'

export default {
  setup() {
    // ✅ Called inside setup
    const theme = reactive({
      color: 'dark',
      toggle() {
        this.color = this.color === 'dark' ? 'light' : 'dark'
      }
    })

    provide('theme', theme)
  }
}

With Script Setup

<script setup>
import { provide, reactive } from 'vue'

// ✅ Top-level in script setup works
const theme = reactive({
  color: 'dark',
  toggle() {
    this.color = this.color === 'dark' ? 'light' : 'dark'
  }
})

provide('theme', theme)
</script>

Common Scenarios

App-Level Provide

// main.js
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// ✅ App-level provide (different from component provide)
app.provide('globalConfig', {
  apiUrl: 'https://api.example.com',
  debug: true
})

app.mount('#app')

Creating a Provider Component

<!-- ThemeProvider.vue -->
<script setup>
import { provide, reactive } from 'vue'

const theme = reactive({
  color: 'dark',
  fontSize: 16,
  setColor(color) {
    this.color = color
  }
})

// ✅ Provide in setup
provide('theme', theme)
</script>

<template>
  <slot />
</template>
<!-- App.vue -->
<template>
  <ThemeProvider>
    <RouterView />
  </ThemeProvider>
</template>

Provide with Readonly

<script setup>
import { provide, reactive, readonly } from 'vue'

const state = reactive({
  count: 0
})

// ✅ Provide readonly to prevent child mutations
provide('state', readonly(state))

// Provide methods separately for controlled updates
provide('increment', () => state.count++)
</script>

Provide Composable

// useProvideTheme.js
import { provide, reactive, inject } from 'vue'

const ThemeSymbol = Symbol('theme')

export function provideTheme() {
  // Must be called in setup()
  const theme = reactive({
    mode: 'light',
    toggle() {
      this.mode = this.mode === 'light' ? 'dark' : 'light'
    }
  })

  provide(ThemeSymbol, theme)

  return theme
}

export function useTheme() {
  // Must be called in setup()
  const theme = inject(ThemeSymbol)

  if (!theme) {
    throw new Error('useTheme requires provideTheme in ancestor')
  }

  return theme
}
<!-- Root component -->
<script setup>
import { provideTheme } from './useProvideTheme'

provideTheme() // ✅ Called in setup
</script>

<!-- Child component -->
<script setup>
import { useTheme } from './useProvideTheme'

const theme = useTheme() // ✅ Called in setup
</script>

Using Symbols for Keys

// keys.js
export const ThemeKey = Symbol('theme')
export const UserKey = Symbol('user')
export const ApiKey = Symbol('api')

// Provider component
import { ThemeKey, UserKey } from './keys'

provide(ThemeKey, theme)
provide(UserKey, user)

// Consumer component
const theme = inject(ThemeKey)
const user = inject(UserKey)

Quick Checklist

  • Call provide() inside setup() or <script setup>
  • Use app.provide() for app-level globals
  • Consider using Symbols as keys to avoid collisions
  • Use readonly() to prevent accidental mutations
  • Create provider components for complex state
  • Document that composables with provide() must be called in setup