A getter is a store's computed property for derived state. This episode covers simple getters, getters that access another store's state, parameterized getters that return a function, and the memoization behavior that distinguishes getters without arguments from parameterized getters.

Not every value deserves to be stored in state. Values that can be computed from other state — total price, filtered lists, login status — should be recalculated automatically through getters. This keeps a single source of truth, and the UI can't possibly become inconsistent.
Episode 6 covers getters thoroughly: simple getters that access their own state, getters that read another store, parameterized getters for dynamic lookups, and the memoization behavior that often surprises people.
The most basic getter receives state and returns a derived value:
export const useCartStore = defineStore('cart', {
state: () => ({
items: [
{ name: 'Coffee', price: 15000, qty: 2 },
{ name: 'Tea', price: 8000, qty: 1 },
],
}),
getters: {
totalItems: (state) =>
state.items.reduce((sum, item) => sum + item.qty, 0),
totalPrice: (state) =>
state.items.reduce((sum, item) => sum + item.price * item.qty, 0),
},
})state.items.reduce(...) computes the total quantity and the total price. Because these getters are computed, their values are cached and only recalculated when state.items changes — no need to rewrite the same calculation in multiple components.
Getters aren't limited to their own store's state. You can call another store inside a getter:
import { useUserStore } from '@/stores/user'
export const useDashboardStore = defineStore('dashboard', {
state: () => ({ greeting: 'Welcome' }),
getters: {
fullGreeting: (state) => {
const userStore = useUserStore()
return `${state.greeting}, ${userStore.name}!`
},
},
})useUserStore() inside a getter looks like calling a store anywhere, but it's safe in Pinia: the getter only executes when the calling store is already active. This pattern is used to combine data across stores without copies.
A getter can accept arguments if it returns a function:
export const useProductStore = defineStore('products', {
state: () => ({
items: [
{ id: 1, name: 'Keyboard', price: 350000 },
{ id: 2, name: 'Mouse', price: 120000 },
],
}),
getters: {
findById: (state) => (id: number) =>
state.items.find((item) => item.id === id),
totalOf: (state) => (id: number) =>
state.items.filter((item) => item.id === id)
.reduce((sum, item) => sum + item.price, 0),
},
})findById: (state) => (id: number) => ... produces a getter called like store.findById(2). This form is ideal for dynamic lookups.
An important difference between the two getter forms:
const item2 = store.findById(2)
const item2Lagi = store.findById(2)
console.log(item2 === item2Lagi) // falseBecause store.findById(2) creates a new function on every access, the returned result is a new object — not the same instance. This usually isn't a problem, but it's important to be aware of when comparing object references.
Tip
If a parameterized getter is used in a template, the resulting value stays reactive as long as the arguments and the state it reads are reactive — only the wrapping function isn't cached.
Episode 6 equips you with getters in full. You can now write simple getters, combine data from another store inside a getter, create dynamic lookups with parameterized getters, and understand the memoization rules for both.
Key takeaways:
state as its first argument.useXStore().In the next episode, episode 7, we'll discuss actions and async logic — how to change state with logic, async actions that call APIs with loading and error status, and how actions call other stores. This is where the store starts interacting with the outside world.