Modern web apps keep far more on a device than a few preferences. They cache product catalogues, store drafts, queue edits made offline, hold downloaded media and sometimes maintain an entire local database. Yet most apps still treat all of that data as one undifferentiated blob.
That is the problem the Storage Buckets API addresses. It lets a site divide its origin storage into named buckets with different durability, persistence and lifecycle expectations. For teams building offline-capable and local-first products, that is a meaningful architectural shift, not just another browser API to watch.
TL;DR
Storage Buckets let a web app separate critical local data, such as unsent work or an offline mutation queue, from rebuildable caches. Chromium has supported the API since version 122. It is worth designing for now, with progressive enhancement and a clear fallback, because it turns storage eviction from an all-or-nothing risk into an explicit product decision.
Table of contents
- Why origin-wide storage is a product risk
- What a storage bucket is
- Choosing the right data for each bucket
- A practical implementation pattern
- Durability, persistence and expiration
- Designing a fallback that does not lie
- Operational and privacy considerations
- A rollout checklist
Why origin-wide storage is a product risk
Historically, browser storage is scoped primarily to an origin. IndexedDB, Cache Storage and the Origin Private File System give applications powerful local capabilities, but a product commonly has to make one broad storage decision: either it accepts that local data may be evicted under pressure, or it asks the browser to persist the origin.
That approach does not match the value of real application data.
Consider a field-service app. An authenticated user may have:
- forms completed offline but not yet uploaded;
- a local copy of the last 30 days of work orders;
- thumbnails and map tiles that can be fetched again;
- diagnostic logs useful for support, but not essential to the user.
Losing the first category may damage trust. Losing the second is inconvenient. Losing the latter two is usually acceptable. If every byte lives in the same logical place, the browser cannot use that distinction when storage is scarce.
The Storage Standard provides the broader model for browser-managed site storage. The Storage Buckets API builds on it by letting an application group data into independently managed buckets. The browser still controls its storage policies, but the app can finally communicate which data deserves a different treatment.
This matters even for apps with a server. “It is synced eventually” is not the same as “the user cannot lose work.” A draft, a payment form, a field report or an edit queue can exist only on the device for minutes or hours. Architecture should protect that window deliberately.
What a storage bucket is
A storage bucket is a named storage partition within an origin. A bucket can expose bucket-specific versions of familiar storage endpoints, including IndexedDB, Cache Storage and file-system access. Its options describe how the data should be handled.
At a high level, an app can create buckets such as:
critical-workfor unsynced user-created data;sync-cachefor server-backed records that are useful offline;media-cachefor large, replaceable downloads;diagnosticsfor logs with a short retention window.
The key word is independently. A browser can make retention decisions at the bucket level instead of treating every piece of storage from the origin alike. Bucket names are also a welcome bit of operational clarity: a developer inspecting the application can see intent instead of reverse-engineering it from database names.
The API is available only in secure contexts, so it belongs behind HTTPS like the rest of a modern offline stack. Chromium’s documentation says the feature has been available since Chromium 122, but cross-browser support must be checked before making it a hard requirement. Use feature detection, not browser sniffing.
Start with data classification, not an API call
The best implementation begins with a data inventory. For every client-side store, ask four questions:
- Can the server recreate it? If yes, it is usually cache-like.
- Can the user recreate it without real harm? If no, it is likely critical.
- How expensive is it to re-download or recompute? Large but replaceable data deserves a different policy from small critical data.
- How long is it valuable? A temporary export or debug trace should have an expiration strategy.
A useful first-cut design looks like this:
| Data | Bucket | Intent | | --- | --- | --- | | Offline mutations, unsent drafts | critical-work | Persisted, stricter durability | | Recently synced records | sync-cache | Rebuildable, normal durability | | Images, model files, media | media-cache | Rebuildable and easy to clear | | Logs and traces | diagnostics | Short-lived, aggressively disposable |
Do not put every IndexedDB database into a bucket merely because you can. The payoff comes from meaningful boundaries. If two data sets share the same recovery expectation, they can share a bucket.
A practical implementation pattern
The following example opens a dedicated bucket for a user’s offline edits. It uses strict durability as a signal that completed writes should be less likely to disappear after an abrupt power loss, and asks for persistence because this is data the user cannot necessarily reproduce.
export async function openCriticalWorkDatabase() {
if (!('storageBuckets' in navigator)) {
return openLegacyDatabase('critical-work');
}
const bucket = await navigator.storageBuckets.open('critical-work', {
persisted: true,
durability: 'strict',
});
return new Promise<IDBDatabase>((resolve, reject) => {
const request = bucket.indexedDB.open('offline-edits', 1);
request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains('mutations')) {
db.createObjectStore('mutations', { keyPath: 'id' });
}
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}The important detail is bucket.indexedDB, rather than the global indexedDB. The same idea applies to other bucket endpoints. You are not changing the query model of IndexedDB. You are choosing where that database lives and what policy it carries.
Keep the bucket boundary behind a small storage module. Application features should ask for criticalWorkDb() or mediaCache(), not repeat bucket setup throughout the codebase. That gives the team one place to adjust browser compatibility, migration and telemetry later.
For a Cache Storage example, a rebuildable media cache might look like this:
async function openMediaCache() {
if (!('storageBuckets' in navigator)) {
return caches.open('media-v1');
}
const bucket = await navigator.storageBuckets.open('media-cache', {
durability: 'relaxed',
});
return bucket.caches.open('media-v1');
}This is not a guarantee that media will be evicted before other data in every circumstance. Browser storage policies remain browser policies. It is an explicit, useful signal that these bytes are not user work and can trade a little resilience for performance.
Durability, persistence and expiration are different controls
These options are easy to conflate, and that can lead to false promises in a product spec.
Durability
durability is about the trade-off around writes and sudden power loss. A strict bucket attempts to reduce the chance of losing recently completed writes. A relaxed bucket can favour performance and device characteristics, while potentially losing the most recent writes after a sudden failure. It does not turn a web app into a database with an absolute durability guarantee.
Reserve strict for narrow, high-value paths. Applying it to a huge image cache would add cost without improving the user experience.
Persistence
persisted: true expresses that the bucket should receive persistent treatment. It is not a licence for unlimited device storage and should not be presented to users as permanent backup. Users can clear site data, browsers can enforce policy and storage pressure still exists. Your product still needs a sync and recovery story.
Expiration
The specification includes an expires option, enabling the application to express that a bucket has a limited useful lifetime. This can be particularly compelling for diagnostics, temporary preview assets and time-bounded offline packs. It is better than quietly leaving temporary data behind forever.
In practice, treat expiry as part of a broader lifecycle. Have application-level cleanup as well, because availability and details evolve across browsers. A background cleanup that removes stale records is useful whether or not bucket expiration is honoured.
Design a fallback that does not lie
Progressive enhancement is essential here. A fallback can still offer a strong experience, but it must not claim guarantees it cannot provide.
A good fallback strategy is:
- Use ordinary IndexedDB and Cache Storage when
navigator.storageBucketsis unavailable. - Keep critical data and disposable cache data in separate database and cache names anyway.
- Request origin persistence for a clear user benefit where appropriate, using
navigator.storage.persist(). - Sync critical mutations promptly and make the queue observable.
- Offer an explicit “clear downloaded data” control that never deletes unsynced work.
The separate logical stores in step two matter. They do not create independent browser eviction policies on their own, but they make migration simple and let the application delete its own cache safely. The platform API then becomes an upgrade, not a rewrite.
const supportsBuckets =
typeof navigator !== 'undefined' && 'storageBuckets' in navigator;
const storageMode = supportsBuckets ? 'bucketed' : 'legacy';
analytics.track('offline_storage_mode', { storageMode });Telemetry should record capability and outcomes, not user content. Measure sync success, queue age, storage-related failures and the time required to rebuild caches. That data will show whether the investment solves a real reliability problem.
Operational and privacy considerations
Storage buckets are an engineering tool, not a reason to retain more personal data locally. Apply the same principles you would apply to server storage:
- minimise what is retained;
- encrypt or otherwise protect sensitive data where your threat model requires it;
- avoid persisting credentials and secrets in application databases;
- document what “downloaded for offline use” means;
- make deletion understandable and reliable.
There is also a support benefit. If an offline workflow fails, a diagnostics bucket can contain bounded, non-sensitive evidence without mixing it with user content or permanent caches. Give it a retention period and a size budget. Observability data has a habit of expanding until somebody makes its disposal explicit.
Before deploying, test the unhappy paths. Fill storage in a test profile, close the tab during a write, clear site data and simulate a device that has been offline for days. Test a user who logs out before pending work has synced. The quality of a local-first feature is revealed by recovery, not by its happy-path demo.
A rollout checklist for product teams
Storage Buckets are most valuable when introduced as a small reliability project rather than a broad refactor.
- Map storage by recoverability. Identify unsynced, server-backed, rebuildable and temporary data.
- Define a small bucket taxonomy. Two to four buckets is a better starting point than ten.
- Extract a storage adapter. Keep native bucket calls out of feature components.
- Ship feature detection and legacy stores together. Users should get the same core workflow everywhere.
- Move one critical workflow first. Offline drafts or a mutation queue are excellent candidates.
- Add recovery telemetry. Track queue age, sync retries and storage errors without recording customer data.
- Test pressure and deletion. Treat those as release scenarios, not edge cases.
- Revisit browser support regularly. Standards and implementations change, so keep the enhancement boundary flexible.
The bigger lesson
The Storage Buckets API is interesting because it reflects a maturing web platform. The browser is no longer just a place to render a remote interface. For many products it is a local runtime with databases, files, caches and background work.
Once that is true, storage policy becomes product design. A team has to decide which data is a convenience, which is a liability and which represents a promise to the user. Named buckets make those choices visible in code.
You do not need to wait for universal support to benefit. Classify data, separate critical writes from disposable caches and build a trustworthy fallback now. When Storage Buckets are available, the browser can act on that architecture. When they are not, your app is still easier to reason about, operate and recover.
FAQ
What is the Storage Buckets API?
It is a web API that lets an origin create named storage groupings, each with its own lifecycle and durability-related options. Bucket-specific endpoints can be used for APIs such as IndexedDB and Cache Storage.
Does a persisted bucket guarantee that data can never be deleted?
No. Persistence communicates an important retention preference, but users can clear site data and browsers retain control of storage policy. Critical data should still sync to a server when possible.
Should every web app use Storage Buckets now?
No. They are most useful for offline-capable, local-first or media-heavy apps that have both irreplaceable local work and rebuildable caches. Use feature detection and retain a standard-storage fallback.
What is the difference between strict and relaxed durability?
Strict durability prioritises reducing loss of recently completed writes after an abrupt power failure. Relaxed durability can favour write performance and resource efficiency, while accepting a greater possibility that the latest writes are lost in that situation.