Los plugins de Pinia permiten añadir comportamiento a todos los stores globalmente: persistir estado en localStorage, agregar propiedades compartidas, envolver acciones con logging, sincronizar con sistemas externos. Un plugin es una función que recibe un objeto de contexto y se ejecuta una vez por cada creación de store.
Crear un plugin
Un plugin es una función que Pinia llama para cada store:
import { type PiniaPlugin } from 'pinia'
const myPlugin: PiniaPlugin = (context) => {
// context.store — la instancia del store
// context.pinia — la instancia raíz de Pinia
// context.app — la instancia de la app Vue
// context.options — las opciones pasadas a defineStore
}Registrarlo antes de montar la app:
const pinia = createPinia()
pinia.use(myPlugin)
app.use(pinia)Ejemplo: persistencia en localStorage
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())Ahora todos los stores guardan y restauran su estado automáticamente desde localStorage.
Ejemplo: logger de acciones
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)
})
})
}
}Añadir propiedades a todos los stores
Devolver un objeto desde el plugin extiende todos los stores:
function piniaCreatedAt(): PiniaPlugin {
return ({ store }) => {
store.createdAt = new Date()
return { createdAt: store.createdAt }
}
}Para TypeScript, ampliar la interfaz del store:
declare module 'pinia' {
export interface PiniaCustomProperties {
createdAt: Date
}
}Ahora store.createdAt está disponible y tipado en todos los stores.
Añadir estado a todos los stores
Usar ref para añadir estado reactivo que quede incluido en devtools y serialización:
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 }
}
}<script setup>
const userStore = useUserStore()
// isLoading está disponible en todos los stores
</script>
<template>
<Spinner v-if="userStore.isLoading" />
</template>Aplicar lógica solo a stores específicos
Comprobar store.$id o usar el objeto options para aplicar lógica de forma selectiva:
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>)
}
}
}defineStore('search', {
actions: {
search() { /* ... */ }
},
debounce: {
search: 300
}
})Plugins en Nuxt
Con @pinia/nuxt, registrar plugins en un fichero de plugin de Nuxt:
// plugins/pinia-persist.ts
export default defineNuxtPlugin(({ $pinia }) => {
if (import.meta.client) {
$pinia.use(piniaLocalStorage())
}
})Plugins de la comunidad más usados
| Plugin | Qué hace |
|---|---|
pinia-plugin-persistedstate | Persistir stores en localStorage/sessionStorage/cookies con control granular |
@pinia/colada | Gestión de estado asíncrono (data fetching, caché, invalidación) |
Plugin vs acción de store vs composable
| Necesidad | Usar |
|---|---|
| Comportamiento añadido a TODOS los stores (logging, persistencia, estado compartido) | Plugin |
| Lógica de negocio para un store específico | Acción del store |
| Lógica reutilizable no ligada a un store | Composable |