npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

encrypt-storage

v3.0.2

Published

Wrapper for encrypted localStorage and sessionStorage in browser

Readme

stargazers count maintenance sponsors GitHub License

NPM Version NPM Downloads jsDelivr hits (npm)

npm bundle size npm bundle size NPM Unpacked Size GitHub code size in bytes

codecov Socket Badge GitHub Actions Workflow Status GitHub Actions Workflow Status GitHub Actions Workflow Status

Encrypt Storage

encrypt-storage wraps the browser Storage API with transparent encryption. Store objects, strings, or any serializable value in localStorage, sessionStorage, or browser cookies while keeping the same familiar Storage-like API.

Choose between a fully synchronous implementation powered by @noble/ciphers or the native Web Crypto API, select an AES algorithm, and keep your application compatible with modern browsers and frameworks.

[!NOTE]

🎉 Welcome to Encrypt Storage v3

Version 3 is the most significant release in the project's history.

This release is a complete modernization of the library, focused on modernization, performance, security, and compatibility with today's JavaScript ecosystem.

What's new?

  • ⚡ Replaced CryptoJS with modern cryptography powered by Web Crypto API and Noble Ciphers/Hashes
  • 🌐 Improved compatibility with modern browser-based frameworks such as Next.js, Astro, Vite, Nuxt, and more
  • New built-in TTL (Time-To-Live) API featuring dedicated methods such as setTTL, getTTL, hasTTL, refreshTTL, removeTTL, and more for managing expiring data
  • 🔒 Stronger key derivation using PBKDF2
  • 🚀 Smaller bundle size and improved tree-shaking
  • 🧠 Lazy initialization for better SSR compatibility
  • 📦 Modern ESM-first distribution with full TypeScript support
  • 🧪 Rewritten test suite and improved code quality
  • 🛠️ Modernized build pipeline, tooling, and CI

While this is a major release internally, the public API remains familiar, making migration from v2 straightforward for most projects.

Thank you to everyone who has supported Encrypt Storage over the years. ❤️

[!IMPORTANT] Browser-side encryption is not a substitute for server-side security.

Secrets shipped to the client can always be extracted by a sufficiently motivated attacker. Encrypt Storage protects data at rest inside browser storage and helps prevent casual inspection, but it must not be considered a replacement for authentication, authorization, or server-side secret management.

Features

  • 🔒 Encrypt values stored in localStorage, sessionStorage, and browser cookies.
  • ⚡ Choose between synchronous (@noble/ciphers) and asynchronous (native Web Crypto API) encryption engines.
  • ⏳ Built-in TTL (Time-To-Live) support.
  • 🧩 Familiar Storage-like API with bulk operations, pattern matching, and utility methods.
  • 📦 Automatic serialization/deserialization.
  • 🏷️ Prefix namespaces.
  • 🗂️ Compatible with Redux Persist, Vuex Persist, Pinia Persist and other Storage-based integrations.

Table of contents

Built with

| Category | Technology | | --- | --- | | Language | TypeScript | | Encryption (sync) | @noble/ciphers — AES-GCM, AES-CBC, AES-CTR | | Hashing | @noble/hashes — SHA-256, PBKDF2 | | Encryption (async) | Web Crypto API — AES-GCM, AES-CBC, AES-CTR | | Bundler | Vite+ (Vite + Rolldown + tsdown) | | Test runner | Vitest | | Coverage | v8 via @vitest/coverage-v8 | | Linter | Oxlint | | Formatter | Oxfmt | | Package manager | pnpm | | Git hooks | Vite+ staged hooks + commitlint | | CI | GitHub Actions |

Installation

npm install encrypt-storage
yarn add encrypt-storage
pnpm add encrypt-storage

The package is intended for bundlers and supports ESM and CommonJS imports. A modern Node.js runtime such as >=20 is recommended for local development and package tooling.

Using a CDN

For browser-only projects, load the ESM build from a CDN. Version 3 uses the factory API, so create an instance with EncryptStorage.create().

unpkg

<script type="module">
  import { EncryptStorage } from 'https://unpkg.com/encrypt-storage@latest?module';

  const encryptStorage = EncryptStorage.create('secret-key-value', {
    engine: 'noble',
    prefix: '@app',
  });

  encryptStorage.setItem('user', { name: 'Ada Lovelace' });
</script>

jsDelivr

<script type="module">
  import { EncryptStorage } from 'https://cdn.jsdelivr.net/npm/encrypt-storage@latest/+esm';

  const encryptStorage = EncryptStorage.create('secret-key-value', {
    engine: 'noble',
    prefix: '@app',
  });

  encryptStorage.setItem('user', { name: 'Ada Lovelace' });
</script>

For production, pin the package to a specific version instead of using @latest.

Version and runtime support

