pinia-plugin-action-flow

Give more power to your Pinia store actions
1,884
Install
npm install pinia-plugin-action-flow
Latest Version:0.1.1
Vue:3
License:MIT
Last Updated:Sep 21, 2026
Links: GitHub  ·  npm
Maintainer: alexbidaud

Pinia Plugin Action Flow

A powerful Pinia plugin that enables you to define custom callbacks (flows) that execute before and after store actions. This plugin allows you to add cross-cutting concerns like logging, validation, and side effects to your Pinia actions in a clean and declarative way.

Features

  • Before Hooks: Execute logic before an action runs
  • After Hooks: Execute logic after an action completes
  • Flexible Configuration: Define flows using functions or method names
  • Type-Safe: Full TypeScript support with proper type definitions
  • Non-Intrusive: Works seamlessly with existing Pinia stores
  • Debug Support: Optional debug logging for development

Installation

npm install pinia-plugin-action-flow
# or
yarn add pinia-plugin-action-flow

Setup

  1. Import the plugin and create your Pinia app:
import { createPinia } from 'pinia'
import { ActionsFlows } from 'pinia-plugin-action-flow'
import { createPlugin } from 'pinia-plugin-subscription'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()

pinia.use(
  createPlugin([ActionsFlows])
)

app.use(pinia)
  1. Configure your store with action flows using the storeOptions property:
import { defineStore } from "pinia"

export const useMyStore = defineStore('myStore', () => {
  // store implementation
}, {
  storeOptions: {
    flows: {
      // define your action flows here
    }
  }
})

Usage

Basic Example

Define before and after hooks for your actions:

import { defineStore } from "pinia"
import { ref } from "vue"

const storeOptions = {
  flows: {
    setMyState: {
      before: 'beforeSetMyState',      // Reference to a store method
      after: afterSetMyStateCallback    // Or use a direct function
      onError: customErrorFunction
    }
  }
}

export const useOnActionStore = defineStore('onActionStore', () => {
  const myState = ref<string>()

  function setMyState(value: string) {
    myState.value = value
  }

  function beforeSetMyState(args: string[]) {
    console.log('Before setting myState with args:', args)
    // Modify arguments before the action executes
    args[0] = args[0].toUpperCase()
  }

  return { myState, setMyState, beforeSetMyState }
}, {
  storeOptions
})

function afterSetMyStateCallback(
  value: ActionFlowDefaultParameters[] 
    | { args: ActionFlowDefaultParameters[], result: ActionFlowDefaultParameters | ActionFlowDefaultParameters[] }
) {
  console.log('After setting myState to:', value)
}

function customErrorFunction(error: unknown) {
  //...
}

Using the Store

import { useOnActionStore } from '@/stores/onAction'

const store = useOnActionStore()

// When you call the action, the flows are automatically triggered
store.setMyState('hello')
// Logs:
// > "Before setting myState with args: ['hello']"
// > Value is now: "HELLO"
// > "After setting myState to: ['HELLO']"

console.log(store.myState) // "HELLO"

Configuration

Action Flow Types

interface ActionFlow {
  before?: Function | string   // Callback before action execution
  after?: Function | string    // Callback after action execution
}

type ActionFlows = Record<string, ActionFlow>

Before Hooks

The before hook receives the action arguments and can modify them before the action executes:

{
  flows: {
    myAction: {
      before: (args: any[]) => {
        // args is the array of arguments passed to the action
        console.log('Action will be called with:', args)
        // You can modify arguments
        args[0] = args[0].toUpperCase()
      }
    }
  }
}

Using a Store Method:

{
  flows: {
    myAction: {
      before: 'beforeMyAction'  // Must be a method in your store
    }
  }
}

// In your store:
function beforeMyAction(args: string[]) {
  console.log('Before my action:', args)
}

After Hooks

The after hook receives an object containing both the original arguments and the action result:

{
  flows: {
    myAction: {
      after: (payload: { args: any[], result: any }) => {
        console.log('Action arguments:', payload.args)
        console.log('Action result:', payload.result)
      }
    }
  }
}

If the action doesn't return a result, the args will be passed directly without the wrapper object.

Advanced Example

Here's a more comprehensive example with multiple flows:

import { defineStore } from "pinia"
import { ref } from "vue"

interface User {
  id: number
  name: string
}

export const useUserStore = defineStore('userStore', () => {
  const users = ref<User[]>([])
  const loading = ref(false)
  const errors = ref<string[]>([])

  // Actions
  function addUser(name: string) {
    const newUser: User = {
      id: users.value.length + 1,
      name
    }
    users.value.push(newUser)
    return newUser
  }

  function deleteUser(id: number) {
    users.value = users.value.filter(u => u.id !== id)
  }

  // Before/After hooks
  function beforeAddUser(args: ActionFlowDefaultParameters[]) {
    console.log('Adding user:', args[0])
    if (!args[0] || args[0].trim() === '') {
      console.warn('User name cannot be empty')
      args[0] = 'Unknown User'
    }
  }

  function afterAddUser(
    payload: ActionFlowDefaultParameters[]
      | {
          args: ActionFlowDefaultParameters[],
          result: ActionFlowDefaultParameters | ActionFlowDefaultParameters[]
  }) {
    console.log('User added:', payload.result)
    // Trigger analytics, notifications, etc.
  }

  function beforeDeleteUser(args: number[]) {
    loading.value = true
    console.log('Deleting user with id:', args[0])
  }

  function afterDeleteUser() {
    loading.value = false
    console.log('User deleted')
  }

  return {
    users,
    loading,
    errors,
    addUser,
    deleteUser,
    beforeAddUser,
    afterAddUser,
    beforeDeleteUser,
    afterDeleteUser
  }
}, {
  storeOptions: {
    flows: {
      addUser: {
        before: 'beforeAddUser',
        after: 'afterAddUser'
      },
      deleteUser: {
        before: 'beforeDeleteUser',
        after: 'afterDeleteUser'
      }
    }
  }
})

API Reference

Plugin Exports

import { ActionsFlows, PLUGIN_NAME } from 'pinia-plugin-action-flow'

// ActionsFlows: The plugin instance to use with pinia.use()
// PLUGIN_NAME: The plugin identifier string ('pinia-plugin-action-flow')

Store Options

Extend your defineStore options with:

interface PluginStoreOptions {
  storeOptions: {
    flows?: ActionFlows
  }
}

Use Cases

  • Validation: Validate action arguments before execution
  • Logging: Log all action calls and results for debugging
  • Analytics: Track user interactions and store mutations
  • Side Effects: Trigger API calls, notifications, or other operations
  • Caching: Implement caching logic around actions
  • Access Control: Check permissions before allowing actions to execute
  • Transformation: Transform data before or after action execution

Performance Considerations

  • Flows are debounced with a 250ms timeout to prevent duplicate execution
  • Each action flow is tracked to avoid re-triggering during nested calls
  • Use the plugin judiciously for performance-critical applications

Debugging

Enable debug logging by passing debug configuration when initializing your store:

// The plugin includes built-in debug support through pinia-plugin-subscription
// Check the Pinia DevTools for flow execution details

TypeScript Support

The plugin is fully typed with TypeScript. Action flows support both function references and string method names with proper type inference:

// Type-safe flow definition
const storeOptions = {
  flows: {
    myAction: {
      before: (args: any[]) => void,      // Function callback
      after: 'myAfterMethod'               // Method name (string)
    }
  }
} as const

License

See LICENSE file for details.

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

Related Packages

pinia-plugin-extending-store

A Pinia plugin to create reusable stores that can extend other Pinia stores.

3,030 0
@tauri-store/pinia

Persistent Pinia stores for Tauri

21,382 137
pinia-plugin-logger

Logger for Pinia

2,311 4
pinia-plugin-subscription

Pinia plugin for Vue.js that helps building Pinia plugins by centralizing subscr...

8,501 0

Version History

Version Released Vue License
0.1.1 ^3.0.0 MIT
0.1.0 ^3.0.0 MIT
0.0.2 ^3.5.24
0.0.1 ^3.5.24
Pre-releases (8)
Version Released Vue License
0.1.1-beta.0 ^3.0.0 MIT
0.0.3-beta.1 ^3.5.24
0.0.0-beta.5 ^3.5.24
0.0.0-beta.4 ^3.5.24
0.0.0-beta.3 ^3.5.24