Proper TypeScript Type Augmentation for Plugins
Rule
When creating Vue plugins that add global properties, you MUST properly augment TypeScript types. The augmentation file MUST contain at least one top-level import or export statement to be treated as a module.
Why This Matters
- Without module syntax, types are overwritten: If your augmentation file isn't a module, it will OVERWRITE Vue's types instead of augmenting them, breaking type checking for the entire application.
- Type safety: Proper augmentation enables autocomplete and type checking for plugin-provided properties.
- IDE support: Developers get proper IntelliSense for global properties like
this.$translate.
- Error prevention: Catch typos and incorrect usage at compile time rather than runtime.
Critical Rule: Module Syntax Required
// BAD - This OVERWRITES Vue types instead of augmenting!
// types/vue.d.ts
declare module 'vue' {
interface ComponentCustomProperties {
$translate: (key: string) => string
}
}
// GOOD - The export {} makes this a module, so it AUGMENTS types
// types/vue.d.ts
export {} // This line is CRITICAL!
declare module 'vue' {
interface ComponentCustomProperties {
$translate: (key: string) => string
}
}Complete Plugin Type Augmentation Example
// plugins/i18n.ts
import type { App, InjectionKey } from 'vue'
export interface I18nOptions {
locale: string
messages: Record<string, Record<string, string>>
}
export interface I18nInstance {
translate: (key: string) => string
locale: string
}
export const i18nInjectionKey: InjectionKey<I18nInstance> = Symbol('i18n')
export function createI18n(options: I18nOptions) {
const i18n: I18nInstance = {
translate(key: string) {
return options.messages[options.locale]?.[key] ?? key
},
locale: options.locale
}
return {
install(app: App) {
// For Composition API
app.provide(i18nInjectionKey, i18n)
// For Options API / templates
app.config.globalProperties.$t = i18n.translate
app.config.globalProperties.$i18n = i18n
}
}
}
// types/i18n.d.ts (or in the same file after export)
export {}
declare module 'vue' {
interface ComponentCustomProperties {
$t: (key: string) => string
$i18n: I18nInstance
}
}Alternative: Augment @vue/runtime-core
Some plugins augment @vue/runtime-core instead of vue:
// types/global.d.ts
export {}
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$myPlugin: MyPluginInstance
}
}Both approaches work, but 'vue' is more common in application code.
Ensure tsconfig.json Includes the Declaration File
{
"compilerOptions": {
// ...
},
"include": [
"src/**/*.ts",
"src/**/*.vue",
"types/**/*.d.ts" // Include your declaration files
]
}For Library Authors: package.json Types Field
If publishing a plugin as a package:
{
"name": "my-vue-plugin",
"types": "./dist/types/index.d.ts",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": "./dist/index.mjs"
}
}
}Common Errors and Solutions
Error: Property '$xyz' does not exist on type
- Check that your
.d.tsfile hasexport {}or an import statement - Verify the file is included in
tsconfig.json - Restart your TypeScript language server (VS Code: Cmd+Shift+P > "Restart TS Server")
Error: Types work in some components but not others
This often happens when using Vetur instead of Volar. If you're on Vue 3, switch to Volar (Vue - Official extension).
Error in Options API but not Composition API
Global properties on this require proper augmentation of ComponentCustomProperties. The Composition API uses inject() which is typed separately.