| Encrypt Storage version | Status | Documentation | | --- | --- | --- | | 3.x | Current API. Requires explicit engine selection with EncryptStorage.create(). | This README | | 2.16.x | Legacy API. (Security updates) | Version 2 documentation | | 1.3.10 (deprecated) | Legacy API. | Version 1 documentation |

| Runtime or framework | Support | | --- | --- | | Modern browsers | Supported when localStorage or sessionStorage is available. | | noble engine | Synchronous encryption via @noble/ciphers. Works in the browser with the Storage API. | | web-crypto engine | Requires globalThis.crypto.subtle, available in modern browser secure contexts. | | Node.js, Bun, and Deno | Not runtime targets for storage usage. They may be used for development, bundling, testing, or SSR frameworks, but storage instances must only be created and used in the browser. | | Next.js App Router | Supported in Client Components with 'use client' (Next.js 13+). | | Next.js Pages Router and SSR | Supported when browser-storage code runs only on the client. |

Migration to version 3

Version 3 has a breaking change: EncryptStorage is now a factory entrypoint, not a directly instantiated class. Do not instantiate EncryptStorage with new.

In version 3, you have two valid patterns:

  • use EncryptStorage.create(secretKey, { engine, ...options })
  • import and instantiate a concrete class directly, such as new EncryptStorageNoble(...) or new EncryptStorageWebApi(...)

| Before version 3 | Version 3 | | --- | --- | | new EncryptStorage(secretKey, options) | EncryptStorage.create(secretKey, { engine: 'noble', ...options }) | | new EncryptStorage(secretKey, options) | new EncryptStorageNoble(secretKey, options) when you want the synchronous noble engine directly | | new EncryptStorage(secretKey, options) | new EncryptStorageWebApi(secretKey, options) when you want the Web Crypto engine directly | | One implicit encryption implementation | Explicit noble or web-crypto engine | | Synchronous API | noble is synchronous; web-crypto encrypt/decrypt operations return promises, while removal and storage utility methods remain synchronous. |

The same secret key and compatible algorithm are required to decrypt existing values. Encrypted payloads produced by different engines are not interchangeable; plan a migration if switching engines.

⚠️ Engine compatibility: The noble (synchronous) and web-crypto (asynchronous) engines use different PBKDF2 key derivation parameters, optimized for each mode's performance and user experience. Data encrypted with one engine cannot be decrypted by the other. Choose an engine once per storage namespace and keep it consistent.

Choose an encryption engine

| Engine | Default algorithm | API style | Use it when | | --- | --- | --- | --- | | noble | AES-GCM | Synchronous | You need a native Storage-compatible shape, especially for persisters. | | web-crypto | AES-GCM | Mixed | You want the browser's native Web Crypto API and can await encrypted operations. | | AsyncEncryptStorage | Selected engine | Fully promise-based | Your application requires promises for every exposed method, such as an asynchronous integration layer. |

All instances require a secretKey with at least 10 characters. Keep it stable: changing it makes existing encrypted values unreadable.

Supported algorithms for both engines: AES-GCM, AES-CBC, and AES-CTR. AES-GCM is the default.

Factory or direct class imports

The package exports both the EncryptStorage.create() factory and the engine classes directly from the main entrypoint:

import {
  EncryptStorage,
  AsyncEncryptStorage,
  EncryptStorageNoble,
  EncryptStorageWebApi,
} from 'encrypt-storage';

Use EncryptStorage.create() when you want a single entrypoint that chooses the implementation based on engine:

import { EncryptStorage } from 'encrypt-storage';

const encryptStorage = EncryptStorage.create('secret-key-value', {
  engine: 'noble',
  prefix: '@app',
});

If you already know which implementation you want, you can import the class directly:

import { EncryptStorageNoble } from 'encrypt-storage';

const encryptStorage = new EncryptStorageNoble('secret-key-value', {
  prefix: '@app',
});
import { EncryptStorageWebApi } from 'encrypt-storage';

const encryptStorage = new EncryptStorageWebApi('secret-key-value', {
  prefix: '@app',
});
import { AsyncEncryptStorage } from 'encrypt-storage';

const encryptStorage = new AsyncEncryptStorage('secret-key-value', {
  engine: 'web-crypto',
  prefix: '@app',
});

AsyncEncryptStorage is intentionally narrower than the engine instances. It does not expose the Cookie API (cookie.set, cookie.get, cookie.remove) and it does not expose the TTL API (setTTL, getTTL, hasTTL, hasExpired, getTTLMetadata, getRemainingTTL, refreshTTL, removeTTL).

If you need cookies or TTL, use an engine instance created with EncryptStorage.create(), new EncryptStorageNoble(...), or new EncryptStorageWebApi(...).

Direct class imports can help bundlers tree-shake more aggressively because your code references one concrete implementation instead of the factory branch that can return different engines. The factory API is still the most convenient option for general use.

Usage

