> For the complete documentation index, see [llms.txt](https://docs.zama.org/protocol/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zama.org/protocol/sdk/guides/web-extensions.md).

# Web extensions

MV3 Chrome extensions present a unique challenge: the background service worker can be terminated by Chrome at any time. When that happens, anything stored in JavaScript memory is lost -- including the SDK's default in-memory permit storage. This guide shows how to keep permits alive across service worker restarts.

## Steps

### 1. Understand the problem

In a normal web page, the SDK defaults to persistent IndexedDB storage for signed permits. But an MV3 service worker has no `window`, so the SDK falls back to an in-memory store — and the service worker can shut down after 30 seconds of inactivity, taking those permits with it.

When the service worker restarts, the in-memory permits are gone. The user would need to re-sign with their wallet on every interaction -- a broken experience.

### 2. Use `chromeSessionStorage` for permit persistence

The SDK ships a `chromeSessionStorage` adapter that stores signed permits in `chrome.storage.session` instead of in-memory. This API is backed by Chrome's own persistence layer, not your JavaScript heap.

```ts
import { ZamaSDK, indexedDBStorage, chromeSessionStorage } from "@zama-fhe/sdk";
```

### 3. Configure the SDK with both storage backends

Pass `indexedDBStorage` for the transport key pair (persistent, survives browser close) and `chromeSessionStorage` for permits (ephemeral, survives service worker restarts):

```ts
import { createConfig } from "@zama-fhe/sdk/viem";
import { web } from "@zama-fhe/sdk/web";
import { sepolia, type FheChain } from "@zama-fhe/sdk/chains";

const mySepolia = {
  ...sepolia,
  relayerUrl: "https://your-app.com/api/relayer/11155111",
} as const satisfies FheChain;

const config = createConfig({
  chains: [mySepolia],
  publicClient,
  walletClient,
  storage: indexedDBStorage, // encrypted transport key pair — persistent
  permitStorage: chromeSessionStorage, // signed permits — ephemeral
  relayers: { [mySepolia.id]: web() },
});
const sdk = new ZamaSDK(config);
```

{% tabs %}
{% tab title="manifest.json" %}

```json
{
  "manifest_version": 3,
  "permissions": ["storage"],
  "background": { "service_worker": "background.js" }
}
```

{% endtab %}
{% endtabs %}

The `"storage"` permission is required for `chrome.storage.session` access.

### 4. Benefits of this setup

With `chromeSessionStorage` in place, you get three things:

**Popup, background, and content script sharing** -- all extension contexts read from the same `chrome.storage.session` store. The user signs once in the popup, and the background script can decrypt balances without another prompt.

**Service worker restart survival** -- `chrome.storage.session` is not tied to JavaScript memory. When Chrome terminates and restarts the service worker, signed permits are still available.

**Automatic cleanup on browser close** -- Chrome purges `chrome.storage.session` when the browser closes. The user starts fresh on the next launch, which matches the expected security behavior for wallet-signed permits.

### 5. Browser close behavior

When the user closes Chrome entirely:

1. `chrome.storage.session` is cleared by the browser -- signed permits are gone
2. `indexedDB` persists -- the transport key pair survives
3. On next launch, the user re-signs once to create fresh permits for their existing transport key pair

Unlike a normal web page (which defaults to persistent IndexedDB), a service worker would otherwise fall back to in-memory permits lost on restart; `chromeSessionStorage` keeps them alive across service worker restarts within the same browser session.

## Next steps

* [GenericStorage](/protocol/sdk/api-references/sdk/genericstorage.md) -- implement a custom storage adapter for other extension APIs
* [Permit Model](/protocol/sdk/concepts/permit-model.md) -- how the transport key pair vault, signed permits, and storage interact


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.zama.org/protocol/sdk/guides/web-extensions.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
