Modular Stores

One File Per Domain

Modular Stores

Split stores by domain into separate files under stores/. Compose them by importing one from another — no global tree, no nesting.

4 min read Level 2/5 #vue#pinia#modules
What you'll learn
  • Organize stores by domain (auth, cart, users)
  • Compose stores by calling one from another
  • Type the returned store automatically

Vuex had a global tree with namespaced modules. Pinia is flatter — each store is an independent file. Compose them by importing.

File Layout

src/stores/
  auth.ts
  cart.ts
  products.ts

Store Per Domain

// stores/auth.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useAuthStore = defineStore('auth', () => {
  const userId = ref<string | null>(null)
  const isLoggedIn = computed(() => userId.value !== null)
  function login(id: string) { userId.value = id }
  function logout() { userId.value = null }
  return { userId, isLoggedIn, login, logout }
})

Composing Stores

One store can use another — just call its use…Store() inside.

// stores/cart.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useAuthStore } from './auth'

export const useCartStore = defineStore('cart', () => {
  const auth = useAuthStore()
  const items = ref<{ id: string; qty: number }[]>([])

  const canCheckout = computed(() => auth.isLoggedIn && items.value.length > 0)

  function add(id: string) {
    items.value.push({ id, qty: 1 })
  }
  return { items, canCheckout, add }
})

Typing

Setup-style stores infer everything. The return type of useCartStore() is automatically { items, canCheckout, add } with full types — no manual generics needed.

Persisting State →