Skip to content
← All questions
Intermediate

How does the Vue plugin system work?

Architecture

A plugin is a way to add app-level functionality to Vue. You install it with app.use(), and inside the plugin's install function you can register global components, directives, provide values for injection, or add global properties.

Plugin structure

A plugin is either an object with an install method or a plain function:

ts
import type { App } from 'vue'

// Object form
const myPlugin = {
  install(app: App, options?: { debug: boolean }) {
    // register things here
  }
}

// Function form
function myPlugin(app: App, options?: { debug: boolean }) {
  // register things here
}

// Usage
app.use(myPlugin, { debug: true })

What you can do inside install

ts
import type { App } from 'vue'

const uiPlugin = {
  install(app: App) {
    // Global component (available in all templates)
    app.component('AppButton', AppButton)

    // Global directive
    app.directive('focus', { mounted: (el) => el.focus() })

    // Provide a service for inject()
    app.provide(httpKey, axiosInstance)

    // Global property (available as this.$http in Options API)
    app.config.globalProperties.$http = axiosInstance
  }
}

Typed injection keys

String keys like 'http' can collide between plugins. Use InjectionKey<T> symbols for type safety and uniqueness:

ts
import type { InjectionKey } from 'vue'
import type { AxiosInstance } from 'axios'

export const httpKey: InjectionKey<AxiosInstance> = Symbol('http')
export const configKey: InjectionKey<AppConfig> = Symbol('appConfig')

const apiPlugin = {
  install(app: App, options: AppConfig) {
    app.provide(httpKey, axios.create({ baseURL: options.apiUrl }))
    app.provide(configKey, options)
  }
}

Consuming plugin values with a composable

Wrap inject in a composable that throws a clear error if the plugin wasn't installed:

ts
import { inject } from 'vue'
import { httpKey } from '@/plugins/api'

export function useHttp() {
  const http = inject(httpKey)
  if (!http) {
    throw new Error('API plugin not installed. Did you forget app.use(apiPlugin)?')
  }
  return http
}

Components use the composable without knowing about the injection key:

vue
<script setup>
const http = useHttp()

const { data } = await http.get('/users')
</script>

Plugin with options (typed)

ts
import type { App, Plugin } from 'vue'

interface I18nOptions {
  locale: string
  messages: Record<string, Record<string, string>>
}

const i18nPlugin: Plugin<[I18nOptions]> = {
  install(app: App, options: I18nOptions) {
    const t = (key: string) => options.messages[options.locale]?.[key] ?? key

    app.provide(i18nKey, { t, locale: options.locale })
    app.config.globalProperties.$t = t
  }
}

// main.ts
app.use(i18nPlugin, {
  locale: 'en',
  messages: {
    en: { greeting: 'Hello' },
    es: { greeting: 'Hola' }
  }
})

When to write a plugin vs a composable

NeedUse
App-wide setup that runs once at startupPlugin
Global components, directives, or propertiesPlugin
Reusable logic consumed by individual componentsComposable
Third-party library integration (analytics, i18n, HTTP)Plugin that provides, composable that consumes

Released under the MIT License.