Your Vue 3 project is growing. Props are being passed down multiple levels, events are flying between parent and child, and that global reactive variable outside any component is making you nervous. You need centralized state management. The classic choice was Vuex. But with the Composition API, Vuex started showing its age. That's why in 2026 the answer is Pinia.
We at Meteora Web have migrated several projects from Vuex to Pinia — and we're not going back. In this guide, we explain what it is, how to use it, and why it's better. With real examples, not theory.
What is Pinia and why does it replace Vuex?
Pinia is a state management library for Vue 3 created by Eduardo San Martin Morote, the same author of the Vuex module. It's not an alternative: it's the official heir. Natively integrated with Vue DevTools, it supports Composition API from day one, and eliminates all the verbosity of Vuex (mutations, forced namespacing, nested stores).
The key difference? Pinia has no mutations. You act directly on the state through actions, which can be synchronous or asynchronous. TypeScript is first-class. And the structure is flat: each store is an independent module, no need for 'namespaced: true' like in Vuex.
Why we left Vuex behind
Vuex was built for Vue 2, with object-based options syntax. With Vue 3 and Composition API, you could use it but it felt like a hybrid. Pinia was designed for Vue 3 and integrates perfectly with the Composition API. If you want to dive into Vue 3 basics, check our guide on Vue.js 3 and Composition API.
Sponsored Protocol
How to install and configure Pinia in a Vue 3 project?
Installation is one command. Then register the plugin in your main app.
npm install pinia
# or
yarn add pinia// main.js / main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')Done. Now you can create stores anywhere. Pinia uses a singleton pattern: once created, a store is reused automatically.
How to define a store with Pinia and the Composition API?
There are two ways: setup store (our favourite) or Options API. Here's the Composition API style, which is more flexible and integrates seamlessly with the rest of your code.
// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// State
const count = ref(0)
// Getters (derived state)
const doubleCount = computed(() => count.value * 2)
// Actions
function increment() {
count.value++
}
async function fetchAndSet() {
const response = await fetch('/api/count')
const data = await response.json()
count.value = data.value
}
return { count, doubleCount, increment, fetchAndSet }
})Note the syntax: defineStore takes two arguments — a unique store name and a setup function. Inside, use ref for state, computed for getters, and regular functions for actions. Everything you return will be accessible from the component.
Sponsored Protocol
How to use the store in a Vue 3 component?
Simple: call the composable useCounterStore inside setup() or <script setup>.
<script setup>
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
</script>
<template>
<div>
<p>Count: {{ store.count }}</p>
<p>Double: {{ store.doubleCount }}</p>
<button @click="store.increment()">+</button>
</div>
</template>No mapState or mapActions. The store object is reactive and you can access it directly. Be careful: to keep reactivity when destructuring, use storeToRefs.
import { storeToRefs } from 'pinia'
const { count, doubleCount } = storeToRefs(store)
// count and doubleCount are ReF, not plain values
const { increment } = store // actions can be destructured freelyHow to handle async actions and API calls with Pinia?
One of Pinia's strengths is that actions can be async without extra configuration. In our example fetchAndSet is already an async action. Call it directly from the component as store.fetchAndSet(). No mutations, no commits: set the state directly, and the UI updates automatically. This cuts boilerplate by 50% compared to Vuex.
Sponsored Protocol
Does Pinia support TypeScript better than Vuex?
Absolutely. Pinia is written in TypeScript and provides automatic type inference. If you define the store with a setup function, types for parameters and return values are deduced without extra interfaces. In Vuex you had to manually declare types for state, getters, mutations. With Pinia, defineStore returns a typed composable — use it in TypeScript components and enjoy autocompletion and compile-time checks.
// counter.ts
interface CounterState {
count: number
}
export const useCounterStore = defineStore('counter', () => {
const count = ref<number>(0)
// ...
})TypeScript works out of the box. For a practical example of TypeScript integration with Vue 3, see our page on Vue.js 3 and Composition API.
How to persist Pinia state (localStorage, sessionStorage)?
Pinia doesn't include a persistence plugin in core, but the ecosystem provides one. The most popular is pinia-plugin-persistedstate. Install and configure once.
npm install pinia-plugin-persistedstate// main.js
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)Then in your store, add persist: true:
Sponsored Protocol
export const useAuthStore = defineStore('auth', {
state: () => ({ token: null }),
persist: true, // saves to localStorage automatically
// ...
})
// Or with setup store:
export const useAuthStore = defineStore('auth', () => {
const token = ref(null)
return { token }
}, { persist: true })You can customize the key, storage, and serialization. For simple projects, it's enough.
What are common mistakes when migrating from Vuex to Pinia?
The most common? Destructuring the store without using storeToRefs. Vuex returned reactive objects, but with Pinia if you do const { count } = store, you lose reactivity. Always use storeToRefs for state and getters. Actions can be destructured freely.
Second mistake: forgetting that Pinia has no mutations. Don't try store.$commit() — it doesn't exist. You modify state directly. That's simpler but requires discipline: are mutations logged in DevTools? Yes. Pinia tracks every state change, even direct ones. So debugging is identical.
Third mistake: creating too many stores when one would do. Each store is a module, but don't multiply them without reason. An e-commerce store will have one for cart, one for user, one for catalog. Not one per page.
Sponsored Protocol
Pinia vs Vuex — which to choose for a new Vue 3 project in 2026?
No contest. For a new Vue 3 project, the choice is Pinia. Vuex is in maintenance mode, no new features. The official Vue documentation recommends Pinia for all new projects. If you have an existing Vuex project, consider migrating: Pinia provides a migration guide (see official migration page).
We at Meteora Web have done it several times. The switch is painless: often you keep the same state structure, replace mutations with actions, remove namespacing. The code becomes more readable and the bundle smaller (Pinia ~1KB, Vuex ~5KB).
What to do now
- Install Pinia in an existing or new Vue 3 project:
npm install pinia. - Create your first store with
defineStoreand a setup function. Usereffor state,computedfor getters, functions for actions. - Use the store in a component with
useXStore()andstoreToRefsif needed. - Add persistence with
pinia-plugin-persistedstateif you have data to save (tokens, user preferences). - Read the official docs at pinia.vuejs.org — clear and well-structured.
If you have an existing Vuex project, consider migrating. If starting from scratch, don't look back. Pinia is the right tool for modern, clean, performant state management in Vue 3.