A Vue plugin installs an application-wide capability through app.use. Its install function can provide a service, register global components or directives, or configure a carefully chosen global property.
Use a plugin for shared startup infrastructure or a distributed library. Keep feature logic in composables and ordinary imports so dependencies remain visible.
A plugin is an object with install(app, options) or the install function itself. Vue passes the current application and app.use options, and installs the same plugin only once per application instance.
The plugin should own one capability and expose the smallest useful contract. It should not become a container for unrelated page state.
| Need | Prefer | Why |
|---|---|---|
| Reusable helper | Module or composable | No application installation is needed. |
| Application service | Plugin plus app.provide | Consumers inject an explicit dependency. |
| Universal component or directive | Plugin registration | One startup registration is intentional. |
| Template convenience | Local import first | Global properties hide dependencies. |
Validate required options before mounting. A Symbol-based InjectionKey prevents key collisions and gives TypeScript consumers an accurate service type.
import type { InjectionKey, Plugin } from "vue";
interface Analytics {
track(event: string): void;
}
export const analyticsKey: InjectionKey<Analytics> =
Symbol("analytics");
export const analyticsPlugin: Plugin<
[{ endpoint: string }]
> = {
install(app, options) {
if (!options.endpoint.startsWith("https://")) {
throw new Error("HTTPS endpoint required.");
}
app.provide(analyticsKey, {
track(event) {
navigator.sendBeacon(
options.endpoint,
JSON.stringify({ event })
);
}
});
}
};
The plugin validates configuration once and provides a narrow typed service instead of exposing mutable options globally.
const app = createApp(App);
app.use(analyticsPlugin, {
endpoint: "https://events.example.test/collect"
});
app.mount("#app");
Every descendant can inject the service during setup because installation happens before mount.
Wrap inject in a composable that rejects a missing provider. Components then receive one typed entry point and a useful error when startup forgot to install the plugin.
export function useAnalytics(): Analytics {
const analytics = inject(analyticsKey);
if (!analytics) {
throw new Error("Analytics plugin is not installed.");
}
return analytics;
}
The composable centralizes the injection key and missing-provider check.
Test consumers with a fake provider and test installation separately. This keeps network effects out of component tests.
For SSR, create a fresh application and plugin-owned service for every request. Mutable module state can leak data between requests.
| Question | Good signal |
|---|---|
| Is the capability used across the app? | Plugin scope may be justified. |
| Can a direct import express it? | Prefer the direct import. |
| Does each SSR request need its own value? | Create it inside install. |
| Can a component test replace it? | Expose a narrow injectable contract. |
Try this next
0 of 2 completed
When setup belongs to the whole application or a reusable library, not one feature component.
Prefer provide for an explicit injectable contract. Use globalProperties only for a truly universal instance property.
Vue prevents the same plugin object from being installed more than once on one application instance.
Explore 500+ free tutorials across 20+ languages and frameworks.