https://github.com/cawa-93/electron-nano-store
A minimalistic, secure, type-safe data store for Electron
https://github.com/cawa-93/electron-nano-store
electron persistent-storage typescript
Last synced: 3 months ago
JSON representation
A minimalistic, secure, type-safe data store for Electron
- Host: GitHub
- URL: https://github.com/cawa-93/electron-nano-store
- Owner: cawa-93
- License: mit
- Created: 2022-12-24T13:05:51.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2025-08-31T12:36:48.000Z (9 months ago)
- Last Synced: 2025-08-31T14:35:10.340Z (9 months ago)
- Topics: electron, persistent-storage, typescript
- Language: TypeScript
- Homepage: https://www.npmjs.com/package/electron-nano-store
- Size: 131 KB
- Stars: 10
- Watchers: 2
- Forks: 1
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
[](https://stand-with-ukraine.pp.ua)
---
# Nano Electron store
A minimalistic, secure, type-safe data store for Electron. This package is flat wrapper around [fs-nano-store] and
with a few strict checks to allow safely use it in renderer.
> **Note**
> See the full **[fs-nano-store]** documentation for more information about how store works
## Installation
```
npm install electron-nano-store
```
or
```
pnpm add electron-nano-store
```
or
```
yarn add electron-nano-store
```
## Usage
### Simple
Just expose in main world `defineStore` function and use it directly in your renderer anywhere.
```ts
// In Electron Preload Script
import { defineStore } from "electron-nano-store"
import { contextBridge } from 'electron'
contextBridge.exposeInMainWorld('defineStore', defineStore)
```
```ts
// In Renderer
const store = await defineStore('user')
store.set('role', 'admin')
console.log(store.get('role')) // -> 'admin'
```
### Recommended
As an additional safety precaution, you can choose not to expose a `defineStore` function, but instead expose an already
defined store.
The examples below also show an example of use with TypeScript.
```ts
// contracts.ts
export type UserStore = {
role: 'admin' | 'user'
}
```
```ts
// in Preload Script
import { defineStore } from "electron-nano-store"
import { contextBridge } from 'electron'
import type { UserStore } from 'contracts.ts'
const userStorePromise = defineStore('user')
contextBridge.exposeInMainWorld('userStorePromise', userStorePromise)
```
```ts
// In Renderer
import type { UserStore } from 'contracts.ts'
import type { defineStore } from 'electron-nano-store'
declare global {
interface Window {
userStorePromise: ReturnType>
}
}
const store = await window.userStorePromise
store.set('role', 'admin')
console.log(store.get('role')) // -> 'admin'
store.set('role', 'wrong-role') // TS Error: Argument of type '"wrong-role"' is not assignable to parameter of type '"admin" | "user"'
```
### Recommended with automatic type inference
The exchange of types between the preload and the renderer can be a little annoying and complicated. So you can
use [unplugin-auto-expose](https://github.com/cawa-93/unplugin-auto-expose) for automatic type inference
```ts
// in Preload Script
import { defineStore } from "electron-nano-store"
type UserStore = {
role: 'admin' | 'user'
}
export const userStorePromise = defineStore('user')
```
```ts
// In Renderer
import { userStorePromise } from '#preload'
const store = await userStorePromise
```
### In Main
This package was intentionally designed with many restrictions for use in preload. If you want to use it in main, or you
need more control you should use [fs-nano-store] directly
```ts
// In Main
import { defineStore } from 'fs-nano-store'
import { resolveStoreFilepath } from 'electron-nano-store'
import { app } from 'electron'
const store = await defineStore(
resolveStoreFilepath(
'store-name',
app.getPath('userData')
)
)
```
### Listen store changes in Renderer
[fs-nano-store] automatically tracks all changes to the store, and emit a `changed` event if the store has been changed
out of context.
However, electron does not allow you to expose `EventEmitter` to the main world. This means that even though it defines
and returns `change` property, you can't add listeners directly.
```ts
const { changes } = defineStore('store-name')
changes.addListener // undefined
```
To do this, you **must** define store in the preload context, add listeners there, and proxy all events to an
existing `EventTarget`, such as a `window`
```ts
// in Preload Script
const storePromise = defineStore('user')
storePromise.then(({ changes }) => {
changes.addListener(
'changed',
() => globalThis.dispatchEvent(new CustomEvent('user:changed')),
)
})
```
```ts
// in Renderer
globalThis.addEventListener(
'user:changed',
() => { /* ... */
}
)
```
## Security limitation
1. You can't somehow change where are storage files placed in filesystems.
It always in `electron.app.getPath('userData')` directory defined by electron.
Since the `defineStore` function may be exposed to a non-secure context, this is done to prevent malicious code from
making
uncontrolled writes anywhere on the file system.
```ts
// 🚫 Harmful use
defineStore('.privat-config', { customPath: '/somewhere/in/user/filesystem/' })
```
> **Note**
> If you need create store in some different location, you should make your own wrapper around [fs-nano-store]. Look
how use [In Main](#in-main).
2. For the same reasons, you cannot use any path fragments in the repository name
```ts
// 🚫 Harmful use
defineStore('../../somewhere/in/user/filesystem/privat-config')
```
[fs-nano-store]: https://github.com/cawa-93/fs-nano-store
