Skip to main content
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

createPlugin

Edit this page
Report a Bug
Open issues
View on GitHub
Copy Markdown

StableAdvancedAug 28, 2026

Register app-wide plugins with typed context any component can consume. Prefer createPluginContext unless you need to control provide and setup yourself.

Usage

For most cases, use createPluginContext — it generates the full plugin tuple from a factory function:

ts
import { createPluginContext } from '@vuetify/v0'

interface AnalyticsOptions {
  trackPageviews?: boolean
}

interface AnalyticsContext {
  track: (event: string) => void
}

export const [createAnalyticsContext, createAnalyticsPlugin, useAnalytics] =
  createPluginContext<AnalyticsOptions, AnalyticsContext>(
    'my:analytics',
    (options) => ({
      track: (event) => {
        if (options.trackPageviews) console.log(event)
      },
    }),
  )
src/main.ts
import { createApp } from 'vue'
import { createAnalyticsPlugin } from './plugins/analytics'
import App from './App.vue'

const app = createApp(App)

app.use(createAnalyticsPlugin({ trackPageviews: true }))

app.mount('#app')
src/components/MyComponent.vue
<script setup lang="ts">
  import { useAnalytics } from './plugins/analytics'

  const analytics = useAnalytics()
  analytics.track('page_view')
</script>

Architecture

createPlugin wraps createContext for Vue plugin registration:

Plugin Architecture

Use controls to zoom and pan. Click outside or press Escape to close.

Plugin Architecture

Low-level API

Use createPlugin directly when you need fine-grained control over plugin setup, or when composing with existing createContext instances:

ts
import { createContext, createPlugin } from '@vuetify/v0'
import type { App } from 'vue'

interface MyPluginContext {
  app: string
}

export const [useMyContext, provideMyContext] = createContext<MyPluginContext>('provide-namespace')

export function createMyPlugin () {
  const context = {
    app: 'my-app',
  }

  return createPlugin({
    namespace: 'provide-namespace',
    provide: (app: App) => {
      provideMyContext(context, app)
    },
    setup: (app: App) => {
      // For everything else not provide related
    },
  })
}
Tip

The setup and provide hooks are separated for semantic purposes — provide is for DI context, setup is for side effects (watchers, adapters, globals).

Vue DevTools

Opt a plugin into the v0 inspector in Vue DevTools with { devtools: true }. Off by default.

The inspector shows that plugin’s context — refs and class getters unwrapped, functions omitted. Pass inspect to overlay extra keys (adapter names, values behind methods) without replacing the rest of the snapshot.

ts
import { createLogger, createPluginContext } from '@vuetify/v0'

export const [createLoggerContext, createLoggerPlugin, useLogger] =
  createPluginContext('v0:logger', createLogger, {
    inspect: ctx => ({
      level: ctx.current(),
      enabled: ctx.enabled(),
    }),
  })
src/main.ts
import { createApp } from 'vue'
import { createLocalePlugin, createThemePlugin } from '@vuetify/v0'
import App from './App.vue'

const app = createApp(App)

app.use(createThemePlugin({ devtools: true }))
app.use(createLocalePlugin()) // not in the inspector

app.mount('#app')

Plugin authors can default it on via createPluginContext config (devtools: true); the install-time option overrides.

createPluginContext binds the context for you. Plugins that wrap createPlugin directly must call bindPluginContext from provide, or the inspector shows context: undefined:

ts
import { bindPluginContext, createContext, createPlugin } from '@vuetify/v0'
import type { App } from 'vue'

const [useMyContext, provideMyContext] = createContext<{ ready: boolean }>('v0:my-plugin')

export function createMyPlugin () {
  const context = { ready: true }
  const namespace = 'v0:my-plugin'

  return createPlugin({
    namespace,
    devtools: true,
    provide: (app: App) => {
      provideMyContext(context, app)
      bindPluginContext(app, namespace, context)
    },
  })
}

This is __DEV__-gated and a no-op if @vue/devtools-api is not present. Pair it with vite-plugin-vue-devtools (or the browser extension) so the inspector has a host.

Examples

Dashboard Features

