Plugins
Pinia stores can be fully extended thanks to a low level API. Here is a list of things you can do:
- Add new properties to stores
- Add new options when defining stores
- Add new methods to stores
- Wrap existing methods
- Intercept actions and its results
- Implement side effects like Local Storage
- Apply only to specific stores
Plugins are added to the pinia instance with
pinia.use()
. The simplest example is adding a static property to all stores by returning an object:
This is useful to add global objects like the router, modal, or toast managers.
Introduction
A Pinia plugin is a function that optionally returns properties to be added to a store. It takes one optional argument, a context :
This function is then passed to
pinia
with
pinia.use()
:
Plugins are only applied to stores created
after the plugins themselves, and after
pinia
is passed to the app
, otherwise they won't be applied.
Augmenting a Store
You can add properties to every store by simply returning an object of them in a plugin:
You can also set the property directly on the
store
but
if possible use the return version so they can be automatically tracked by devtools
:
Any property
returned
by a plugin will be automatically tracked by devtools so in order to make
hello
visible in devtools, make sure to add it to
store._customProperties
in dev mode only
if you want to debug it in devtools:
Note that every store is wrapped with
reactive
, automatically unwrapping any Ref (
ref()
,
computed()
, ...) it contains:
This is why you can access all computed properties without
.value
and why they are reactive.
Adding new state
If you want to add new state properties to a store or properties that are meant to be used during hydration, you will have to add it in two places :
-
On the
store
so you can access it withstore.myState
-
On
store.$state
so it can be used in devtools and, be serialized during SSR .
On top of that, you will certainly have to use a
ref()
(or other reactive API) in order to share the value across different accesses:
Note that state changes or additions that occur within a plugin (that includes calling
store.$patch()
) happen before the store is active and therefore
do not trigger any subscriptions
.
WARNING
If you are using
Vue 2
, Pinia is subject to the
same reactivity caveats
as Vue. You will need to use
Vue.set()
(Vue 2.7) or
set()
(from
@vue/composition-api
for Vue <2.7) for when creating new state properties like
secret
and
hasError
:
Resetting state added in plugins
By default,
$reset()
will not reset state added by plugins but you can override it to also reset the state you add:
Adding new external properties
When adding external properties, class instances that come from other libraries, or simply things that are not reactive, you should wrap the object with
markRaw()
before passing it to pinia. Here is an example adding the router to every store:
Calling
$subscribe
inside plugins
You can use store.$subscribe and store.$onAction inside plugins too:
Adding new options
It is possible to create new options when defining stores to later on consume them from plugins. For example, you could create a
debounce
option that allows you to debounce any action:
The plugin can then read that option to wrap actions and replace the original ones:
Note that custom options are passed as the 3rd argument when using the setup syntax:
TypeScript
Everything shown above can be done with typing support, so you don't ever need to use
any
or
@ts-ignore
.
Typing plugins
A Pinia plugin can be typed as follows:
Typing new store properties
When adding new properties to stores, you should also extend the
PiniaCustomProperties
interface.
It can then be written and read safely:
PiniaCustomProperties
is a generic type that allows you to reference properties of a store. Imagine the following example where we copy over the initial options as
$options
(this would only work for option stores):
We can properly type this by using the 4 generic types of
PiniaCustomProperties
:
TIP
When extending types in generics, they must be named
exactly as in the source code
.
Id
cannot be named
id
or
I
, and
S
cannot be named
State
. Here is what every letter stands for:
- S: State
- G: Getters
- A: Actions
- SS: Setup Store / Store
Typing new state
When adding new state properties (to both, the
store
and
store.$state
), you need to add the type to
PiniaCustomStateProperties
instead. Differently from
PiniaCustomProperties
, it only receives the
State
generic:
Typing new creation options
When creating new options for
defineStore()
, you should extend the
DefineStoreOptionsBase
. Differently from
PiniaCustomProperties
, it only exposes two generics: the State and the Store type, allowing you to limit what can be defined. For example, you can use the names of the actions:
TIP
There is also a
StoreGetters
type to extract the
getters
from a Store type. You can also extend the options of
setup stores
or
option stores
only
by extending the types
DefineStoreOptions
and
DefineSetupStoreOptions
respectively.
Nuxt.js
When
using pinia alongside Nuxt
, you will have to create a
Nuxt plugin
first. This will give you access to the
pinia
instance:
INFO
The above example is using TypeScript, you have to remove the type annotations
PiniaPluginContext
and
Plugin
as well as their imports if you are using a
.js
file.
Nuxt.js 2
If you are using Nuxt.js 2, the types are slightly different: