mrkeyoor.com_
Sat 08 Aug 21:01 UTC
npmWeb Frontendupdated 08 Aug 2026

pinia

Pinia is the official application-state store for Vue 3. A store groups reactive state, computed getters, and actions behind a typed `use...Store()` function, while Vue Devtools records state changes and actions. You can define stores with an Options-style object or a Composition API setup function, split them by domain, call them from components or routing code, extend them with plugins, and hydrate them during server rendering. It replaces Vuex for new Vue applications without Vuex's mutations or nested module ceremony.

Verdict

Pinia is the default state-store choice for a current Vue application, and its simple API stays out of the way. Install it only for genuinely shared client state, and budget for ESM, required peers, persistence choices, and SSR isolation rather than assuming those details are automatic.

API stability4/5The everyday API has stayed centered on defineStore, state, getters, actions, storeToRefs, and createPinia across multiple majors. Version 4 is intentionally a technical break: its release notes make the package ESM-only, upgrade the required Devtools API, raise the Vue and TypeScript peer floors, and refactor diagnostics. Application store code usually survives, but build and test configuration may not.
Docs5/5The official site covers Options and setup stores, state patching, action hooks, plugins, stores outside components, Vue/Vite SSR, Nuxt, HMR, Vuex migration, testing, composable caveats, and a generated API reference. Warnings are concrete, including broken reactivity from direct destructuring, mandatory returned refs in setup stores, SSR serialization safety, and testing actions being stubbed by default.
Maintenance5/5Pinia 4.0.2 was released on 2026-07-15, the repository was pushed on 2026-07-27, and GitHub reported 26 open issues and pull requests at collection time. The v4 branch has active CI and coordinated releases for Pinia, the Nuxt integration, and the testing package. It is maintained under the Vue.js organization and is identified by Vue's own guide as the successor to Vuex.
Ecosystem5/5Pinia is the official Vue store, integrates with Vue Devtools and Nuxt, has a separate testing package, and supports a plugin API used for persistence and other extensions. npm recorded 4,560,044 downloads for 2026-07-31 through 2026-08-06, while GitHub showed 14,689 stars. Its strength is specifically the Vue 3 ecosystem; it is not useful as a framework-neutral shared-state layer.

Use it if

  • You have state shared across unrelated Vue components and prop drilling or ad hoc module refs have become hard to trace
  • You want Vue-native reactivity, first-class TypeScript inference, Vue Devtools inspection, and hot-module replacement in one small store layer
  • You are starting or modernizing a Vue 3.5 application and want the state library recommended by the Vue documentation
  • You need domain stores that can call one another without maintaining a nested module tree
Skip it if

Setup reality

For Pinia 4, install both `pinia` and `@vue/devtools-api`; the latter is a required peer dependency, not an optional dev-only convenience. The peer range also requires Vue ^3.5.11, and TypeScript users need TypeScript 5.6 or newer. Pinia 4 is ESM-only. Create exactly one `createPinia()` instance for a normal SPA and register it with `app.use(pinia)` before any component calls a store. Calls made outside component setup, such as router guards or API modules, must run after installation or receive the pinia instance explicitly. Destructuring state directly breaks reactivity; use `storeToRefs()`, while actions can be destructured normally. Setup stores must return every state ref or SSR, Devtools, and plugins can break, and unlike Option stores they do not get a built-in `$reset()`. Persistence is not built in, so either subscribe and serialize carefully or vet a plugin. SSR adds the sharpest edge: create a fresh Pinia per request, serialize `pinia.state.value` with an XSS-safe serializer such as devalue, and activate the request's instance before calling stores outside setup. Nuxt users should use the matching `@pinia/nuxt` module rather than hand-rolling hydration. Tests need `setActivePinia(createPinia())` for unit work or the separate `@pinia/testing` package; its default behavior stubs actions, which can make a test pass without executing business logic unless `stubActions: false` is set.

Patterns

Install one Pinia root in a Vue appinstall-plugin

import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';

const app = createApp(App);
app.use(createPinia());
app.mount('#app');

Pinia 4 also requires `@vue/devtools-api` to be installed. Register Pinia before any mounted component calls a store.

Define a typed Option storedefine-option-store

import { defineStore } from 'pinia';

