Skip to content
← All questions
Advanced

What are Pinia plugins and how do you create one?

State ManagementArchitecture

Pinia plugins let you add behavior to every store globally: persist state to localStorage, add shared properties, wrap actions with logging, sync with external systems. A plugin is a function that receives a context object and runs once per store creation.

Creating a plugin

A plugin is a function that Pinia calls for every store:

ts
import { type PiniaPlugin } from 'pinia'

const myPlugin: PiniaPlugin = (context) => {
  // context.store  — the store instance
  // context.pinia  — the Pinia root instance
  // context.app    — the Vue app instance
  // context.options — the options passed to defineStore
}

Register it before mounting the app:

ts
const pinia = createPinia()
pinia.use(myPlugin)
app.use(pinia)

Example: localStorage persistence

ts
function piniaLocalStorage(): PiniaPlugin {
  return ({ store }) => {
    const saved = localStorage.getItem(store.$id)
    if (saved) {
      store.$patch(JSON.parse(saved))
    }

    store.$subscribe((mutation, state) => {
      localStorage.setItem(store.$id, JSON.stringify(state))
    })
  }
}

const pinia = createPinia()
pinia.use(piniaLocalStorage())

Every store now automatically saves and restores its state from localStorage.

Example: action logger

ts
function piniaLogger(): PiniaPlugin {
  return ({ store }) => {
    store.$onAction(({ name, args, after, onError }) => {
      const start = performance.now()

      after(() => {
        const duration = (performance.now() - start).toFixed(1)
        console.log(`[${store.$id}] ${name}(${JSON.stringify(args)}) — ${duration}ms`)
      })

      onError((error) => {
        console.error(`[${store.$id}] ${name} failed:`, error)
      })
    })
  }
}

Adding properties to every store

Return an object from the plugin to extend all stores:

ts
function piniaCreatedAt(): PiniaPlugin {
  return ({ store }) => {
    store.createdAt = new Date()
    return { createdAt: store.createdAt }
  }
}

For TypeScript, augment the store interface:

ts
declare module 'pinia' {
  export interface PiniaCustomProperties {
    createdAt: Date
  }
}

Now store.createdAt is available and typed on every store.

Adding state to every store

Use ref to add reactive state that gets included in devtools and serialization:

ts
function piniaLoadingState(): PiniaPlugin {
  return ({ store }) => {
    const isLoading = ref(false)

    store.$onAction(({ after, onError }) => {
      isLoading.value = true
      after(() => { isLoading.value = false })
      onError(() => { isLoading.value = false })
    })

    return { isLoading }
  }
}
vue
<script setup>
const userStore = useUserStore()
// isLoading is available on every store
</script>

<template>
  <Spinner v-if="userStore.isLoading" />
</template>

Targeting specific stores

Check store.$id or use the options object to apply logic selectively:

ts
function piniaDebounce(): PiniaPlugin {
  return ({ store, options }) => {
    if (options.debounce) {
      return Object.keys(options.debounce).reduce((debounced, action) => {
        debounced[action] = useDebounceFn(
          store[action],
          options.debounce[action]
        )
        return debounced
      }, {} as Record<string, Function>)
    }
  }
}
ts
defineStore('search', {
  actions: {
    search() { /* ... */ }
  },
  debounce: {
    search: 300
  }
})

Plugins in Nuxt

With @pinia/nuxt, register plugins in a Nuxt plugin file:

ts
// plugins/pinia-persist.ts
export default defineNuxtPlugin(({ $pinia }) => {
  if (import.meta.client) {
    $pinia.use(piniaLocalStorage())
  }
})
PluginWhat it does
pinia-plugin-persistedstatePersist stores to localStorage/sessionStorage/cookies with fine-grained control
@pinia/coladaAsync state management (data fetching, caching, invalidation)

Plugin vs store action vs composable

NeedUse
Behavior added to ALL stores (logging, persistence, shared state)Plugin
Business logic for one specific storeStore action
Reusable logic not tied to a storeComposable

Released under the MIT License.