Create and export one instance per storage namespace. A singleton module keeps the configuration consistent across an application.

Noble (synchronous)

Use noble for regular synchronous browser storage operations.

import { EncryptStorage } from 'encrypt-storage';

export const encryptStorage = EncryptStorage.create('secret-key-value', {
  engine: 'noble',
  prefix: '@app',
  storageType: 'localStorage',
});

encryptStorage.setItem('user', { id: '42', name: 'Ada Lovelace' });

const user = encryptStorage.getItem<{ id: string; name: string }>('user');

| Browser storage key | Stored value | getItem('user') result | | --- | --- | --- | | @app:user | Encrypted text | { id: '42', name: 'Ada Lovelace' } |

Web Crypto API (asynchronous)

Use the native Web Crypto API with engine: 'web-crypto'. Operations that encrypt or decrypt values must be awaited. It is available only where globalThis.crypto.subtle is supported.

import { EncryptStorage } from 'encrypt-storage';

export const encryptStorage = EncryptStorage.create('secret-key-value', {
  engine: 'web-crypto',
  encAlgorithm: 'AES-GCM',
  prefix: '@app',
  storageType: 'sessionStorage',
});

await encryptStorage.setItem('access-token', 'token-value');
const token = await encryptStorage.getItem<string>('access-token');

| Browser storage key | Stored value | await getItem() result | | --- | --- | --- | | @app:access-token | Encrypted text with a random IV | 'token-value' |

Supported algorithms are AES-GCM, AES-CBC, and AES-CTR. AES-GCM is the default.

web-crypto intentionally has a mixed API because the browser Storage operations themselves are synchronous.

| Asynchronous (Promise) | Synchronous | | --- | --- | | setItem, setMultipleItems, getItem, getMultipleItems, getItemFromPattern | removeItem, removeMultipleItems, removeItemFromPattern, clear, key, length | | encryptValue, decryptValue, hash, cookie.set, cookie.get, cookie.remove | — |

await encryptStorage.setItem('access-token', 'token-value');
encryptStorage.removeItem('access-token');
encryptStorage.clear();

AsyncEncryptStorage (fully promise-based)

AsyncEncryptStorage wraps the selected engine and exposes a promise-returning API for a smaller storage-focused surface. It is useful when an application expects asynchronous storage calls, including integrations built around promise-based runtimes such as React Native.

import { AsyncEncryptStorage } from 'encrypt-storage';

export const encryptStorage = new AsyncEncryptStorage('secret-key-value', {
  engine: 'web-crypto',
  encAlgorithm: 'AES-GCM',
  prefix: '@app',
});

await encryptStorage.setItem('user', { id: '42', name: 'Ada Lovelace' });
const user = await encryptStorage.getItem<{ id: string; name: string }>('user');

await encryptStorage.removeItem('user');
await encryptStorage.clear();

| Method | Return type | | --- | --- | | length | Promise<number> | | setItem, removeItem, removeItemFromPattern, clear | Promise<void> | | getItem, getItemFromPattern, key | Promise<value> | | encryptValue, decryptValue, hash | Promise<value> |

AsyncEncryptStorage exposes setItem, getItem, removeItem, getItemFromPattern, removeItemFromPattern, clear, key, encryptValue, decryptValue, hash, and length.

It does not expose:

  • bulk operations such as setMultipleItems, getMultipleItems, and removeMultipleItems
  • the Cookie API
  • the TTL API

For bulk operations, cookies, or TTL, use the engine instance returned by EncryptStorage.create() or instantiate EncryptStorageNoble / EncryptStorageWebApi directly.

React Native note: this package requires a compatible Web Storage implementation (localStorage or sessionStorage) to persist data. AsyncEncryptStorage provides a promise-based API, but it does not include a React Native storage backend or replace @react-native-async-storage/async-storage.

Multiple instances

Pass a unique prefix to every instance that shares the same browser storage. This prevents keys from colliding.

const authStorage = EncryptStorage.create('secret-key-value', {
  engine: 'noble',
  prefix: '@auth',
});

const settingsStorage = EncryptStorage.create('secret-key-value', {
  engine: 'noble',
  prefix: '@settings',
});

authStorage.setItem('token', 'token-value');
settingsStorage.setItem('theme', 'dark');

| Key | Value | | --- | --- | | @auth:token | Encrypted text | | @settings:theme | Encrypted text |

Server-side rendering

Browser storage is unavailable during server-side rendering. Create or use the instance only on the client.

Next.js Client Components

For Next.js App Router, put 'use client' at the top of the component that uses browser storage. This marks the component as client-side, allowing access to localStorage, sessionStorage, and EncryptStorage methods.

'use client';

import { useEffect, useState } from 'react';
import { EncryptStorage } from 'encrypt-storage';

const encryptStorage = EncryptStorage.create('secret-key-value', {
  engine: 'noble',
  prefix: '@app',
});

