Generated banner for Pinia
Generated logo for Pinia

Pinia

Anthony FuCommunityCreated by Anthony Fu

  • Skill
  • Tool integration
  • Software development
  • MIT
  • No issues found

From the author

Pinia official Vue state management library, type-safe and extensible. Use when defining stores, working with state/getters/actions, or implementing store patterns in Vue apps.
— the description by Anthony Fu

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

Pinia is the official state management library for Vue, offering an intuitive API for managing application state. It supports both Options and Composition API styles, provides first-class TypeScript support, and includes built-in devtools integration. The library facilitates store creation, state management, and complex logic handling through actions and getters, with specific support for server-side rendering and Nuxt integration.

What it does

  • Defines stores using Options or Composition API patterns
  • Provides reactive state management with getters and actions
  • Supports store-to-store communication and composable integration
  • Enables store extensibility through custom plugins
  • Facilitates server-side rendering and state hydration
  • Supports hot module replacement for development workflows

When to use it

  • When implementing centralized state management in Vue applications
  • When requiring type-safe store definitions in TypeScript projects
  • When building Nuxt applications needing integrated state handling
  • When needing to preserve state during development via HMR
  • When testing components that rely on shared store state

Advantages

  • First-class TypeScript support for type-safe stores
  • Seamless integration with Nuxt and server-side rendering
  • Flexible store definition styles for different architectural needs
  • Built-in support for devtools and hot module replacement
  • Extensible architecture via plugin system

Usage examples

“How do I define a basic store in Pinia?”
You use the defineStore function to create a store, providing a unique ID and either an object for Options API or a function for Composition API.
“How can I preserve state during development?”
You can implement Hot Module Replacement (HMR) by using the acceptHMRUpdate function within your store definition file.
“How do I use a store inside a Nuxt middleware?”
You access the store by passing the pinia instance from useNuxtApp to your store function, ensuring it is available in the middleware context.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/antfu/pinia/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip pinia.zip -d .agents/skills

Documentation by Anthony Fu

Pinia

Pinia is the official state management library for Vue, designed to be intuitive and type-safe. It supports both Options API and Composition API styles, with first-class TypeScript support and devtools integration.

The skill is based on Pinia v4.0.3, generated at 2026-09-25.

Core References

TopicDescriptionReference
StoresDefining stores, state, getters, actions, storeToRefs, subscriptionscore-stores

Features

Extensibility
TopicDescriptionReference
PluginsExtend stores with custom properties, state, and behaviorfeatures-plugins
Composability
TopicDescriptionReference
ComposablesUsing Vue composables within stores (VueUse, etc.)features-composables
Composing StoresStore-to-store communication, avoiding circular dependenciesfeatures-composing-stores

Best Practices

TopicDescriptionReference
TestingUnit testing with @pinia/testing, mocking, stubbingbest-practices-testing
Outside ComponentsUsing stores in navigation guards, plugins, middlewaresbest-practices-outside-component

Advanced

TopicDescriptionReference
SSRServer-side rendering, state hydrationadvanced-ssr
NuxtNuxt integration, auto-imports, SSR best practicesadvanced-nuxt
HMRHot module replacement for developmentadvanced-hmr

Key Recommendations

  • Prefer Setup Stores for complex logic, composables, and watchers

  • Use storeToRefs() when destructuring state/getters to preserve reactivity

  • Actions can be destructured directly - they're bound to the store

  • Call stores inside functions not at module scope, especially for SSR

  • Add HMR support to each store for better development experience

  • Use @pinia/testing for component tests with mocked stores

Files

references/core-stores.md


name: stores description: Defining stores, state, getters, and actions in Pinia

Pinia Stores

Stores are defined using defineStore() with a unique name. Each store has three core concepts: state, getters, and actions.

Defining Stores

Option Stores

Similar to Vue's Options API:

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    name: 'Eduardo',
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

Think of state as data, getters as computed, and actions as methods.

Uses Composition API syntax - more flexible and powerful:

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

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const name = ref('Eduardo')
  const doubleCount = computed(() => count.value * 2)

  function increment() {
    count.value++
  }

  return { count, name, doubleCount, increment }
})

In Setup Stores: ref() → state, computed() → getters, function() → actions.

Important: You must return all state properties for Pinia to track them.

Using Stores
<script setup>
import { useCounterStore } from '@/stores/counter'

const store = useCounterStore()
// Access: store.count, store.doubleCount, store.increment()
</script>
Destructuring with storeToRefs
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'

const store = useCounterStore()

// ❌ Breaks reactivity
const { name, doubleCount } = store

// ✅ Preserves reactivity for state/getters
const { name, doubleCount } = storeToRefs(store)

// ✅ Actions can be destructured directly
const { increment } = store
</script>

State

State is defined as a function returning the initial state.

TypeScript

Type inference works automatically. For complex types:

interface UserInfo {
  name: string
  age: number
}

export const useUserStore = defineStore('user', {
  state: () => ({
    userList: [] as UserInfo[],
    user: null as UserInfo | null,
  }),
})

Or use an interface for the return type:

interface State {
  userList: UserInfo[]
  user: UserInfo | null
}

