---
name: stores
description: Defining stores, state, getters, and actions in Pinia
---
# Pinia Stores
Stores are defined using `defineStore()` with a unique name. Each store has three core concepts: **state**, **getters**, and **actions**.
## Defining Stores
### Option Stores
Similar to Vue's Options API:
```ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: 'Eduardo',
}),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
```
Think of `state` as `data`, `getters` as `computed`, and `actions` as `methods`.
### Setup Stores (Recommended)
Uses Composition API syntax - more flexible and powerful:
```ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const name = ref('Eduardo')
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, name, doubleCount, increment }
})
```
In Setup Stores: `ref()` → state, `computed()` → getters, `function()` → actions.
**Important:** You must return all state properties for Pinia to track them.
### Using Stores
```vue
```
### Destructuring with storeToRefs
```vue
```
---
## State
State is defined as a function returning the initial state.
### TypeScript
Type inference works automatically. For complex types:
```ts
interface UserInfo {
name: string
age: number
}
export const useUserStore = defineStore('user', {
state: () => ({
userList: [] as UserInfo[],
user: null as UserInfo | null,
}),
})
```
Or use an interface for the return type:
```ts
interface State {
userList: UserInfo[]
user: UserInfo | null
}
export const useUserStore = defineStore('user', {
state: (): State => ({
userList: [],
user: null,
}),
})
```
### Accessing and Modifying
```ts
const store = useStore()
store.count++
```
```vue
```
### Mutating with $patch
Apply multiple changes at once:
```ts
// Object syntax
store.$patch({
count: store.count + 1,
name: 'DIO',
})
// Function syntax (for complex mutations)
store.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
```
### Resetting State
Option Stores have built-in `$reset()`. For Setup Stores, implement your own:
```ts
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function $reset() {
count.value = 0
}
return { count, $reset }
})
```
### Subscribing to State Changes
```ts
cartStore.$subscribe((mutation, state) => {
mutation.type // 'direct' | 'patch object' | 'patch function'
mutation.storeId // 'cart'
mutation.payload // patch object (only for 'patch object')
localStorage.setItem('cart', JSON.stringify(state))
})
// Options
cartStore.$subscribe(callback, { flush: 'sync' }) // Immediate
cartStore.$subscribe(callback, { detached: true }) // Keep after unmount
```
---
## Getters
Getters are computed values, equivalent to Vue's `computed()`.
### Basic Getters
```ts
getters: {
doubleCount: (state) => state.count * 2,
}
```
### Accessing Other Getters
Use `this` with explicit return type:
```ts
getters: {
doubleCount: (state) => state.count * 2,
doublePlusOne(): number {
return this.doubleCount + 1
},
},
```
### Getters with Arguments
Return a function (note: loses caching):
```ts
getters: {
getUserById: (state) => {
return (userId: string) => state.users.find((user) => user.id === userId)
},
},
```
Cache within parameterized getters:
```ts
getters: {
getActiveUserById(state) {
const activeUsers = state.users.filter((user) => user.active)
return (userId: string) => activeUsers.find((user) => user.id === userId)
},
},
```
### Accessing Other Stores in Getters
```ts
import { useOtherStore } from './other-store'
getters: {
combined(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
```
---
## Actions
Actions are methods for business logic. Unlike getters, they can be asynchronous.
### Defining Actions
```ts
actions: {
increment() {
this.count++
},
randomizeCounter() {
this.count = Math.round(100 * Math.random())
},
},
```
### Async Actions
```ts
actions: {
async registerUser(login: string, password: string) {
try {
this.userData = await api.post({ login, password })
} catch (error) {
return error
}
},
},
```
### Accessing Other Stores in Actions
```ts
import { useAuthStore } from './auth-store'
actions: {
async fetchUserPreferences() {
const auth = useAuthStore()
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences()
}
},
},
```
**SSR:** Call all `useStore()` before any `await`:
```ts
async orderCart() {
// ✅ Call stores before await
const user = useUserStore()
await apiOrderCart(user.token, this.items)
// ❌ Don't call useStore() after await in SSR
}
```
### Subscribing to Actions
```ts
const unsubscribe = someStore.$onAction(
({ name, store, args, after, onError }) => {
const startTime = Date.now()
console.log(`Start "${name}" with params [${args.join(', ')}]`)
after((result) => {
console.log(`Finished "${name}" after ${Date.now() - startTime}ms`)
})
onError((error) => {
console.warn(`Failed "${name}": ${error}`)
})
}
)
unsubscribe() // Cleanup
```
Keep subscription after component unmount:
```ts
someStore.$onAction(callback, true)
```
---
## Options API Helpers
```ts
import { mapState, mapWritableState, mapActions } from 'pinia'
import { useCounterStore } from '../stores/counter'
export default {
computed: {
// Readonly state/getters
...mapState(useCounterStore, ['count', 'doubleCount']),
// Writable state
...mapWritableState(useCounterStore, ['count']),
},
methods: {
...mapActions(useCounterStore, ['increment']),
},
}
```
---
## Accessing Global Providers in Setup Stores
```ts
import { inject } from 'vue'
import { useRoute } from 'vue-router'
import { defineStore } from 'pinia'
export const useSearchFilters = defineStore('search-filters', () => {
const route = useRoute()
const appProvided = inject('appProvided')
// Don't return these - access them directly in components
return { /* ... */ }
})
```