export function UserName() {
  const [name, setName] = useState<string | undefined>();

  useEffect(() => {
    setName(encryptStorage.getItem<string>('user-name'));
  }, []);

  return <p>{name ?? 'Anonymous'}</p>;
}

Importing a Client Component from a Server Component is fine. The important rule is that any code creating or using a storage instance must stay inside a file marked with 'use client', or otherwise run only in the browser.

getStorage alternative

For shared modules, the Pages Router, or situations where a client-only component is not appropriate, keep the getStorage() guard. It returns null during SSR and an instance in the browser.

import { EncryptStorage } from 'encrypt-storage';

export const getStorage = () => {
  if (typeof window === 'undefined') return null;

  return EncryptStorage.create('secret-key-value', {
    engine: 'noble',
    prefix: '@app',
  });
};

For client-side framework variables, use the framework's public environment-variable convention. Do not expose a server-only secret in browser code.

Options

Pass an options object as the second argument to EncryptStorage.create().

| Property | Default | Applies to | Description | | --- | --- | --- | --- | | engine | — | all | Required. 'noble' or 'web-crypto'. | | prefix | '' | all | Prefix added to every storage key as prefix:key. | | storageType | 'localStorage' | all | Browser storage target: 'localStorage' or 'sessionStorage'. | | stateManagementUse | false | all | Returns raw strings without automatic parsing; required by persisters. Use only with noble for persisters. | | doNotEncryptValues | false | all | Stores values without encryption. Prefixes and storage helpers still apply. | | doNotParseValues | false | all | Disables automatic JSON.stringify/JSON.parse conversion. | | notifyHandler | undefined | all | Callback invoked after supported storage operations. | | validation | undefined | all | Value validation rules applied on setItem. See Validation. | | encAlgorithm | 'AES-GCM' | all | Encryption algorithm. Applies to both engines. |

Supported algorithms: AES-GCM, AES-CBC, and AES-CTR.

doNotParseValues expects string-compatible values. With its default false, objects are serialized on write and parsed on read; scalar values are recovered when valid JSON.

Validation

Pass a validation object to enforce value constraints on every setItem call. When a validation rule is violated, an error is thrown synchronously, preventing the value from being written to storage.

const encryptStorage = EncryptStorage.create('secret-key-value', {
  engine: 'noble',
  prefix: '@app',
  validation: {
    allowNull: false,
    allowUndefined: false,
  },
});

// Throws NullValueError
encryptStorage.setItem('key', null);

// Throws UndefinedValueError
encryptStorage.setItem('key', undefined);

| Property | Default | Description | | --- | --- | --- | | allowNull | true | When false, storing null throws NullValueError. | | allowUndefined | false | When false, storing undefined throws UndefinedValueError. | | strict | false | When true, overrides both allowNull and allowUndefined to false. Neither null nor undefined can be stored. |

The strict option is a shorthand. It takes precedence over individual settings:

const encryptStorage = EncryptStorage.create('secret-key-value', {
  engine: 'noble',
  validation: { strict: true },
});

// Both throw — strict disallows null and undefined regardless of other settings
encryptStorage.setItem('key', null);      // NullValueError
encryptStorage.setItem('key', undefined); // UndefinedValueError

By default (no validation option), null is allowed and undefined is not. This matches standard JSON serialization behavior where null is a valid JSON value but undefined is not.

Validation applies to setItem and, by extension, to any method that calls it internally (setMultipleItems, setTTL).

Storage methods

The examples below use a synchronous noble instance. Prefixes are added to physical browser-storage keys but are omitted from method parameters and returned pattern keys.

| Method | Parameters | Return (noble) | Return (web-crypto) | | --- | --- | --- | --- | | setItem | (key: string, value: any, doNotEncrypt?: boolean) | void | Promise<void> | | getItem<T> | (key: string, doNotDecrypt?: boolean) | T \| undefined | Promise<T \| undefined> | | removeItem | (key: string) | void | void | | setMultipleItems | (items: [string, any][], doNotEncrypt?: boolean) | void | Promise<void> | | getMultipleItems | (keys: string[], doNotDecrypt?: boolean) | Record<string, any> | Promise<Record<string, any>> | | removeMultipleItems | (keys: string[]) | void | void | | getItemFromPattern | (pattern: string, options?: GetFromPatternOptions) | Record<string, any> \| undefined | Promise<Record<string, any> \| undefined> | | removeItemFromPattern | (pattern: string, options?: RemoveFromPatternOptions) | void | void | | clear | () | void | void | | key | (index: number) | string \| null | string \| null | | length | — (getter) | number | number | | encryptValue | (value: any) | string | Promise<string> | | decryptValue<T> | (value: string) | T \| null | Promise<T \| null> | | hash | (value: string) | string | Promise<string> |

