Learn Pinia - Custom Plugin & Advanced Lifecycle
Episode 18 of 23

Learn Pinia - Custom Plugin & Advanced Lifecycle

Plugins aren't just for persistence. This episode covers $onAction for tracking action calls, $subscribe that writes state to JSON, and global plugins for analytics and error handlers. You also learn to extend stores with global helpers and external library integration.

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

Introduction

Episode 8 introduced the plugin basics. Now we level up: plugins for analytics, error handling, and logging — the things you don't want to rewrite in every store. It's these advanced plugins that make application observability run without cluttering store code.

Episode 18 covers $onAction for tracking, $subscribe that writes state to JSON, global plugins for analytics and error handlers, and how to extend stores with global helpers.

Tracking Actions with $onAction

$onAction listens to all action calls in a store — including arguments, results, and errors:

JSAction tracking plugin
export function actionTrackerPlugin({ store }) {
  store.$onAction(({ name, args, after, onError }) => {
    console.time(`action:${name}`)
    after((result) => {
      console.timeEnd(`action:${name}`)
      console.log('Result', name, result)
    })
    onError((error) => {
      console.error('Action failed', name, error)
    })
  })
}

store.$onAction(({ name, args, after, onError }) => ...) gives complete hooks for the action lifecycle. after is called when the action succeeds, onError when it fails. This is the foundation for building loggers or action performance metrics.

Subscribes That Write to JSON

$subscribe can be used to periodically save state snapshots, for example for debugging sessions:

JSSubscribe writing state JSON
let terakhir = 0
 
store.$subscribe((_mutation, state) => {
  const sekarang = Date.now()
  if (sekarang - terakhir > 5000) {
    terakhir = sekarang
    localStorage.setItem(
      `snapshot-${store.$id}`,
      JSON.stringify(state),
    )
  }
})

JSON.stringify(state) inside the subscriber creates a state snapshot that can be analyzed for bug reproduction. The time limit prevents snapshots from flooding storage.

Global Plugin: Analytics

A single plugin can handle analytics for every store:

JSGlobal analytics plugin
export function analyticsPlugin({ store }) {
  store.$onAction(({ name, after }) => {
    after(() => {
      trackEvent('store_action', { store: store.$id, action: name })
    })
  })
}

trackEvent('store_action', ...) sends the store name and action every time an action succeeds. The marketing or monitoring team only needs to read this event, with no changes in any component.

Global Plugin: Error Handler

Errors that occur in actions can be centralized in one place:

JSPlugin error handler
export function errorHandlerPlugin({ store }) {
  store.$onAction(({ onError }) => {
    onError((error) => {
      reportError(error, { storeId: store.$id })
    })
  })
}
 
function reportError(error: unknown, info: object) {
  console.error('Error from store', info, error)
}

store.$onAction(({ onError }) => ...) captures errors from all of a store's actions. With this plugin, error handling is written once and centralized — not scattered across every action.

Extending Stores with Global Helpers

Plugins can also attach helpers used by all stores:

JSGlobal helper from a plugin
pinia.use(({ store }) => {
  store.$error = (message: string) => {
    console.error(`[${store.$id}]`, message)
  }
})

store.$error = (message) => ... adds a helper method to every store. To make TypeScript recognize it, use PiniaCustomProperties as discussed in episode 16.

Warning

Choose between plugins or manual actions wisely. Use plugins for things that are uniform across stores; use actions for domain-specific logic. Don't force everything into plugins.

Closing

Episode 18 refines your plugin capabilities. You can now use $onAction for tracking and error handling, write state snapshots to JSON, build global analytics and error handler plugins, and add helpers to all stores.

Key takeaways:

  • $onAction provides before, after, and onError hooks for every action.
  • $subscribe can write state snapshots to storage.
  • Analytics only needs to be written once in a global plugin.
  • Centralized error handlers via plugins reduce duplication.
  • Global helpers are added through store.$name.
  • Don't force all logic into plugins.

In the next episode, episode 19, we'll discuss scaling store architecture — the stores/ folder structure per feature, action and getter naming conventions, splitting large stores, and code-splitting with dynamic store registration for large-scale applications.

Learn Pinia - Custom Plugin & Advanced Lifecycle | Learning Pinia