Learn Pinia - Core Concepts & Main Architecture
Episode 2 of 23

Learn Pinia - Core Concepts & Main Architecture

This episode dissects the anatomy of a Pinia store: state for reactive data, getters for derived state, and actions for mutations and side effects. You also understand how Pinia works behind the scenes, the createPinia and app.use installation, and the difference between Options stores and Setup stores.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Every state management framework has core concepts. In Pinia, that concept is called a store: a reactive entity that holds data, derived logic, and actions together in a single unit. Understanding the anatomy of a store is the key to understanding this entire series.

Episode 2 dissects Pinia's main architecture: the three parts that make up a store, how Pinia works behind the scenes, and the two styles of writing stores. You don't need to write complex stores yet — this episode builds a shared vocabulary so the following episodes run smoothly.

Store Anatomy

state: Reactive Data

state is a store's source of truth. It's defined as a function that returns an object, so every store instance gets truly fresh data:

JSComplete store anatomy
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    title: 'Counter App',
  }),
  getters: {
    double: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

defineStore('counter', ...) takes a unique id as its first argument — this id is what distinguishes one store from another within the app.

getters: Derived State

getters are the store's computed properties. Their values are calculated from state and automatically updated when state changes. Because they're computed, their results are memoized as long as their dependencies don't change.

actions: Mutations and Side Effects

actions are methods that may change state, call APIs, set timers, and call other stores. Inside an action, this points to the store instance, so you can access state and other getters.

How It Works Behind the Scenes

Stores Are Wrapped in reactive

When a store is first used, Pinia creates that store as a reactive object inside the Pinia instance. As a result, accessing store.count in a component tracks the dependency just like a regular ref — when the value changes, the consuming components re-render along with it.

storeToRefs and the Plugin System

Because a store is a reactive object, ordinary destructuring destroys reactivity. Pinia provides storeToRefs(store) to extract state and getters as refs — we'll cover it thoroughly in episode 5. On the other hand, Pinia's plugin system lets you add properties to all stores globally, such as localStorage sync or a logger.

Key Concepts

createPinia and app.use

Pinia is installed into the app in two steps: create an instance with createPinia(), then register it with app.use(pinia). In apps with more than one Pinia instance — for example during SSR — each request gets its own instance:

JSInstall Pinia into the app
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import App from './App.vue'
 
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')

createPinia() creates the instance, and app.use(pinia) activates it for the entire app. After this, stores can be called from anywhere inside components.

Options Store vs Setup Store

Pinia offers two ways of writing stores:

  • Options store: uses an object with state, getters, actions — close to Vuex, comfortable for teams that just migrated.
  • Setup store: uses a Composition API function that returns refs, computeds, and functions — more flexible because you can use other composables inside the store.

Both styles write the same thing in different shapes:

JSTwo styles of writing stores
// Options store
export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: { double: (state) => state.count * 2 },
  actions: { increment() { this.count++ } },
})
 
// Setup store
export const useCounterStoreSetup = defineStore('counter-setup', () => {
  const count = ref(0)
  const double = computed(() => count.value * 2)
  function increment() {
    count.value++
  }
  return { count, double, increment }
})

Notice the difference: in the Options store, this.count++ is used because state and getters are available as properties. In the Setup store, you use ref and computed directly as you would inside a component — there's no this, and the results are returned explicitly. Both forms access store.count, store.double, and store.increment() the same way from components.

Both have equal capabilities; the choice is a matter of taste and team convention. The details are compared directly in episode 4.

Warning

A store must have a unique id. If two stores use the same id, Pinia will throw a warning and one of them will overwrite the other in DevTools.

Closing

Episode 2 gives you the map of Pinia's architecture. You now understand the three parts that make up a store — state, getters, actions — how stores work as reactive objects, installation via createPinia and app.use, and the two styles of writing stores.

Key takeaways:

  • A store consists of state (data), getters (derived values), and actions (mutations plus side effects).
  • defineStore needs a unique id as its first argument.
  • A store is a reactive object, so accessing its properties is tracked automatically.
  • storeToRefs is required when destructuring so reactivity isn't lost.
  • Installation: createPinia() then app.use(pinia).
  • Options stores and Setup stores have equal capabilities.

In the next episode, episode 3, we'll do the installation setup and create the first store — install pinia, register it with the app, define a counter store with an increment action, and use it directly inside a Vue component. It's time to write real code.