const encryptStorage = EncryptStorage.create('secret-key-value', {
  engine: 'noble',
  prefix: '@example',
});

With a Web Crypto instance, add await to setItem, setMultipleItems, getItem, getMultipleItems, getItemFromPattern, encryptValue, decryptValue, and hash. Keep removeItem, removeMultipleItems, removeItemFromPattern, clear, key, and length synchronous.

Write and read values

encryptStorage.setItem('token', 'edbe38e0-748a-49c8-9f8f-b68f38dbe5a2');
encryptStorage.setItem('user', { id: '123456', name: 'John Doe' });

const token = encryptStorage.getItem<string>('token');
const user = encryptStorage.getItem<{ id: string; name: string }>('user');

| Key | Value in browser storage | Read result | | --- | --- | --- | | @example:token | Encrypted text | 'edbe38e0-748a-49c8-9f8f-b68f38dbe5a2' | | @example:user | Encrypted text | { id: '123456', name: 'John Doe' } |

Pass true as the third argument to setItem to skip encryption for that call. Pass true as the second argument to getItem to skip decryption for that call.

Bulk operations

encryptStorage.setMultipleItems([
  ['token', 'token-value'],
  ['user', { id: '123456', name: 'John Doe' }],
]);

const values = encryptStorage.getMultipleItems(['token', 'user', 'missing']);
encryptStorage.removeMultipleItems(['token', 'user']);

| Method | Result | | --- | --- | | getMultipleItems(['token', 'user', 'missing']) | { token: 'token-value', user: { id: '123456', name: 'John Doe' }, missing: undefined } | | removeMultipleItems(['token', 'user']) | Removes both prefixed browser-storage keys. |

Pattern operations

getItemFromPattern() and removeItemFromPattern() look for matching storage keys within the current prefix. Set exact: true to require an exact key match. getItemFromPattern() returns all matches by default; set multiple: false to return one value.

encryptStorage.setItem('fruit:apple', 'apple');
encryptStorage.setItem('fruit:grape', 'grape');
encryptStorage.setItem('vegetable:lettuce', 'lettuce');

const fruit = encryptStorage.getItemFromPattern('fruit');
encryptStorage.removeItemFromPattern('vegetable');

| Key before removal | Value | | --- | --- | | @example:fruit:apple | Encrypted text for 'apple' | | @example:fruit:grape | Encrypted text for 'grape' | | @example:vegetable:lettuce | Encrypted text for 'lettuce' |

// { 'fruit:apple': 'apple', 'fruit:grape': 'grape' }
console.log(fruit);

Storage utilities

const firstKey = encryptStorage.key(0);
const itemCount = encryptStorage.length;

encryptStorage.removeItem('token');
encryptStorage.clear();

| Member | Result | | --- | --- | | key(index) | Browser-storage key at index, or null. | | length | Number of entries in the selected underlying browser storage. | | removeItem(key) | Removes the prefixed key. | | clear() | Clears all entries in the selected underlying storage, not only the current prefix. |

Encrypt, decrypt, and hash

Use these helpers when encryption is needed without writing to browser storage.

const encrypted = encryptStorage.encryptValue({ id: '123456', name: 'John Doe' });
const decrypted = encryptStorage.decryptValue<{ id: string; name: string }>(encrypted);
const digest = encryptStorage.hash('John Doe');

| Method | Result | | --- | --- | | encryptValue(value) | An encrypted string. | | decryptValue<T>(value) | The decrypted value, parsed when possible. | | hash(value) | SHA-256 hash string. |

Cookies

Engine instances expose cookie.set, cookie.get, and cookie.remove. Cookie operations use the same selected encryption engine. With the noble engine all cookie methods are synchronous; with web-crypto all three methods return promises.

AsyncEncryptStorage does not implement the Cookie API.

Noble cookies (synchronous)

const encryptStorage = EncryptStorage.create('secret-key-value', {
  engine: 'noble',
  prefix: '@app',
});

encryptStorage.cookie.set(
  'preferences',
  { colorScheme: 'dark' },
  {
    path: '/',
    expires: new Date(Date.now() + 86_400_000),
    secure: true,
    sameSite: 'lax',
  },
);

const preferences = encryptStorage.cookie.get<{ colorScheme: string }>('preferences');
encryptStorage.cookie.remove('preferences', { path: '/' });

Web Crypto cookies (asynchronous)

With engine: 'web-crypto', all cookie methods (set, get, and remove) are asynchronous and must be awaited.

const encryptStorage = EncryptStorage.create('secret-key-value', {
  engine: 'web-crypto',
  prefix: '@app',
});

await encryptStorage.cookie.set(
  'preferences',
  { colorScheme: 'dark' },
  {
    path: '/',
    expires: new Date(Date.now() + 86_400_000),
    secure: true,
    sameSite: 'lax',
  },
);