A four-file plugin example showing how createPlugin, createContext, and createGroup compose to manage feature-flag state for a dashboard. plugin.ts is the factory: it calls createGroup(), bulk-registers five feature toggles with onboard(), pre-selects two via group.select(['animations', 'notifications']), assembles a DashboardContext object (app name, locale ref, locales list, and the group instance), and calls provideDashboard(context) through the [useDashboard, provideDashboard] tuple produced by createContext. A commented-out block shows how the same code would be wrapped in createPlugin() for app.use() in a real app — for sandbox purposes the factory returns the context object directly.

DashboardProvider.vue creates the plugin instance and calls provideContext in a single setup call, then renders only a slot. DashboardConsumer.vue destructures { group, locale, locales, app } from useDashboard() and renders a feature grid — each feature is a ticket with toggle(), isSelected, and value — alongside a locale selector that writes directly to context.locale. The critical pattern: the consumer never imports from the provider; it only imports from plugin.ts. dashboard.vue composes the two.

The example illustrates the primary reason to compose createGroup inside a plugin rather than manage selection state ad hoc: the group handles toggle logic, mandatory enforcement, select-all, and unselect-all without any custom bookkeeping. The plugin is the factory; the group is the logic layer; the context is the contract. For plugin contexts that need persistence across page reloads, see the Persistence section.

Plugin Architecture

Use controls to zoom and pan. Click outside or press Escape to close.

Plugin Architecture

File breakdown:

FileRole
plugin.tsDefines the DashboardContext (wrapping a GroupContext), creates the context tuple, and exports the createDashboardPlugin factory
DashboardProvider.vueCreates the plugin instance and provides the context, rendering only a slot
DashboardConsumer.vueConsumes the context via useDashboard() and uses the group’s toggle(), selectAll(), and unselectAll() methods
dashboard.vueEntry point that composes Provider around Consumer

Key patterns:

  • Provider components are invisible wrappers that render only a slot

  • The plugin composes createGroup — each feature is a ticket with selection state built in

  • In a real app, the factory would return a plugin for app.use() — here it returns context directly for the sandbox

  • Consumers import only from plugin.ts, never from the Provider

My Dashboard

2 / 5 features enabled

Active features

AnimationsNotifications
Theme
Mode
Palettes
Accessibility

Recipes

Persistence

Plugins can automatically save and restore state across page reloads using useStorage. Add persist and restore hooks to the plugin config, then consumers opt in with persist: true.

Plugin author

Define what to save and how to restore in the createPluginContext config:

ts
import { createPluginContext, createTheme } from '@vuetify/v0'

export const [createThemeContext, createThemePlugin, useTheme] =
  createPluginContext('v0:theme', createTheme, {
    setup: (context, app, options) => {
      // adapter setup...
    },
    persist: ctx => ctx.selectedId.value,
    restore: (ctx, saved) => ctx.select(saved),
  })

Consumer

src/main.ts
import { createApp } from 'vue'
import { createThemePlugin } from '@vuetify/v0'
import App from './App.vue'

const app = createApp(App)

app.use(createThemePlugin({ persist: true }))

app.mount('#app')

When persist: true is passed, the plugin automatically:

  1. Reads from useStorage using the plugin namespace as key

  2. Calls restore with the saved value before setup runs

  3. Watches the persist return value and writes changes to storage

Tip

The default option becomes the true default — it’s only used when no persisted value exists.

Lifecycle

Persist Lifecycle

Use controls to zoom and pan. Click outside or press Escape to close.

Persist Lifecycle

The critical ordering is restore before setup. This means adapters (like the theme CSS variable injector) see the correct restored state on their first run — no flash of wrong values.

Hook signatures

ts
import { createPluginContext, createTheme } from '@vuetify/v0'
import type { PluginContextConfig } from '@vuetify/v0'

const hooks: PluginContextConfig<unknown, ReturnType<typeof createTheme>> = {
  persist: ctx => ctx.selectedId.value,
  restore: (ctx, saved) => ctx.select(saved as string),
}

createPluginContext('v0:theme', createTheme, hooks)

The persist return value is stored under the plugin namespace key (e.g. v0:theme). restore receives whatever was stored — cast to the expected type inside the hook.

Built-in support

PluginPersistsStorage key
createThemePluginSelected theme IDv0:theme
createRtlPluginRTL directionv0:rtl
createLocalePluginSelected localev0:locale

FAQ

Discord
Need help? Join our community for support and discussions ↗

API Reference

The following API details are for the createPlugin composable.
Was this page helpful?

© 2016-1970 Vuetify, LLC
Services
Ctrl+/