{"id":19639917,"url":"https://github.com/extend-chrome/storage","last_synced_at":"2025-06-22T09:37:37.477Z","repository":{"id":49128177,"uuid":"185686290","full_name":"extend-chrome/storage","owner":"extend-chrome","description":"Extend the Chrome Extension Storage API with Promises and great TypeScript support.","archived":false,"fork":false,"pushed_at":"2024-01-13T02:36:53.000Z","size":812,"stargazers_count":84,"open_issues_count":17,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-04T20:09:32.969Z","etag":null,"topics":["chrome-api","chrome-extension","chrome-storage-api","promises","storage","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@extend-chrome/storage","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/extend-chrome.png","metadata":{"funding":{"ko_fi":"jacksteam"},"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-05-08T22:14:38.000Z","updated_at":"2025-05-16T17:53:01.000Z","dependencies_parsed_at":"2024-06-18T21:20:07.816Z","dependency_job_id":"ddd31b7b-a267-44c5-bcc1-eed864821092","html_url":"https://github.com/extend-chrome/storage","commit_stats":null,"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/extend-chrome/storage","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extend-chrome%2Fstorage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extend-chrome%2Fstorage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extend-chrome%2Fstorage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extend-chrome%2Fstorage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/extend-chrome","download_url":"https://codeload.github.com/extend-chrome/storage/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extend-chrome%2Fstorage/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259977668,"owners_count":22941115,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chrome-api","chrome-extension","chrome-storage-api","promises","storage","typescript"],"created_at":"2024-11-11T14:03:46.622Z","updated_at":"2025-06-22T09:37:32.464Z","avatar_url":"https://github.com/extend-chrome.png","language":"TypeScript","funding_links":["https://ko-fi.com/jacksteam"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"http://bit.ly/35jbrc5\" rel=\"noopener\"\u003e\n \u003cimg width=200px height=200px src=\"https://imgur.com/2BRBk4K.png\" alt=\"@extend-chrome/storage logo\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003ch3 align=\"center\"\u003e@extend-chrome/storage\u003c/h3\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![npm (scoped)](https://img.shields.io/npm/v/@extend-chrome/storage.svg)](https://www.npmjs.com/package/@extend-chrome/storage)\n[![GitHub last commit](https://img.shields.io/github/last-commit/extend-chrome/storage.svg)](https://github.com/extend-chrome/storage)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](/LICENSE)\n[![TypeScript Declarations Included](https://img.shields.io/badge/types-TypeScript-informational)](#typescript)\n\n[![Fiverr: We make Chrome extensions](https://img.shields.io/badge/Fiverr%20-We%20make%20Chrome%20extensions-brightgreen.svg)](http://bit.ly/37mZsfA)\n[![ko-fi](https://img.shields.io/badge/ko--fi-Buy%20me%20a%20coffee-ff5d5b)](http://bit.ly/2qmaQYB)\n\n\u003c/div\u003e\n\n---\n\nManage Chrome Extension storage easily with `@extend-chrome/storage`.\n\nThis is a wrapper for the Chrome Extension Storage API that adds\n[great TypeScript support](#features-typescript) using virtual storage buckets with a modern Promise-based API.\n\n# Table of Contents\n\n- [Getting Started](#getting_started)\n- [Usage](#usage)\n  \u003c!-- - [Testing with Jest](https://github.com/extend-chrome/storage/jest/README.md) --\u003e\n- [Features](#features)\n- [API](#api)\n  - [interface Bucket](#api-bucket)\n    - [bucket.get()](#api-bucket-get)\n    - [bucket.getKeys()](#api-bucket-getKeys)\n    - [bucket.set()](#api-bucket-set)\n    - [bucket.remove()](#api-bucket-remove)\n    - [bucket.clear()](#api-bucket-clear)\n    - [bucket.changeStream](#api-bucket-changeStream)\n    - [bucket.valueStream](#api-bucket-valueStream)\n  - [function getBucket()](#api-getBucket)\n\n# Getting started \u003ca name = \"getting_started\"\u003e\u003c/a\u003e\n\nYou will need to use a bundler like\n[Rollup](https://rollupjs.org/guide/en/), Parcel, or Webpack to\ninclude this library in your Chrome extension.\n\nSee [`rollup-plugin-chrome-extension`](http://bit.ly/35hLMR8) for\nan easy way to use Rollup to build your Chrome extension!\n\n## Installation\n\n```sh\nnpm i @extend-chrome/storage\n```\n\n# Usage \u003ca name = \"usage\"\u003e\u003c/a\u003e\n\nAdd the `storage` permission to your `manifest.json` file.\n\n```jsonc\n// manifest.json\n{\n  \"permissions\": [\"storage\"]\n}\n```\n\nTake your Chrome extension to another level! 🚀\n\n```javascript\nimport { storage } from '@extend-chrome/storage'\n\n// Set storage using an object\nstorage.set({ friends: ['Jack'] })\n\n// Set storage using a function\nstorage.set(({ friends }) =\u003e {\n  // friends is ['Jack']\n  return { friends: [...friends, 'Amy'] }\n})\n\n// Get storage value using a key\nstorage.get('friends').then(({ friends }) =\u003e {\n  // friends is ['Jack', 'Amy']\n})\n\n// Get storage values using an object\nstorage\n  .get({ friends: [], enemies: [] })\n  .then(({ friends, enemies }) =\u003e {\n    // friends is ['Jack', 'Amy']\n    // enemies is the [] from the getter object\n  })\n```\n\n# Features \u003ca name = \"features\"\u003e\u003c/a\u003e\n\n## Virtual typed storage buckets \u003ca name = \"features-typescript\"\u003e\u003c/a\u003e\n\nThis library allows you to create a storage area and define the\ntype of data that area will manage.\n\n\u003c!-- TODO: Convert code block to GIF --\u003e\n\n```typescript\nimport { getBucket } from '@extend-chrome/storage'\n\ninterface Store {\n  a: string\n  b: number\n}\n\nconst store = getBucket\u003cStore\u003e('store')\n\nstore.set({ a: 'abc', b })\nstore.set(({ b = 0 }) =\u003e ({ b: b + 500 }))\n\nstore.set({ c: true }) // ts error\nstore.set(({ a }) =\u003e ({ d: 'invalid' })) // ts error\n```\n\n## Promises and functional setters \u003ca name = \"features-async\"\u003e\u003c/a\u003e\n\nThe Chrome Storage API is asynchronous. This means synchronous\ncalls to `get` and `set` will not reflect pending changes. This\nmakes calls to `set` that depend on values held in storage\ndifficult.\n\nWhile the Chrome Storage API is async, it uses callbacks. This\nbrings a whole world of difficulty into the developer experience\nthat have been solved with Promises.\n\n`@extend-chrome/storage` solves both of these problems. Every method\nreturns a Promise and both `get` and `set` can take a function\nthat provides current storage values, similar to React's\n`this.setState`.\n\n## Composed set operations \u003ca name = \"features-composed\"\u003e\u003c/a\u003e\n\nThe `set` method can be called with a function (setter) as well\nas the normal types (a string, array of strings, or an object).\n\nThis setter will receive the entire contents of that storage area\nas an argument. It must return an object which will be passed to\nthe native storage area `set` method.\n\nSynchronous calls to `set` will be composed into one call to the\nnative `set`. The setters will be applied in order, but each call\nwill resolve with the final value passed to the storage area.\n\n```javascript\nbucket.set({ a: 123 })\nbucket.set({ b: 456 })\nbucket\n  .set(({ a, b }) =\u003e {\n    // a === 123\n    // b === 456\n    return { c: 789 }\n  })\n  .then(({ a, b, c }) =\u003e {\n    // New values in bucket\n    // a === 123\n    // b === 456\n    // c === 789\n  })\n```\n\nAn individual call to `set` will reject if the setter function\nthrows an error or returns an invalid type, but will not affect\nother set operations.\n\n# API \u003ca name = \"api\"\u003e\u003c/a\u003e\n\n## interface `Bucket` \u003ca name = \"api-bucket\"\u003e\u003c/a\u003e\n\nA synthetic storage area. It has the same methods as the native\nChrome API StorageArea, but `get` and `set` can take a function as an\nargument. A `Bucket` can use\n[either local or sync storage](https://developer.chrome.com/extensions/storage#using-sync).\n\nMultiple synchronous calls to set are composed into one call to\nthe native Chrome API\n[`StorageArea.set`](https://developer.chrome.com/extensions/storage#method-StorageArea-set).\n\nDefault storage areas are included, so you can just import\n`storage` if you're don't care about types and only need one\nstorage area.\n\n```javascript\nimport { storage } from '@extend-chrome/storage'\n\nstorage.local.set({ a: 'abc' })\nstorage.sync.set({ b: 123 })\n```\n\nCreate a bucket or two using [`getBucket`](#api-getBucket):\n\n```javascript\nimport { getBucket } from '@extend-chrome/storage'\n\n// Buckets are isomorphic, so export and\n// use them throughout your extension\nexport const bucket1 = getBucket('bucket1')\nexport const bucket2 = getBucket('bucket2')\n```\n\nEach bucket is separate, so values don't overlap.\n\n```javascript\nbucket1.set({ a: 123 })\nbucket2.set({ a: 'abc' })\n\nbucket1.get() // { a: 123 }\nbucket2.get() // { a: 'abc' }\n```\n\nBuckets really shine if you're using TypeScript, because you can\ndefine the types your bucket will contain.\n[Click here for more details.](#features-typescript)\n\n## async function `bucket.get` \u003ca name = \"api-bucket-get\"\u003e\u003c/a\u003e\n\nTakes an optional getter. Resolves to an object with the\nrequested storage area values.\n\n\u003cdetails\u003e\u003csummary\u003eParameters\u003c/summary\u003e\n\u003cp\u003e\n\n```typescript\nfunction get(getter?: string | object | Function) =\u003e Promise\u003c{ [key: string]: any }\u003e\n```\n\n**[getter]**\\\nType: `null`, `string`, `string array`, or `function`\\\nDefault: `null`\\\nUsage is the same as for the native Chrome API, except for the function\ngetter.\n\n- Use `null` to get the entire bucket contents.\n- Use a `string` as a storage area key to get an object with only\n  that key/value pair.\n- Use an `object` with property names for the storage keys you\n  want.\n  - The values for each property will be used if the key is\n    undefined in storage.\n- Use a `function` to map the contents of storage to any value.\n  - The function will receive the entire contents of storage as\n    the first argument.\n  - The call to get will resolve to the function's return value.\n- Calls to `get` after `set` will resolve to the new set values.\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n```typescript\nbucket.get('a') // resolves to object as key/value pair\nbucket.get({ a: 123 }) // same, but 123 is default value\nbucket.get(({ a }) =\u003e a) // resolves to value of \"a\"\n```\n\n## async function `bucket.getKeys` \u003ca name = \"api-bucket-getKeys\"\u003e\u003c/a\u003e\n\nTakes no arguments. Resolves to an array of strings that represents the keys of the values in the storage area bucket.\n\n```typescript\nfunction getKeys() =\u003e Promise\u003cstring[]\u003e\n```\n\n```typescript\nbucket.set({ a: 123 })\nbucket.getKeys() // Resolves to ['a']\n```\n\n## async function `bucket.set` \u003ca name = \"api-bucket-set\"\u003e\u003c/a\u003e\n\nSet a value or values in storage. Takes a setter `object` or\n`function`. Resolves to the new bucket values.\n\n\u003cdetails\u003e\u003csummary\u003eParameters\u003c/summary\u003e\n\u003cp\u003e\n\n```typescript\nfunction set(setter: object | Function) =\u003e Promise\u003c{ [key: string]: any }\u003e\n```\n\n**`setter`**\\\nType: `object` or `Function`\n\n- Use an `object` with key/value pairs to set those values to\n  storage.\n- Use a `Function` that returns an object with key/value pairs.\n  - The setter function receives the results of previous\n    synchronous set operations.\n  - The setter function cannot be an async function.\n- Returns a `Promise` that resolves to the new storage values.\n- Calls to `get` after `set` will resolve with the new values.\n\n```typescript\n// Values in bucket: { a: 'abc' }\n\n// First call to set\nbucket.set({ b: 123 })\n\n// Second synchronous call to set\nbucket\n  .set(({ a, b, c }) =\u003e {\n    // Values composed from storage\n    // and previous call to set:\n    // a === 'abc'\n    // b === 123\n    // c === undefined\n    return { c: true }\n  })\n  .then(({ a, b, c }) =\u003e {\n    // New values in storage\n    // a === 'abc'\n    // b === 123\n    // c === true\n  })\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n```typescript\nbucket.set({ a: 123 })\nbucket\n  .set(({ a }) =\u003e ({ b: true }))\n  .then(({ a, b }) =\u003e {\n    // Values were set\n    // a === 123\n    // b === true\n  })\n```\n\n## async function `bucket.remove` \u003ca name = \"api-bucket-remove\"\u003e\u003c/a\u003e\n\nRemove a value or values from storage. Resolves when the\noperation is complete.\n\n```typescript\nbucket.remove('a')\nbucket.remove(['a', 'b'])\n```\n\n## async function `bucket.clear` \u003ca name = \"api-bucket-clear\"\u003e\u003c/a\u003e\n\nEmpties only this bucket. Resolves when the operation is\ncomplete. Other buckets are untouched.\n\n```typescript\nbucket.clear()\n```\n\n## async function `bucket.changeStream` \u003ca name = \"api-bucket-changeStream\"\u003e\u003c/a\u003e\n\nAn RxJs Observable that emits a\n[StorageChange](https://developer.chrome.com/extensions/storage#type-StorageChange)\nobject when the Chrome Storage API `onChanged` event fires.\n\n```typescript\nbucket.changeStream\n  .pipe(filter(({ a }) =\u003e !!a))\n  .subscribe(({ a }) =\u003e {\n    console.log('old value', a.oldValue)\n    console.log('new value', a.newValue)\n  })\n```\n\n## async function `bucket.valueStream` \u003ca name = \"api-bucket-valueStream\"\u003e\u003c/a\u003e\n\nAn Observable that emits all the values in storage immediately,\nand when `onChanged` fires.\n\n```typescript\nbucket.valueStream.subscribe((values) =\u003e {\n  console.log('Everything in this bucket', values)\n})\n```\n\n## function `getBucket` \u003ca name = \"api-getBucket\"\u003e\u003c/a\u003e\n\nCreate a [bucket](#api-bucket) (a synthetic storage area). Takes\na string `bucketName` and an optional string `areaName`. Returns\na [`Bucket`](#api-bucket) synthetic storage area. Export this\nbucket and use it throughout your Chrome extension. It will work\neverywhere, including privileged pages and content scripts.\n\n`getBucket` is a TypeScript Generic. Pass it an interface to\ndefine the types to expect in your storage area.\n\n\u003cdetails\u003e\u003csummary\u003eParameters\u003c/summary\u003e\n\u003cp\u003e\n\n```typescript\nfunction getBucket(bucketName: string, areaName?: 'local' | 'sync') =\u003e Bucket\n```\n\n**`bucketName`**\\\nType: `string`\\\nA unique id for this bucket.\n\n**`[areaName]`**\\\nType: `\"local\"` or `\"sync\"`\\\nDefault: `\"local\"`\\\nChoose which [native Chrome API storage area](https://developer.chrome.com/extensions/storage#using-sync)\nto use.\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n```typescript\nimport { getBucket } from '@extend-chrome/storage'\n\n// JavaScript\nexport const localBucket = getBucket('bucket1')\nexport const syncBucket = getBucket('bucket2', 'sync')\n\n// TypeScript\nexport const localBucket = getBucket\u003c{ a: string }\u003e('bucket1')\nexport const syncBucket = getBucket\u003c{ b: number }\u003e(\n  'bucket2',\n  'sync',\n)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextend-chrome%2Fstorage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fextend-chrome%2Fstorage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextend-chrome%2Fstorage/lists"}