const preferences = await encryptStorage.cookie.get<{ colorScheme: string }>('preferences');
await encryptStorage.cookie.remove('preferences', { path: '/' });

Cookie API reference

| Method | noble return | web-crypto return | | --- | --- | --- | | cookie.set(key, value, options?) | void | Promise<void> | | cookie.get<T>(key) | T \| null | Promise<T \| null> | | cookie.remove(key, options?) | void | Promise<void> |

| Option | Type | Description | | --- | --- | --- | | expires | number \| Date | Expiration: seconds from now (number) or absolute date (Date). | | path | string | Cookie path. | | domain | string | Cookie domain. | | secure | boolean | Marks the cookie as secure. | | sameSite | 'strict' \| 'lax' \| 'none' | SameSite attribute. |

cookie.remove accepts path and domain options to match the original cookie scope.

Cookies set from JavaScript cannot be HttpOnly; use server-set HttpOnly cookies for session tokens whenever possible.

TTL (Time-To-Live)

new

Engine instances expose a TTL API that stores values with an expiration time. When a TTL item expires, it is lazily removed: the item is deleted from browser storage only when it is accessed after expiration. There is no background observer, timer, or polling mechanism watching for expiration. This means an expired item remains physically in storage until your code reads it with getTTL, hasTTL, hasExpired, getTTLMetadata, getRemainingTTL, or refreshTTL.

Important: expired items are cleaned up on access, not on a schedule. If your application never reads an expired key, it stays in browser storage indefinitely. Design your access patterns accordingly, or call getTTL for known keys at application startup.

The TTL API is available on both noble (synchronous) and web-crypto (asynchronous) engine instances. AsyncEncryptStorage does not implement TTL methods. The examples below use the synchronous noble engine; see TTL with Web Crypto for the async equivalent.

const encryptStorage = EncryptStorage.create('secret-key-value', {
  engine: 'noble',
  prefix: '@app',
});

Store a value with TTL

Use setTTL to store a value that expires after a given duration or at a specific date.

// Expire in 3600 seconds (1 hour)
encryptStorage.setTTL({
  key: 'access_token',
  value: 'eyJhbGciOiJIUzI1NiIs...',
  ttl: 3600,
});

// Expire at a specific date
encryptStorage.setTTL({
  key: 'session',
  value: { userId: '42', role: 'admin' },
  ttl: new Date('2030-01-01T00:00:00Z'),
});

// Store without encryption
encryptStorage.setTTL({
  key: 'public_notice',
  value: 'Maintenance at 3 AM',
  ttl: 7200,
  doNotEncrypt: true,
});

| Parameter | Type | Description | | --- | --- | --- | | key | string | Storage key. | | value | T | Value to store. Objects are serialized automatically. | | ttl | number \| Date | Expiration: seconds from now (number) or an absolute expiration date (Date). | | doNotEncrypt | boolean | Skip encryption for this item. Default false. |

The stored value in browser storage is an encrypted JSON object containing the original value and the expiration timestamp. The prefix is applied to the key as with regular setItem.

Read a TTL value

Use getTTL to retrieve a stored value. If the item has expired, it is removed from storage and null is returned.

const token = encryptStorage.getTTL<string>('access_token');

if (token) {
  // Token is still valid
  api.setHeader('Authorization', `Bearer ${token}`);
} else {
  // Token expired or never existed — redirect to login
  router.push('/login');
}
const session = encryptStorage.getTTL<{ userId: string; role: string }>('session');
// session is typed as { userId: string; role: string } | null

| Scenario | Return value | Side effect | | --- | --- | --- | | Key exists and has not expired | T (the stored value) | None | | Key exists and has expired | null | Item is removed from storage | | Key does not exist | null | None |

Check TTL state

// Returns true when the key exists and has NOT expired
const exists = encryptStorage.hasTTL('access_token');

// Returns true when the key does not exist or HAS expired
const expired = encryptStorage.hasExpired('access_token');

| Method | Returns true when | Removes expired item | | --- | --- | --- | | hasTTL(key) | Key exists and is still valid | Yes (if expired) | | hasExpired(key) | Key does not exist or has expired | Yes (if expired) |

Both methods trigger lazy removal when the item is expired.

TTL metadata and remaining time

const metadata = encryptStorage.getTTLMetadata('access_token');

if (metadata) {
  console.log(metadata.expiresAt);  // Date object
  console.log(metadata.remaining);  // Remaining time in seconds
  console.log(metadata.expired);    // false (always false when metadata is returned)
}

const remaining = encryptStorage.getRemainingTTL('access_token');

if (remaining !== null) {
  console.log(`Token expires in ${remaining} seconds`);
}