export const useUserStore = defineStore('user', {
  state: (): State => ({
    userList: [],
    user: null,
  }),
})
Accessing and Modifying
const store = useStore()
store.count++
<input v-model="store.count" type="number" />
Mutating with $patch

Apply multiple changes at once:

// Object syntax
store.$patch({
  count: store.count + 1,
  name: 'DIO',
})

// Function syntax (for complex mutations)
store.$patch((state) => {
  state.items.push({ name: 'shoes', quantity: 1 })
  state.hasChanged = true
})
Resetting State

Option Stores have built-in $reset(). For Setup Stores, implement your own:

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)

  function $reset() {
    count.value = 0
  }

  return { count, $reset }
})
Subscribing to State Changes
cartStore.$subscribe((mutation, state) => {
  mutation.type // 'direct' | 'patch object' | 'patch function'
  mutation.storeId // 'cart'
  mutation.payload // patch object (only for 'patch object')

  localStorage.setItem('cart', JSON.stringify(state))
})

// Options
cartStore.$subscribe(callback, { flush: 'sync' }) // Immediate
cartStore.$subscribe(callback, { detached: true }) // Keep after unmount

flush: 'sync' caveats: it runs the callback after every state change instead of batching, so it can hurt performance if the callback is heavy or state changes often. It also matters for correctness in one niche case: a direct mutation (e.g. store.count++) happening synchronously right after a $patch() in the same tick won't trigger a non-sync subscription on its own — use flush: 'sync' if you must be notified of every such mutation.


Getters

Getters are computed values, equivalent to Vue's computed().

Basic Getters
getters: {
  doubleCount: (state) => state.count * 2,
}
Accessing Other Getters

Use this with explicit return type:

getters: {
  doubleCount: (state) => state.count * 2,
  doublePlusOne(): number {
    return this.doubleCount + 1
  },
},
Getters with Arguments

Return a function (note: loses caching):

getters: {
  getUserById: (state) => {
    return (userId: string) => state.users.find((user) => user.id === userId)
  },
},

Cache within parameterized getters:

getters: {
  getActiveUserById(state) {
    const activeUsers = state.users.filter((user) => user.active)
    return (userId: string) => activeUsers.find((user) => user.id === userId)
  },
},
Accessing Other Stores in Getters
import { useOtherStore } from './other-store'

getters: {
  combined(state) {
    const otherStore = useOtherStore()
    return state.localData + otherStore.data
  },
},

Actions

Actions are methods for business logic. Unlike getters, they can be asynchronous.

Defining Actions
actions: {
  increment() {
    this.count++
  },
  randomizeCounter() {
    this.count = Math.round(100 * Math.random())
  },
},
Async Actions
actions: {
  async registerUser(login: string, password: string) {
    try {
      this.userData = await api.post({ login, password })
    } catch (error) {
      return error
    }
  },
},
Accessing Other Stores in Actions
import { useAuthStore } from './auth-store'

actions: {
  async fetchUserPreferences() {
    const auth = useAuthStore()
    if (auth.isAuthenticated) {
      this.preferences = await fetchPreferences()
    }
  },
},

SSR: Call all useStore() before any await:

async orderCart() {
  // ✅ Call stores before await
  const user = useUserStore()

  await apiOrderCart(user.token, this.items)
  // ❌ Don't call useStore() after await in SSR
}
Subscribing to Actions
const unsubscribe = someStore.$onAction(
  ({ name, store, args, after, onError }) => {
    const startTime = Date.now()
    console.log(`Start "${name}" with params [${args.join(', ')}]`)

    after((result) => {
      console.log(`Finished "${name}" after ${Date.now() - startTime}ms`)
    })

    onError((error) => {
      console.warn(`Failed "${name}": ${error}`)
    })
  }
)

unsubscribe() // Cleanup

Keep subscription after component unmount:

someStore.$onAction(callback, true)

Options API Helpers

import { mapState, mapWritableState, mapActions } from 'pinia'
import { useCounterStore } from '../stores/counter'

export default {
  computed: {
    // Readonly state/getters
    ...mapState(useCounterStore, ['count', 'doubleCount']),
    // Writable state
    ...mapWritableState(useCounterStore, ['count']),
  },
  methods: {
    ...mapActions(useCounterStore, ['increment']),
  },
}

Accessing Global Providers in Setup Stores

import { inject } from 'vue'
import { useRoute } from 'vue-router'
import { defineStore } from 'pinia'

export const useSearchFilters = defineStore('search-filters', () => {
  const route = useRoute()
  const appProvided = inject('appProvided')

  // Don't return these - access them directly in components
  return { /* ... */ }
})

<!-- Source references: - https://pinia.vuejs.org/core-concepts/ - https://pinia.vuejs.org/core-concepts/state.html - https://pinia.vuejs.org/core-concepts/getters.html - https://pinia.vuejs.org/core-concepts/actions.html -->

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Anthony Fu
Publisher
Anthony Fu (Community)
Source
antfu/skills · skills/pinia · commit f63e9ec
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE.md)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Anthony Fu.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download pinia.zip