Skip to content
← All questions
🌱 Beginner

What is Pinia and how does it differ from Vuex?

📦 State Management🍍 Pinia🗃️ Vuex

Pinia is the official state management library for Vue 3. It replaces Vuex. The Vue team created it because Vuex's design (mutations, string-based API, complex modules) didn't align well with TypeScript and the Composition API.

Why Pinia replaced Vuex

Vuex has three pain points that Pinia eliminates:

1. Mutations are gone. In Vuex, you can't change state directly. You have to write a mutation (synchronous) and commit it by name. Pinia lets actions change state directly, because Vue 3's reactivity system tracks changes automatically through DevTools without needing a separate mutation layer.

2. Full TypeScript inference. Vuex relies on string keys (commit('SET_USER'), dispatch('fetchUser'), getters.userName). A typo in any of those strings is a runtime bug, not a compile error. Pinia stores are plain objects; your IDE knows every property and method.

3. No modules or namespacing. Vuex has one global store, split into namespaced modules (store.commit('cart/ADD_ITEM')). Pinia stores are independent, each is its own defineStore(), imported directly where needed.

Side-by-side comparison

ts
// Vuex: mutations required, string-based
store.commit('SET_COUNT', 5)
store.dispatch('fetchUsers')
store.getters.activeUsers

// Pinia: direct, type-safe
counterStore.count = 5
await userStore.fetchUsers()
userStore.activeUsers

Defining a Pinia store

ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCartStore = defineStore('cart', () => {
  const items = ref<CartItem[]>([])

  const total = computed(() =>
    items.value.reduce((sum, item) => sum + item.price, 0)
  )

  function addItem(item: CartItem) {
    items.value.push(item)
  }

  async function checkout() {
    await api.checkout(items.value)
    items.value = []
  }

  return { items, total, addItem, checkout }
})

This uses the Composition API syntax (setup function). There's also an Options syntax with state, getters, and actions properties. Same result, different style.

Using it in a component

vue
<script setup lang="ts">
import { useCartStore } from '@/stores/cart'

const cart = useCartStore()
</script>

<template>
  <span>{{ cart.items.length }} items, {{ cart.total }}€</span>
  <button @click="cart.checkout()">Checkout</button>
</template>
Open in Vue Playground

No commit, no dispatch, no mapGetters. Just import the store and use it.

Key differences table

AspectVuexPinia
MutationsRequired (synchronous)Don't exist
TypeScriptLimited, string-based APIFull inference, type-safe
Store structureOne store, namespaced modulesIndependent stores
DevToolsYesYes (time-travel, state editing)
SSRRequires careful setupBuilt-in support
Bundle size~6kb~1kb

See also: How does Vuex work? · How does Pinia work internally? · How would you migrate a Vuex module to Pinia?

References

Released under the MIT License.