# Storage Buckets API

> navigator.storageBuckets.open() creates named storage partitions with their own IndexedDB, caches, persistence, quota, and expiry. Options, names, rejections.

`navigator.storageBuckets.open(name, options)` creates or opens a named storage bucket: a partition of the origin's storage with its own `indexedDB` factory, `caches`, bucket file system, persistence mode, quota ceiling, and expiry date, so the browser can evict a low-value bucket under storage pressure while leaving a persisted one intact. Without buckets an origin has a single default bucket and eviction is all-or-nothing.

Chrome 122 and Edge 122 ship `StorageBucketManager` and every `StorageBucket` member (`indexedDB`, `caches`, `getDirectory()`, `persist()`, `estimate()`, `setExpires()`) together; Chrome for Android, Samsung Internet, and Android WebView mirror that version. Firefox has not implemented it ([Bugzilla 1594740](https://bugzil.la/1594740)) and Safari has no implementation (BCD `api.StorageBucketManager`). Treat buckets as an enhancement over the default bucket: a page that stores data in `bucket.indexedDB` on Chrome and in `window.indexedDB` elsewhere keeps the same schema code.

## Syntax

```js
navigator.storageBuckets.open(name)
navigator.storageBuckets.open(name, options)
navigator.storageBuckets.keys()
navigator.storageBuckets.delete(name)

bucket.persist()
bucket.persisted()
bucket.estimate()
bucket.setExpires(timestamp)
bucket.expires()
bucket.getDirectory()
```

`open()` returns a `Promise<StorageBucket>`, `keys()` a `Promise<sequence<DOMString>>` of live bucket names, and `delete()` a `Promise<undefined>` that resolves even when no bucket of that name exists. `navigator.storageBuckets` is `[SecureContext]` and exposed on `Window` and workers; `persist()` is `Window`-only because it may consult the `persistent-storage` permission. The `indexedDB` and `caches` attributes are ordinary `IDBFactory` and `CacheStorage` objects scoped to the bucket, and `getDirectory()` returns a `FileSystemDirectoryHandle` for a bucket-scoped origin private file system.

## Parameters

`open()` takes a bucket name and an optional `StorageBucketOptions` dictionary. The name must be 1 to 64 code points of ASCII lowercase letters, digits, `_`, or `-`, and must not start with `_` or `-`; opening an existing name returns the existing bucket and ignores `options`.

| Member | Type | Required | Description |
|---|---|---|---|
| `persisted` | `boolean` | No, defaults to `false` | Request the `"persistent"` bucket mode. Granted only when the `persistent-storage` permission is `"granted"`; otherwise the bucket opens in best-effort mode and `bucket.persisted()` resolves `false`. |
| `quota` | `unsigned long long` (bytes) | No | Upper bound for the bucket's usage; the browser may enforce a lower one. `bucket.estimate()` reports it as `quota` when set. |
| `expires` | `DOMHighResTimeStamp` (ms since the Unix epoch) | No | When the bucket is to be treated as gone. An expired bucket is removed lazily on the next `open()`, `keys()`, or access. |

Chromium's IDL also carries a `durability` member (`"strict"` or `"relaxed"`) behind the `StorageBucketsDurability` runtime flag; the WICG explainer removed it from the first version of the API because IndexedDB transactions already expose the same `durability` option, so do not rely on it.

The `StorageBucket` returned by `open()` has these members.

| Member | Type | Description |
|---|---|---|
| `name` | `DOMString` (read-only) | The key the bucket was opened with. |
| `persist()` | `Promise<boolean>` | Upgrades to `"persistent"` mode if the `persistent-storage` permission is already granted; resolves with the resulting mode. |
| `persisted()` | `Promise<boolean>` | `true` when the bucket is in `"persistent"` mode. |
| `estimate()` | `Promise<StorageEstimate>` | `usage` and `quota` for this bucket alone, unlike `navigator.storage.estimate()`, which covers the whole origin. |
| `setExpires(ms)` / `expires()` | `Promise<undefined>` / `Promise<DOMHighResTimeStamp?>` | Set or read the expiry; `expires()` resolves `null` when none is set. |
| `indexedDB`, `caches`, `getDirectory()` | `IDBFactory`, `CacheStorage`, `Promise<FileSystemDirectoryHandle>` | The bucket-scoped storage endpoints. |

## Exceptions

`open()`, `keys()`, and `delete()` reject rather than throw, except for the synchronous `SecurityError` Chromium raises when the whole API is unavailable to the context.

| Exception | Method | Condition |
|---|---|---|
| `TypeError` | `open()`, `keys()`, `delete()`, `estimate()` | No storage shelf is available for the origin (opaque origin, storage disabled); `open()` also rejects for an invalid name, an `expires` in the past, or a `quota` of zero or less. |
| `InvalidCharacterError` | `delete()` | Invalid bucket name, per the specification. Chromium rejects with `TypeError` here instead (see the observed note). |
| `InvalidStateError` | `persist()`, `persisted()`, `estimate()`, `setExpires()`, `expires()` | The bucket's `removed` flag is set: it was deleted or expired after you obtained the handle. |
| `QuotaExceededError` | `open()` | Chromium-specific: the origin has created more buckets than the implementation allows (`Too many buckets created.`). |
| `UnknownError` | `open()`, `keys()`, `delete()` | Chromium-specific: the storage backend failed (`Unknown error occured while creating a bucket.`, the typo is Chromium's). |
| `SecurityError` | any (thrown synchronously) | Chromium denies the API in this context, for example an opaque origin: `Access to Storage Buckets API is denied in this context.` |

:::observed
In Chrome, `navigator.storageBuckets.open('Inbox')` rejects with `TypeError: The bucket name 'Inbox' is not a valid name.` because of the capital letter, `open('inbox', { quota: 0 })` with `TypeError: The bucket's quota cannot equal zero.`, and `open('inbox', { expires: Date.now() - 1000 })` with `TypeError: The bucket expiration is invalid.` `navigator.storageBuckets.delete('Inbox')` rejects with `TypeError: The bucket name Inbox is not a valid name.` where the specification calls for `InvalidCharacterError`. All four strings are in Chromium's [`storage_bucket_manager.cc`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/buckets/storage_bucket_manager.cc) (chromium.googlesource.com).
:::

## Examples

Both examples resolve a storage endpoint first and run the same application code against it, so the fallback for Firefox and Safari is the origin's default storage rather than a second code path.

### Keeping drafts in a persisted bucket with the default IndexedDB as fallback

Drafts are the data the user would miss most, so they go in a bucket that asks for `persisted: true`. When `navigator.storageBuckets` is missing the same database opens on `window.indexedDB`, and when the persistence request is refused the code says so instead of assuming the bucket is safe.

```js
function openDatabase(factory, name, version) {
  return new Promise((resolve, reject) => {
    const request = factory.open(name, version);
    request.onupgradeneeded = () => request.result.createObjectStore('drafts', { keyPath: 'id' });
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

async function openDraftsDatabase() {
  if (!('storageBuckets' in navigator)) {
    return { db: await openDatabase(indexedDB, 'drafts', 1), persisted: false };
  }
  const bucket = await navigator.storageBuckets.open('drafts', { persisted: true });
  return {
    db: await openDatabase(bucket.indexedDB, 'drafts', 1),
    persisted: await bucket.persisted(), // false until persistent-storage is granted
  };
}

const { db, persisted } = await openDraftsDatabase();
if (!persisted) {
  document.querySelector('#storage-note').textContent =
    'Drafts are stored best-effort and may be cleared under storage pressure.';
}
```

The `persisted` flag comes back `false` in a fresh Chrome profile because `persistent-storage` has not been granted; calling `navigator.storage.persist()` from a user gesture first changes that outcome.

### A feed cache that expires on its own

A news feed cached for offline reading is worth keeping for a week and worthless after. The bucket carries an `expires` timestamp so the browser removes it without a cleanup job; the fallback stores the same responses in the default `caches` and prunes them by a stored date.

```js
const WEEK = 7 * 24 * 60 * 60 * 1000;

async function feedCache() {
  if ('storageBuckets' in navigator) {
    const bucket = await navigator.storageBuckets.open('feed', { expires: Date.now() + WEEK });
    return bucket.caches.open('articles');
  }
  const cache = await caches.open('articles');
  const stamp = await cache.match('/__cached-at');
  if (stamp && Date.now() - Number(await stamp.text()) > WEEK) {
    await caches.delete('articles');
    return caches.open('articles');
  }
  if (!stamp) await cache.put('/__cached-at', new Response(String(Date.now())));
  return cache;
}

const cache = await feedCache();
await cache.add('/api/feed?page=1');
```

Opening an existing bucket does not refresh its expiry, so a long-lived feed bucket needs `bucket.setExpires(Date.now() + WEEK)` after each successful sync to keep sliding the deadline.

## See also

- [IndexedDB: structured client-side storage for PWAs](/reference/storage/indexeddb/)
- [Storage persistence, quotas, and eviction](/reference/storage/persistence/), the origin-wide model buckets refine
- [Quotas and StorageManager.estimate()](/reference/storage/quota-estimate/)
- [Cache Storage: the request/response store behind offline PWAs](/reference/storage/cache-storage/)
- [Storage Buckets support](/compatibility/storage-buckets/)
- [Storage Buckets API: open() method](https://wicg.github.io/storage-buckets/#dom-storagebucketmanager-open) (wicg.github.io)
- [Storage Buckets API: validate a bucket name](https://wicg.github.io/storage-buckets/#validate-a-bucket-name) (wicg.github.io)
- [Storage Buckets explainer](https://github.com/WICG/storage-buckets/blob/main/explainer.md) (github.com)
- [Bugzilla 1594740: Implement support for storage buckets](https://bugzil.la/1594740) (bugzilla.mozilla.org)