| Method | Return type | Description | | --- | --- | --- | | getTTLMetadata(key) | TTLMetadata \| null | Returns expiresAt (Date), remaining (seconds), and expired (boolean). Returns null when the key does not exist or has expired. | | getRemainingTTL(key) | number \| null | Remaining lifetime in seconds. Returns null when the key does not exist or has expired. |

Refresh and remove TTL

// Extend the expiration by setting a new TTL (in seconds from now)
const refreshed = encryptStorage.refreshTTL({
  key: 'access_token',
  ttl: 1800,
});
// refreshed === true if the key existed, false otherwise

// Or set a new absolute expiration date
encryptStorage.refreshTTL({
  key: 'session',
  ttl: new Date('2030-06-01T00:00:00Z'),
});

// Remove the TTL, making the value permanent
const removed = encryptStorage.removeTTL('access_token');
// removed === true if the key existed, false otherwise
// The value is now stored permanently without TTL metadata

| Method | Return | Description | | --- | --- | --- | | refreshTTL({ key, ttl }) | boolean | Updates the expiration time using seconds-from-now (number) or an absolute expiration date (Date). The stored value remains unchanged. Returns false when the key does not exist or has expired. | | removeTTL(key) | boolean | Removes the TTL wrapper. The plain value is re-stored as a permanent item. Returns false when the key does not exist or has expired. |

TTL with Web Crypto (asynchronous)

With the web-crypto engine, all TTL methods return promises. The API is identical otherwise.

const encryptStorage = EncryptStorage.create('secret-key-value', {
  engine: 'web-crypto',
  prefix: '@app',
});

await encryptStorage.setTTL({
  key: 'access_token',
  value: 'eyJhbGciOiJIUzI1NiIs...',
  ttl: 3600,
});

const token = await encryptStorage.getTTL<string>('access_token');
const exists = await encryptStorage.hasTTL('access_token');
const expired = await encryptStorage.hasExpired('access_token');
const metadata = await encryptStorage.getTTLMetadata('access_token');
const remaining = await encryptStorage.getRemainingTTL('access_token');
const refreshed = await encryptStorage.refreshTTL({ key: 'access_token', ttl: 1800 });
const removed = await encryptStorage.removeTTL('access_token');

| Method | noble | web-crypto | | --- | --- | --- | | setTTL | void | Promise<void> | | getTTL | T \| null | Promise<T \| null> | | hasTTL | boolean | Promise<boolean> | | hasExpired | boolean | Promise<boolean> | | getTTLMetadata | TTLMetadata \| null | Promise<TTLMetadata \| null> | | getRemainingTTL | number \| null | Promise<number \| null> | | refreshTTL | boolean | Promise<boolean> | | removeTTL | boolean | Promise<boolean> |

State management persisters

State-management persisters must use the noble engine. They expect a synchronous, native Storage-like interface; the asynchronous web-crypto engine does not satisfy that contract. Set stateManagementUse: true so values are returned as raw strings, which is required for persisters to serialize and deserialize state correctly.

import { EncryptStorage } from 'encrypt-storage';

export const persisterStorage = EncryptStorage.create('secret-key-value', {
  engine: 'noble',
  stateManagementUse: true,
  prefix: '@app',
});

Vuex Persist

import VuexPersistence from 'vuex-persist';
import { persisterStorage } from './storage';

const vuexLocal = new VuexPersistence<RootState>({
  storage: persisterStorage,
});

Zustand Persist

Zustand's persist middleware accepts a custom storage via createJSONStorage. Wrap the synchronous noble instance to satisfy its interface. Do not use web-crypto directly unless you provide an async-compatible storage adapter.

import { createStore } from 'zustand/vanilla'
import { persist, createJSONStorage } from 'zustand/middleware';

import { persisterStorage } from './storage';

type PositionStoreState = { position: { x: number; y: number } }

type PositionStoreActions = {
  setPosition: (nextPosition: PositionStoreState['position']) => void
}

type PositionStore = PositionStoreState & PositionStoreActions

const storage = createJSONStorage(() => ({
  getItem: (name) => persisterStorage.getItem(name) ?? null,
  setItem: (name, value) => persisterStorage.setItem(name, value),
  removeItem: (name) => persisterStorage.removeItem(name),
}));

const positionStore = createStore<PositionStore>()(
  persist(
    (set) => ({
      position: { x: 0, y: 0 },
      setPosition: (position) => set({ position }),
    }),
    {
      storage,
      name: 'position-store',
    }
  ),
);

Redux Persist

Use the same synchronous noble instance. Do not use web-crypto or a custom asynchronous wrapper with this integration.

import { persistReducer } from 'redux-persist';
import { persisterStorage } from './storage';

const persistConfig = {
  key: 'root',
  storage: persisterStorage,
  whitelist: ['navigation'],
};

export const persistedReducer = persistReducer(persistConfig, rootReducer);

Pinia persist plugins

import { defineStore } from 'pinia';
import { persisterStorage } from './storage';