export const useCartStore = defineStore('cart', {
  state: () => ({ items: [] as { id: string; price: number }[] }),
  getters: {
    total: (state) => state.items.reduce((sum, item) => sum + item.price, 0),
  },
  actions: {
    add(item: { id: string; price: number }) {
      this.items.push(item);
    },
  },
});

The id must be unique across the application because Pinia uses it for registration, Devtools, and hydration.

Define a Composition API storedefine-setup-store

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

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0);
  const doubled = computed(() => count.value * 2);
  function increment() { count.value += 1; }
  function reset() { count.value = 0; }
  return { count, doubled, increment, reset };
});

Return every state ref. Hidden or readonly state can break SSR, Devtools, and plugins; setup stores also need their own reset action.

Destructure store state without losing reactivitykeep-destructured-state-reactive

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

const cart = useCartStore();
const { items, total } = storeToRefs(cart);
const { add } = cart;
</script>

Use storeToRefs for state, getters, and reactive plugin properties. Actions are already bound and can be destructured directly.

Apply related state changes as one patchpatch-state

const cart = useCartStore();

cart.$patch((state) => {
  state.items.push({ id: 'book', price: 20 });
  state.coupon = 'READ20';
});

The function form is preferable for collection mutations. It groups the changes into one Devtools entry and one subscription event.

Reset an Option storereset-option-store

const cart = useCartStore();
cart.$reset();

$reset recreates an Option store's initial state. Setup stores do not provide it automatically; define and expose a reset action there.

Put an async request in an actionrun-async-action

export const useUserStore = defineStore('user', {
  state: () => ({ profile: null as User | null, loading: false }),
  actions: {
    async load(id: string) {
      this.loading = true;
      try {
        this.profile = await api.getUser(id);
      } finally {
        this.loading = false;
      }
    },
  },
});

Actions may be async and can call other actions or stores. Pinia does not deduplicate requests or cache server data automatically.

Persist selected state changessubscribe-to-state

const cart = useCartStore();

const stop = cart.$subscribe((_mutation, state) => {
  localStorage.setItem('cart', JSON.stringify({ items: state.items }));
}, { detached: true });

// call stop() when this application-level subscription is no longer needed

detached keeps the subscription alive beyond the creating component. Validate and version persisted data before hydrating it in a real application.

Record action outcomesobserve-actions

const user = useUserStore();

const stop = user.$onAction(({ name, args, after, onError }) => {
  const started = performance.now();
  after(() => console.log(name, args, performance.now() - started));
  onError((error) => console.error(name, error));
}, true);

The second argument detaches the listener from the current component lifecycle. Avoid logging secrets carried in action arguments.

Use a store in a router guarduse-store-outside-component

import { createPinia } from 'pinia';
import { createApp } from 'vue';

export const pinia = createPinia();
createApp(App).use(pinia).use(router).mount('#app');

router.beforeEach((to) => {
  const auth = useAuthStore(pinia);
  if (to.meta.requiresAuth && !auth.user) return '/login';
});

Passing the instance makes ordering explicit. In SSR, never share this module-level root across requests; create one per request instead.

Add a property with a Pinia pluginextend-with-plugin

import { markRaw } from 'vue';

const pinia = createPinia();
pinia.use(({ store }) => {
  store.router = markRaw(router);
});
app.use(pinia);

Plugins apply only to stores created after the plugin is installed and after Pinia is registered on an app. Add a PiniaCustomProperties declaration for TypeScript.

Unit-test a store with a fresh roottest-store

import { beforeEach, expect, it } from 'vitest';
import { createPinia, setActivePinia } from 'pinia';

beforeEach(() => setActivePinia(createPinia()));

it('increments', () => {
  const counter = useCounterStore();
  counter.increment();
  expect(counter.count).toBe(1);
});

A fresh active Pinia prevents state leaking between tests. Component tests using @pinia/testing stub actions by default unless stubActions is false.

Alternatives

PackageRegistryPick it when
vuexnpmKeep it in an established Vuex application where migration cost outweighs removing mutations and modules
nanostoresnpmChoose it for tiny framework-agnostic stores shared across Vue, React, Svelte, or vanilla islands
@xstate/vuenpmChoose it when workflows need explicit states, guarded transitions, cancellation, and statechart tooling
@vueuse/corenpmChoose composables alone when the shared concern is narrow and does not need a centralized store or Devtools timeline