| name | pinia-v3 |
| description | Pinia v3 Vue state management with defineStore, getters, actions. Use for Vue 3 stores, Nuxt SSR, Vuex migration, or encountering store composition, hydration, testing errors. |
| metadata | {"keywords":["pinia","vue state management","pinia stores","defineStore","vue 3 state","state management","getters","actions","pinia plugins","pinia ssr","nuxt pinia","vuex migration","store composition","pinia testing","setup stores","option stores","storeToRefs","mapState","mapActions","state hydration","pinia nuxt module","createPinia","useStore","pinia devtools","pinia hmr","hot module replacement"]} |
| license | MIT |
Pinia v3 - Vue State Management
Status: Production Ready ✅
Last Updated: 2025-11-11
Dependencies: Vue 3 (or Vue 2.7 with @vue/composition-api)
Latest Versions: pinia@^3.0.4, @pinia/nuxt@^0.11.2, @pinia/testing@^1.0.2
Quick Start (5 Minutes)
1. Install Pinia
bun add pinia
bun add pinia
bun add pinia
For Vue <2.7 users: Also install @vue/composition-api with bun add @vue/composition-api
Why this matters:
- Pinia is the official Vue state management library
- Provides better TypeScript support than Vuex
- Eliminates mutations and namespacing complexity
- Full DevTools support with time-travel debugging
2. Create and Register Pinia Instance
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
CRITICAL:
- Install Pinia BEFORE using any store
- Call
app.use(pinia) before mounting the app
- Only one Pinia instance per application (unless SSR)
3. Define Your First Store
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: 'Eduardo'
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
4. Use Store in Components
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
</script>
<template>
<div>
<p>Count: {{ counter.count }}</p>
<p>Double: {{ counter.doubleCount }}</p>
<button @click="counter.increment">Increment</button>
</div>
</template>
The Two Store Syntaxes
Load references/store-syntax-guide.md for complete comparison of Option vs Setup stores.
Quick Overview
Pinia supports two store definition syntaxes:
Option Stores:
- Similar to Vue Options API
- Built-in
$reset() method
- Best for: Simpler use cases, teams familiar with Vuex
Setup Stores:
- Uses Composition API pattern
- Full composables integration
- Best for: Advanced patterns, need watchers/VueUse integration
→ Load references/store-syntax-guide.md for: Complete syntax comparison, examples, choosing criteria
State, Getters, and Actions
Load references/state-getters-actions.md for complete API reference.
Quick Reference
State:
- Define in
state: () => ({...}) (option) or ref() (setup)
- Access directly:
store.count
- Mutate directly:
store.count++ or store.$patch({...})
- Reset:
store.$reset() (option stores only)
Getters:
- Computed properties:
getters: { double: (state) => state.count * 2 }
- Access other getters with
this (must type return value)
Actions:
- Business logic:
actions: { increment() { this.count++ } }
- Can be async
- Access other stores directly
Store Destructuring:
import { storeToRefs } from 'pinia'
const { name, count } = storeToRefs(store)
const { increment } = store
→ Load references/state-getters-actions.md for: Complete API, subscriptions, store composition patterns, Options API usage
Plugins and Composables
Load references/plugins-composables.md for complete plugin and composables guide.
Plugin Basics
pinia.use(({ store, options }) => {
return { customProperty: 'value' }
})
Composables Integration
Option Stores: Limited to useLocalStorage style in state()
Setup Stores: Full VueUse/composables support
→ Load references/plugins-composables.md for: Complete plugin patterns, VueUse integration, TypeScript typing, common patterns (persistence, router, logger)
Using Stores Outside Components
The Problem
Stores need the Pinia instance, which is auto-injected in components but not available in module scope.
❌ Wrong: Accessing Store at Module Level
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
router.beforeEach((to) => {
if (userStore.isLoggedIn) { }
})
✅ Right: Accessing Store Inside Callbacks
import { useUserStore } from '@/stores/user'
router.beforeEach((to) => {
const userStore = useUserStore()
if (userStore.isLoggedIn) { }
})
Why it works: Router guards execute AFTER app.use(pinia) completes.
SSR: Explicit Pinia Instance
export function setupRouter(pinia) {
router.beforeEach((to) => {
const userStore = useUserStore(pinia)
})
}
Server-Side Rendering & Nuxt
Load references/ssr-and-nuxt.md for complete SSR and Nuxt integration guide.
SSR Quick Reference
State Hydration:
- Server: Serialize with
devalue() (not JSON.stringify)
- Client: Hydrate BEFORE calling
useStore()
- Critical: Call all
useStore() BEFORE await in actions
Nuxt 3/4 Integration
bunx nuxi@latest module add pinia
Auto-imports: defineStore, storeToRefs, usePinia, acceptHMRUpdate, all stores
→ Load references/ssr-and-nuxt.md for: Complete SSR patterns, Nuxt configuration, server-side data fetching, SSR pitfalls, debugging
Testing
Load references/testing-guide.md for complete testing guide.
Testing Quick Start
import { setActivePinia, createPinia } from 'pinia'
beforeEach(() => {
setActivePinia(createPinia())
})
Component Testing
bun add -d @pinia/testing
import { createTestingPinia } from '@pinia/testing'
mount(Component, {
global: { plugins: [createTestingPinia()] }
})
→ Load references/testing-guide.md for: Complete test patterns, stubbing actions, mocking getters, async testing, SSR testing
Hot Module Replacement (HMR)
Vite Setup
import { defineStore, acceptHMRUpdate } from 'pinia'
export const useCounterStore = defineStore('counter', {
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useCounterStore, import.meta.hot))
}
Webpack Setup
if (import.meta.webpackHot) {
import.meta.webpackHot.accept(acceptHMRUpdate(useCounterStore, import.meta.webpackHot))
}
Benefits:
- Edit stores without full page reload
- Preserve application state during development
- Faster development iteration
Options API Usage
For projects still using Options API, load complete mapper documentation.
→ Load references/state-getters-actions.md for: Complete Options API integration, all mappers (mapStores, mapState, mapWritableState, mapActions)
Migrating from Vuex
Load references/vuex-migration.md for complete migration guide.
Quick Conversion Overview
Key Changes:
- Remove
namespaced (automatic via store ID)
- Eliminate
mutations (direct state mutation)
- Replace
commit() with direct mutations
- Replace
rootState/rootGetters with store imports
- Use
store.$reset() instead of custom clear mutations
Directory: store/modules/ → stores/ (each module = separate store)
→ Load references/vuex-migration.md for: Complete conversion steps, component migration, checklist, gradual migration strategy
Critical Rules
Always Do
✅ Define all state properties in state() or return them from setup stores
✅ Use storeToRefs() when destructuring state/getters in components
✅ Call app.use(pinia) BEFORE mounting the app
✅ Return all state from setup stores (private state breaks SSR/DevTools)
✅ Call useStore() inside functions/callbacks when used outside components
✅ Use acceptHMRUpdate() for development HMR support
✅ Type return values when getters use this to access other getters
✅ Use devalue for SSR state serialization (prevents XSS)
✅ Hydrate state BEFORE calling any useStore() on the client (SSR)
✅ Call all useStore() BEFORE any await in async actions (SSR)
Never Do
❌ Add state properties dynamically after store creation
❌ Destructure store directly without storeToRefs() (loses reactivity)
❌ Use arrow functions for actions (need this context)
❌ Return private state in setup stores (breaks SSR/DevTools/plugins)
❌ Call useStore() at module top-level (before Pinia installed)
❌ Create circular dependencies between stores (both reading each other's state)
❌ Use JSON.stringify() for SSR serialization (vulnerable to XSS)
❌ Call useStore() after await in actions (breaks SSR)
❌ Forget to type getter return values when using this
❌ Skip beforeEach(() => setActivePinia(createPinia())) in unit tests
Known Issues Prevention
This skill prevents 12 documented issues:
Issue #1: Lost Reactivity from Direct Destructuring
Error: State changes don't update in template after destructuring
Why It Happens: JavaScript destructuring breaks Vue reactivity
Prevention: Always use storeToRefs() for state/getters
Issue #2: Cannot Add State Properties Dynamically
Error: New properties added after store creation aren't reactive
Why It Happens: Pinia needs all properties defined upfront for reactivity
Prevention: Declare all properties in state(), even if initially undefined
Issue #3: Store Not Found Before Pinia Install
Error: getActivePinia() returns undefined
Why It Happens: Calling useStore() before app.use(pinia)
Prevention: Call app.use(pinia) before mounting or accessing stores
Issue #4: Setup Store Private State Breaks SSR
Error: State not serialized/hydrated correctly in SSR
Why It Happens: Properties not returned from setup aren't tracked
Prevention: Return ALL state properties from setup stores
Issue #5: Getters with this Don't Infer Types
Error: TypeScript can't infer return type when getter uses this
Source: Known TypeScript limitation with Pinia
Prevention: Explicitly type return value: getterName(): ReturnType { ... }
Issue #6: Options API Store Suffix Confusion
Error: Can't find this.counterStore in component
Why It Happens: mapStores() automatically adds 'Store' suffix
Prevention: Use store name + 'Store' or call setMapStoreSuffix()
Issue #7: Actions Called After await Break SSR
Error: Wrong Pinia instance used in SSR, causing state pollution
Why It Happens: await changes execution context in async functions
Prevention: Call all useStore() before any await statements
Issue #8: Circular Store Dependencies Crash App
Error: Maximum call stack exceeded
Why It Happens: Both stores read each other's state during initialization
Prevention: Use getters/actions for cross-store access, not setup-time reads
Issue #9: XSS Vulnerability in SSR State Serialization
Error: User input in state can execute malicious scripts
Why It Happens: JSON.stringify() doesn't escape executable code
Prevention: Use devalue library for safe serialization
Issue #10: HMR Doesn't Work in Development
Error: Changes to store require full page reload
Why It Happens: Vite/webpack HMR not configured for store
Prevention: Add acceptHMRUpdate() block to each store file
Issue #11: Composables Return Functions Break Option Stores
Error: Store state contains non-serializable functions
Why It Happens: Option stores state() can only return writable refs
Prevention: Use setup stores for complex composables, or extract only writable state
Issue #12: State Not Reset Between Unit Tests
Error: Tests affect each other, sporadic failures
Why It Happens: Single Pinia instance shared across tests
Prevention: beforeEach(() => setActivePinia(createPinia())) in test suites
Package Versions (Verified 2026-08-03)
Core: pinia@^3.0.4, vue@^3.5.24
Nuxt: @pinia/nuxt@^0.11.2, nuxt@^4.0.0 (Nuxt 3 reached EOL on 2026-07-31; v4 is the current stable line)
Testing: @pinia/testing@^1.0.2, vitest@^2.0.0
SSR: devalue@^5.3.2 (for safe serialization)
Common Patterns
See reference files for complete pattern examples:
- Authentication stores →
references/state-getters-actions.md
- Persistence plugins →
references/plugins-composables.md
- Form stores →
references/store-syntax-guide.md (setup store examples)
- Router integration →
references/state-getters-actions.md (accessing stores outside components)
Official Documentation
Troubleshooting
Problem: "getActivePinia() was called with no active Pinia"
Solution:
- Ensure
app.use(pinia) is called before mounting
- If outside component, call
useStore() inside callback/function
- For SSR, pass pinia instance explicitly:
useStore(pinia)
Problem: State changes don't update in template
Solution: Use storeToRefs() instead of direct destructuring
Problem: Getter using this has TypeScript errors
Solution: Explicitly type the return value: myGetter(): ReturnType { return this.otherGetter }
Problem: $reset() not available in setup store
Solution: Implement custom reset manually:
function $reset() {
count.value = 0
name.value = ''
}
return { count, name, $reset }
Problem: HMR not working for stores
Solution: Add HMR acceptance block:
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useMyStore, import.meta.hot))
}
Problem: Tests fail intermittently
Solution: Create fresh Pinia in beforeEach():
beforeEach(() => {
setActivePinia(createPinia())
})
When to Load References
Load references/store-syntax-guide.md when:
- Need detailed comparison between Option and Setup store syntaxes
- Deciding which syntax to use for a new store
- Questions about Option vs Setup stores trade-offs
- Need complete examples of both syntaxes
Load references/state-getters-actions.md when:
- Need complete API reference for state, getters, or actions
- Questions about
$patch, $subscribe, or $onAction
- Implementing store composition patterns
- Using Options API mappers (
mapStores, mapState, mapActions)
- Accessing stores outside components (router, plugins)
Load references/plugins-composables.md when:
- Creating custom Pinia plugins
- Integrating VueUse or other composables into stores
- Need persistence, routing, or logging plugin patterns
- Questions about TypeScript typing for plugins
- Advanced composables integration
Load references/ssr-and-nuxt.md when:
- Setting up server-side rendering
- Integrating with Nuxt 3/4
- Questions about state hydration or serialization
- SSR-related errors (wrong Pinia instance, hydration mismatch)
- Nuxt auto-imports or configuration
- Server-side data fetching patterns
Load references/testing-guide.md when:
- Setting up unit tests for stores
- Testing components that use Pinia stores
- Need to stub actions or mock getters
- Questions about
createTestingPinia
- Testing SSR stores
- Vitest or testing framework integration
Load references/vuex-migration.md when:
- Migrating existing Vuex codebase to Pinia
- Questions about Vuex→Pinia conversion
- Need migration checklist or examples
- Gradual migration strategy needed
Complete Setup Checklist
Questions? Issues?
- Check official docs: https://pinia.vuejs.org/
- Review "Known Issues Prevention" section above
- Verify setup checklist is complete
- Check for TypeScript configuration issues
- Ensure Pinia is installed before using stores