In Vue 3, how do you make a $formatCurrency helper available in every component template, and what Vue 2 mechanism did that replace?
answer
- a field on app.config
- replaces the Vue 2 prototype
- templates and this
- own properties win
- not in script setup code
basics
~20 sAssign it to app.config.globalProperties.$formatCurrency before mounting. It replaces Vue 2's Vue.prototype: templates and Options API this can read it, but a component's own property of the same name wins and script setup code cannot use this.
solid answer
~30 sIn `main.ts`, set `app.config.globalProperties.$formatCurrency = (n: number) => formatter.format(n)` before `app.mount()`. Every component in that app can then write `{{ $formatCurrency(total) }}` in its template, and Options API code can call `this.$formatCurrency`. This is the Vue 3 replacement for Vue 2's `Vue.prototype`, scoped to one app instead of the whole page. Two limits matter: a component's own property with the same name, such as an Options API method, shadows it, and `<script setup>` code has no `this`, so logic there should import the function directly. Because globals are invisible dependencies, use them sparingly.
code
vue · 12 lines<script setup lang="ts">
import { formatCurrency } from './format' // script logic imports it
const props = defineProps<{ total: number }>()
const summary = `Total: ${formatCurrency(props.total)}`
</script>
<template>
<!-- the template can use the global property -->
<p>{{ $formatCurrency(total) }}</p>
<p>{{ summary }}</p>
</template>go deeper
Recall that app.config.globalProperties replaced Vue.prototype and that you set it in main.ts before mounting.
Explain the proxy lookup order, why own properties shadow globals, and why script setup code cannot reach them through this.
Judge when a global helper is worth its hidden dependency, and how it affects tests, typing and name collisions across libraries.
Set a policy: a short list of template-only globals, everything stateful injected or imported, so dependencies stay visible across teams.
## What app.config.globalProperties is Every Vue 3 application instance, created with `createApp(App)`, exposes a `config` object. One of its fields, `globalProperties`, is a plain object whose keys become readable on **every component instance** in that app. It is the official replacement for Vue 2's `Vue.prototype`, which no longer exists in Vue 3. ```ts import { createApp } from 'vue' import App from './App.vue' const formatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }) const app = createApp(App) app.config.globalProperties.$formatCurrency = (n: number) => formatter.format(n) app.mount('#app') ``` Any template in the app can now write `{{ $formatCurrency(order.total) }}`. ## How lookup works When a template expression or Options API `this` reads a name, Vue's component proxy searches in a fixed order: 1. the component's own state: setup bindings, `data`, props and other context properties; 2. Vue's built-in public properties such as `$el`, `$props` or `$attrs`; 3. finally, `app.config.globalProperties`. So a global property is a **fallback**. If a component defines its own `$formatCurrency`, that one wins silently, and a global property cannot replace a built-in `$` property. ## Vue 2 versus Vue 3 | | Vue 2 `Vue.prototype.$x` | Vue 3 `app.config.globalProperties.$x` | |---|---|---| | Scope | every instance on the page | only components of that one app | | Mechanism | the JavaScript prototype chain | a lookup fallback in the component proxy | | Functions | reached through the prototype, called as methods | returned as-is, not bound to the component | | Script setup | not applicable | not reachable through `this` | The third row has a practical consequence: in Vue 3 a function stored in `globalProperties` should not rely on `this` being the calling component. Write it as a closure over the values it needs. ## Where it does not reach - **`<script setup>` logic.** The block compiles to `setup()`, which has no component `this`. The template of that same component still reaches the global, but script code should simply `import { formatCurrency } from './format'`. - **Composables and stores.** They do not go through the component proxy, so they cannot read globals by name either. - **Other apps on the page.** Each app has its own `config`. ## Rules around app.config - **Mutate fields, never replace the object.** `app.config = { ... }` is ignored, and development builds warn *app.config cannot be replaced. Modify individual options instead.* - **Configure before `mount()`.** Global properties are read at access time, but anything the first render needs should be in place before it runs. - **Type it.** TypeScript cannot see a runtime assignment, so a global property also needs a type declaration; that augmentation step is covered with plugins. ## Global property or provide? Both `globalProperties` and `app.provide()` share a value with the whole app, so interviewers often ask when to pick which: | Need | Better fit | |---|---| | a stateless helper called from many templates | `globalProperties` | | a service with state or configuration | `app.provide()` plus `inject()` | | typed access from `<script setup>` and composables | `app.provide()` with an `InjectionKey` | | Options API code that already uses `this.$x` everywhere | `globalProperties` | A `$formatCurrency` formatter sits at the boundary: it is stateless and template-heavy, so a global property is reasonable, but the same function should also be importable so script code and tests can use it without the app. ## When not to use it Global properties are **implicit dependencies**: a reader of a component cannot tell where `$formatCurrency` comes from, tests must remember to install it, and every library shares the same `$` namespace. They fit a small number of template helpers that are used everywhere. For anything with state or configuration, an imported function or an injected service is easier to trace, type and test. ## Common mistakes - Expecting `this.$formatCurrency` to work inside `<script setup>`. - Relying on `this` inside the global function. - Adding a global with the same name as a component's own property and wondering which one runs. - Assuming a global set on one app is visible in a second app on the same page. - Returning a `$`-prefixed name from `data()` or `setup()` to override a global: Vue reserves the `$` and `_` prefixes and does not expose such keys on the instance (development warns), so the realistic collision is an Options API method of the same name.
- Why does this inside a Vue 3 global property function not point at the component?When a template reads a global property, Vue's proxy returns the stored value as-is; it does not bind functions to the calling instance. The template calls it as a plain function, so `this` is not the component. Write global helpers as closures or pure functions, and pass anything component-specific as an argument.
- How would a component test provide $formatCurrency?The test creates its own app, so it must add the global itself, for example through the test utility's global mounting options, or it must install the same setup the real app uses. Relying on `main.ts` does nothing in a test, which is one of the costs of global properties.
saying these in an interview costs you the question
- Vue 3 still supports Vue.prototype for adding instance properties
- A global property overrides a component's own property of the same name
- Global properties set on one app are visible to every app on the page
- script setup code can call this.$formatCurrency like Options API code
- Replacing app.config with a new object applies the new settings