Vue Provide Inject Patterns in Vue 3: A Complete Guide to Dependency Injection

Vue 3's provide/inject API eliminates prop drilling by enabling ancestor components to expose values that any descendant can retrieve through a prototype-based lookup chain, creating a lightweight dependency injection system that preserves reactivity across component trees.

The vuejs/core repository implements a sophisticated dependency injection mechanism that allows developers to share state and services between components without explicitly passing props through every intermediate layer. This pattern fundamentally changes how large Vue 3 applications manage cross-cutting concerns while maintaining clean component APIs.

How Provide/Inject Works Under the Hood

Prototype-Based Value Resolution

The core mechanism relies on JavaScript's prototype chain for efficient lookups. In packages/runtime-core/src/component.ts, each component instance creates a provides object that inherits from its parent's provides (lines 36-37). When inject() is called, it traverses this chain upward until it finds the requested key.

In packages/runtime-core/src/apiInject.ts, the provide() function registers values on the current instance's provides object (lines 24-30), while inject() performs the lookup by checking the parent component's provides through the prototype chain.

Key Benefits of Vue Provide Inject Patterns

Eliminates Prop Drilling

Traditional Vue applications require passing props through every intermediate component to reach deeply nested children. The provide/inject pattern removes this boilerplate by allowing direct ancestor-to-descendant communication without intermediate components knowing about the data. Only the provider and consumer need to coordinate on the injection key.

Hierarchical Scope with Automatic Fallback

The prototype-based implementation in packages/runtime-core/src/component.ts creates a natural shadowing mechanism. If a child component provides the same key as an ancestor, descendants receive the child's value while still maintaining access to parent values through the prototype chain. This enables scoped overrides without breaking the injection chain or requiring explicit merging logic.

Type Safety with InjectionKey

Vue 3 exports an InjectionKey<T> type from packages/runtime-core/src/apiInject.ts. This symbol-based type allows TypeScript to infer the correct type when injecting, preventing runtime type mismatches and enabling IDE autocomplete support. Using symbols instead of strings also eliminates key collision risks across different libraries or application modules.

Global Service Registration

The pattern extends beyond component trees. In packages/runtime-core/src/apiInject.ts (lines 64-70), inject() falls back to the current app's provides object. This allows global services to be registered once with app.provide() and accessed anywhere, including functional components outside the standard component hierarchy.

SSR and Custom Element Support

The implementation supports server-side rendering through packages/server-renderer/src/renderToString.ts (lines 81-84), which injects the SSR context via the same provide mechanism. Similarly, packages/runtime-dom/src/apiCustomElement.ts (lines 326-331) enables custom elements to participate in the provide/inject hierarchy, allowing web components to access Vue app-level dependencies.

Practical Implementation Examples

Basic Composition API Usage

// Provider.vue
import { provide, ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    provide('counter', count)
    return { count }
  }
}
// Consumer.vue
import { inject } from 'vue'

export default {
  setup() {
    const count = inject('counter')
    if (!count) {
      throw new Error('count not provided')
    }
    return { count }
  }
}

Type-Safe Injection with Symbols

// keys.ts
import type { InjectionKey } from 'vue'

export const UserKey: InjectionKey<{ name: string; id: number }> = Symbol()
// Provider.vue
import { provide } from 'vue'
import { UserKey } from './keys'

provide(UserKey, { name: 'Alice', id: 1 })
// Consumer.vue
import { inject } from 'vue'
import { UserKey } from './keys'

const user = inject(UserKey)
// TypeScript infers: { name: string; id: number } | undefined

Options API Integration

export default {
  data() {
    return { message: 'hello' }
  },
  provide() {
    // Function form allows access to `this`
    return {
      greeting: this.message
    }
  }
}

During initialization, Vue calls this function and registers each key, as implemented in packages/runtime-core/src/componentOptions.ts (lines 702-708).

Global App-Level Providers

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

const app = createApp(App)
app.provide('apiClient', myAxiosInstance)
app.mount('#app')
// AnyComponent.vue
import { inject } from 'vue'

export default {
  setup() {
    const apiClient = inject('apiClient')
    // Use without importing or prop drilling
    return {}
  }
}

SSR Context Injection

// server entry
import { renderToString } from '@vue/server-renderer'
import { createApp } from 'vue'

const app = createApp(App)
await renderToString(app, { url: req.url })
// Component.vue
import { inject } from 'vue'
import { ssrContextKey } from '@vue/server-renderer'

const ssrContext = inject(ssrContextKey)

The SSR context is automatically provided via app.provide(ssrContextKey, context) in packages/server-renderer/src/renderToString.ts (lines 81-84).

Summary

Frequently Asked Questions

What is the difference between props and provide/inject in Vue 3?

Props create explicit parent-child contracts where data flows down through every intermediate component, requiring each layer to declare and forward the prop. Provide/inject establishes an implicit dependency injection system where only the provider and consumer need to know about the value, eliminating intermediate boilerplate while maintaining reactive connections through the prototype chain.

Can provide/inject be used with the Options API?

Yes, the Options API supports provide/inject through the provide and inject component options. The provide option can be an object or a function that returns an object, allowing access to this when using the function form. The inject option accepts an array of strings or an object mapping for aliasing and default values, as processed in packages/runtime-core/src/componentOptions.ts (lines 702-708).

Is provide/inject reactive in Vue 3?

Provide/inject itself is not inherently reactive, but it preserves the reactivity of whatever value you provide. If you provide a ref or reactive object, the injected value remains the same reactive proxy, allowing descendants to react to changes. However, replacing the entire provided value requires calling provide() again with a new reference, as the injection establishes the connection during component initialization.

How does provide/inject handle server-side rendering in Vue 3?

During SSR, Vue injects the server context using the same provide/inject mechanism via app.provide(ssrContextKey, context) in packages/server-renderer/src/renderToString.ts (lines 81-84). This allows components to access request-specific data like headers, cookies, or the current URL by injecting the SSR context key, ensuring isomorphic behavior between client and server environments without conditional logic.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →