Pinia plugins allow you to extend the functionality of your stores by adding reusable features like persisted state, authentication handling, or logging.
1. What are Pinia Plugins?
A Pinia plugin is a function that runs when a store is initialized. It allows you to:
✅ Extend state with extra properties
✅ Add global actions or getters
✅ Automatically persist data (e.g., in localStorage
)
✅ Handle authentication, logging, or analytics
2. How to Create a Custom Pinia Plugin in Nuxt
You can create your own Pinia plugin to extend store functionality globally.
Step 1: Create a Plugin (plugins/piniaPlugin.ts
)
import { PiniaPluginContext } from 'pinia'
export default function myPiniaPlugin({ store }: PiniaPluginContext) {
store.$subscribe((mutation, state) => {
console.log(`[Pinia]: Mutation in ${store.$id}:`, mutation, state)
})
return {
version: '1.0.0', // Adding a custom global property
}
}
This plugin logs all store mutations and adds a version
property to every store.
Step 2: Register the Plugin in nuxt.config.ts
Modify your nuxt.config.ts
to enable the plugin:
export default defineNuxtConfig({
modules: ['@pinia/nuxt'],
pinia: {
autoImports: ['defineStore'],
},
plugins: ['~/plugins/piniaPlugin.ts'], // Register the Pinia plugin
})
Step 3: Use the Plugin in a Store
Now, every store can access the version
property:
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++
}
}
})
Access the plugin property in a component:
<script setup lang="ts">
import { useCounterStore } from '~/stores/counter'
const counter = useCounterStore()
console.log(counter.version) // Output: '1.0.0'
</script>
3. Built-in Pinia Plugins (Example: Persisted State)
One of the most popular built-in plugins is pinia-plugin-persistedstate, which allows automatic localStorage or sessionStorage persistence.
Step 1: Install the Plugin
npm install pinia-plugin-persistedstate
Step 2: Register in plugins/piniaPersist.ts
import { defineNuxtPlugin } from '#app'
import { Pinia } from 'pinia'
import piniaPluginPersistedState from 'pinia-plugin-persistedstate'
export default defineNuxtPlugin(({ $pinia }) => {
$pinia.use(piniaPluginPersistedState)
})
Step 3: Enable Persistence in a Store
import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', {
state: () => ({
token: ''
}),
persist: true // This automatically stores the state in localStorage
})
Now, token
is persisted across page reloads.
4. When Should You Use Pinia Plugins?
✅ Global Enhancements – Logging, analytics, authentication, or caching
✅ Auto Persistence – Save user preferences, authentication tokens
✅ Cross-Store Features – Shared behavior across multiple stores
II. Authentication with Pinia in Nuxt.js
Managing authentication in Nuxt.js with Pinia is a clean and efficient approach. We'll implement:
✅ User login/logout
✅ Storing tokens securely
✅ Auto-persisting authentication state
1. Setting Up an Authentication Store
First, create an authentication store to handle user sessions.
Updated Auth Store (Improved Security & Maintainability)
stores/auth.ts
import { defineStore } from 'pinia'
import { useCookie } from '#app'
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null as null | { id: number; name: string; email: string },
token: useCookie('auth_token'), // Securely store token in a cookie
isAuthenticated: false,
loading: false,
error: null as string | null,
}),
getters: {
getUser: (state) => state.user,
getToken: (state) => state.token.value, // Use cookie value
isUserAuthenticated: (state) => state.isAuthenticated,
getError: (state) => state.error,
},
actions: {
async login(credentials: { email: string; password: string }) {
try {
this.loading = true
this.error = null
const response = await $fetch('/api/auth/login', {
method: 'POST',
body: credentials,
})
if (!response?.token) throw new Error('Invalid response from server')
// Store token securely
this.token.value = response.token
this.user = response.user
this.isAuthenticated = true
navigateTo('/dashboard') // Redirect user after login
} catch (error) {
this.error = error.message || 'Login failed'
throw error
} finally {
this.loading = false
}
},
async logout() {
try {
await $fetch('/api/auth/logout', {
method: 'POST',
})
} catch (error) {
console.error('Logout error:', error)
} finally {
// Clear user session securely
this.token.value = null
this.user = null
this.isAuthenticated = false
navigateTo('/login') // Redirect to login
}
},
async checkAuth() {
if (!this.token.value) {
this.logout()
return
}
try {
const response = await $fetch('/api/auth/verify', {
headers: {
Authorization: `Bearer ${this.token.value}`,
},
})
this.user = response.user
this.isAuthenticated = true
} catch (error) {
this.logout()
}
},
async register(userData: { name: string; email: string; password: string }) {
try {
this.loading = true
this.error = null
await $fetch('/api/auth/register', {
method: 'POST',
body: userData,
})
// Auto-login after successful registration
await this.login({
email: userData.email,
password: userData.password,
})
} catch (error) {
this.error = error.message || 'Registration failed'
throw error
} finally {
this.loading = false
}
},
},
persist: {
enabled: true, // Automatically persists the auth state
},
})