export const useUserStore = defineStore('user', {
  state: () => ({
    firstName: 'John',
    lastName: 'Doe',
    accessToken: 'token-value',
  }),
  persist: {
    storage: persisterStorage,
    paths: ['accessToken'],
  },
});

Notifications

Use notifyHandler to observe storage activity. It receives an object with type and, where applicable, key, value, and index.

const encryptStorage = EncryptStorage.create('secret-key-value', {
  engine: 'noble',
  notifyHandler: ({ type, key, value }) => {
    console.info('encrypt-storage event', { type, key, value });
  },
});

Event types include set, get, setMultiple, getMultiple, remove, removeMultiple, clear, length, key, set:cookie, get:cookie, and remove:cookie.

Error handling

encrypt-storage throws specific errors during instance creation when preconditions are not met. These errors are thrown synchronously by both EncryptStorage.create() and new AsyncEncryptStorage().

InvalidSecretKeyError

Thrown when the provided secretKey has fewer than 10 characters.

import { EncryptStorage } from 'encrypt-storage';

try {
  const encryptStorage = EncryptStorage.create('short', {
    engine: 'noble',
  });
} catch (error) {
  // error.name === 'InvalidSecretKey'
  // error.message === 'The secretKey parameter must bne contains min 10 characters. Please provide a valid secretKey'
  console.error(error);
}

| Property | Value | | --- | --- | | name | InvalidSecretKey | | message | The secretKey parameter must bne contains min 10 characters. Please provide a valid secretKey | | Thrown by | EncryptStorage.create(), new AsyncEncryptStorage() | | Condition | secretKey.length < 10 |

IsNotBrowserEnvironmentError

Thrown when the instance is created in a non-browser environment where window is undefined or not an object. This typically occurs during server-side rendering (SSR) or in Node.js scripts without a browser-like global.

/**
 * In a Node.js / SSR environment:
 */
import { EncryptStorage } from 'encrypt-storage';

try {
  const encryptStorage = EncryptStorage.create('secret-key-value', {
    engine: 'noble',
  });
} catch (error) {
  // error.name === 'IsNotBrowserEnvironmentError'
  // error.message === 'The current environment is not a browser environment. Please use the EncryptStorageWebApi engine.'
  console.error(error);
}

| Property | Value | | --- | --- | | name | IsNotBrowserEnvironmentError | | message | The current environment is not a browser environment. Please use the EncryptStorageWebApi engine. | | Thrown by | EncryptStorage.create(), new AsyncEncryptStorage() | | Condition | typeof window === 'undefined' \|\| typeof window !== 'object' |

Both errors extend the native Error class and can be caught with standard try/catch blocks. Use the Server-side rendering patterns to avoid IsNotBrowserEnvironmentError in SSR contexts.

NullValueError

Thrown when setItem receives null and the validation.allowNull option is false (or validation.strict is true).

import { EncryptStorage } from 'encrypt-storage';

const encryptStorage = EncryptStorage.create('secret-key-value', {
  engine: 'noble',
  validation: { allowNull: false },
});

try {
  encryptStorage.setItem('key', null);
} catch (error) {
  // error.name === 'NullValueError'
  // error.message === 'The value parameter cannot be null. Please provide a valid value.'
  console.error(error);
}

| Property | Value | | --- | --- | | name | NullValueError | | message | The value parameter cannot be null. Please provide a valid value. | | Thrown by | setItem, setMultipleItems, setTTL | | Condition | value === null when allowNull is false |

UndefinedValueError

Thrown when setItem receives undefined and the validation.allowUndefined option is false (or validation.strict is true). This is the default behavior — undefined is not allowed unless explicitly enabled.

import { EncryptStorage } from 'encrypt-storage';

const encryptStorage = EncryptStorage.create('secret-key-value', {
  engine: 'noble',
});

try {
  encryptStorage.setItem('key', undefined);
} catch (error) {
  // error.name === 'UndefinedValueError'
  // error.message === 'The value parameter cannot be undefined. Please provide a valid value.'
  console.error(error);
}

| Property | Value | | --- | --- | | name | UndefinedValueError | | message | The value parameter cannot be undefined. Please provide a valid value. | | Thrown by | setItem, setMultipleItems, setTTL | | Condition | value === undefined when allowUndefined is false |

All errors extend the native Error class and can be caught with standard try/catch blocks.

License

MIT License

Help project

HELP THIS PROJECT: A GitHub star helps this project. It costs nothing and is greatly appreciated.

SUPPORT THE PROJECT: Encrypt Storage is maintained by Michelon Souza, a solo Brazilian developer keeping this project secure, tested, and up-to-date for thousands of developers. If this package helps your project, consider supporting through any of these channels — every contribution helps keep it free and evolving. Thank you! 💙

GitHub Sponsors   Buy Me A Coffee   Patreon