Watching Storage Usage with getBytesInUse
Measure chrome.storage usage before it hits quota: getBytesInUse per key and per area, sync quotas, warning thresholds, finding the keys that grow, and showing users how much space the extension uses.
Table of Contents
The first sign of a storage problem is usually an error from a user’s machine: “QUOTA_BYTES_PER_ITEM quota exceeded” from chrome.storage.sync, or writes to local failing after a year of accumulated history. By then the user has lost data. chrome.storage exposes exactly the information needed to see it coming — getBytesInUse reports how many bytes a key, a set of keys, or a whole area occupies — and a few lines of monitoring turn quota exhaustion from an incident into a warning. This guide shows how. It belongs to chrome.storage API and sync.
What the numbers mean
chrome.storage.<area>.getBytesInUse(keys) returns the size of the given keys, or of the whole area when called with null. The size is measured as the JSON serialisation of each value plus the length of its key — the same measure the quotas use. The quotas themselves are exposed as constants: chrome.storage.sync.QUOTA_BYTES (102,400 bytes total), QUOTA_BYTES_PER_ITEM (8,192 bytes), MAX_ITEMS (512) and write-rate limits; chrome.storage.local.QUOTA_BYTES (10,485,760 bytes, unless the extension has unlimitedStorage); and chrome.storage.session.QUOTA_BYTES (10 MB in recent Chrome). Comparing usage with those constants tells you how close each area is to its ceiling.
Step-by-step: monitor before it fails
1. Read usage for every area
1// sw.js
2export async function usageReport() {
3 const areas = {
4 sync: chrome.storage.sync,
5 local: chrome.storage.local,
6 session: chrome.storage.session,
7 };
8 const report = {};
9 for (const [name, area] of Object.entries(areas)) {
10 if (typeof area?.getBytesInUse !== "function") continue;
11 const used = await area.getBytesInUse(null);
12 const quota = area.QUOTA_BYTES ?? null;
13 report[name] = { used, quota, pct: quota ? Math.round((used / quota) * 100) : null };
14 }
15 return report;
16}
Execution context: the service worker or any extension page. QUOTA_BYTES is undefined for local when the extension declares unlimitedStorage in some versions, and very large in others; treat a missing quota as “no practical limit” and watch growth instead. Feature-detecting getBytesInUse keeps the code working in engines that do not implement it for every area.
2. Find the keys that grow
1export async function largestKeys(area = chrome.storage.local, top = 10) {
2 const all = await area.get(null);
3 const sizes = await Promise.all(Object.keys(all).map(async (k) => [k, await area.getBytesInUse(k)]));
4 return sizes.sort((a, b) => b[1] - a[1]).slice(0, top);
5}
Execution context: the service worker, typically from a debug page or on a schedule. Total usage tells you there is a problem; per-key usage tells you where. In practice one or two keys — an ever-growing history array, a cache that is never pruned — account for most of the space. Calling getBytesInUse per key is cheap for a few hundred keys; for thousands, compute sizes with new Blob([JSON.stringify(value)]).size as an approximation.
3. Check sync writes against per-item limits before writing
1export async function safeSyncSet(key, value) {
2 const bytes = new TextEncoder().encode(key + JSON.stringify(value)).length;
3 if (bytes > chrome.storage.sync.QUOTA_BYTES_PER_ITEM) {
4 throw Object.assign(new Error(`${key} is ${bytes} bytes; sync limit is ${chrome.storage.sync.QUOTA_BYTES_PER_ITEM}`), { code: "too-large" });
5 }
6 const used = await chrome.storage.sync.getBytesInUse(null);
7 const current = await chrome.storage.sync.getBytesInUse(key);
8 if (used - current + bytes > chrome.storage.sync.QUOTA_BYTES) {
9 throw Object.assign(new Error("sync storage would exceed its total quota"), { code: "quota" });
10 }
11 await chrome.storage.sync.set({ [key]: value });
12}
Execution context: any trusted context. Checking before writing turns a rejected write — whose data is simply lost — into an error your code can handle, for example by falling back to local and telling the user that this setting will not sync. Measuring with TextEncoder counts bytes, matching how the quota is measured for non-ASCII text.
4. Run a scheduled health check
1chrome.alarms.create("storage-health", { periodInMinutes: 7 * 24 * 60 });
2chrome.alarms.onAlarm.addListener(async ({ name }) => {
3 if (name !== "storage-health") return;
4 const report = await usageReport();
5 for (const [area, r] of Object.entries(report)) {
6 if (r.pct !== null && r.pct >= 70) {
7 await pruneOwnedCaches(area);
8 const after = (await usageReport())[area];
9 if (after.pct >= 70) await chrome.storage.local.set({ storageWarning: { area, pct: after.pct, at: Date.now() } });
10 }
11 }
12});
Execution context: the service worker, with the alarm listener at the top level. Seventy per cent leaves room to act before writes fail. Prune what the extension owns and can regenerate — caches, old logs, expired drafts — automatically; surface a warning for anything that is user data, so the user decides what to delete.
5. Show usage to the user
1// options.js
2const report = await chrome.runtime.sendMessage({ type: "storage:report" });
3const fmt = new Intl.NumberFormat(undefined, { style: "unit", unit: "kilobyte", maximumFractionDigits: 0 });
4usageEl.textContent = `Synced settings: ${fmt.format(report.sync.used / 1024)} of ${fmt.format(report.sync.quota / 1024)}`;
Execution context: the options page. Users rarely think about extension storage, but a short line in options — with a “Clear cached data” button next to it — answers support questions before they are asked and gives users a way out of quota trouble without uninstalling. Intl.NumberFormat with units localises the numbers.
6. Include usage in diagnostics
1export async function diagnostics() {
2 return {
3 version: chrome.runtime.getManifest().version,
4 storage: await usageReport(),
5 largest: await largestKeys(chrome.storage.local, 5),
6 };
7}
Execution context: the service worker, exposed through a “Copy diagnostics” button in options. Key names and sizes are enough to diagnose growth without including any user content. When a user reports lost settings, a diagnostics paste showing sync at 99% resolves the case in minutes.
7. Track usage over time in development
Add a debug page that records usageReport() daily during dogfooding and plots it. Growth that looks harmless over a week — a few kilobytes a day — becomes a quota failure after a year in the field. Catching the slope early is far cheaper than shipping a migration later.
Common mistakes
- Waiting for quota errors. By the time
setfails, the write’s data is gone. - Ignoring
QUOTA_BYTES_PER_ITEMin sync. A single 9 KB settings object fails even when the area is nearly empty. - Counting characters, not bytes. Non-ASCII text uses more bytes than characters.
- Pruning user data automatically. Prune caches you can rebuild; ask before deleting anything the user created.
- Unbounded arrays. History, logs and recents grow forever unless capped.
Cross-browser variation
- Chrome / Edge:
getBytesInUseonsync,localandsession; quota constants exposed on each area. - Firefox:
getBytesInUsesupport has varied by area and version — feature-detect it and fall back to measuring serialised size yourself. Sync quotas match Chrome’s. - Safari: limited
getBytesInUsesupport; measure serialised sizes manually and keepsyncusage small.
Verification
- Run
await chrome.storage.sync.getBytesInUse(null)and compare withchrome.storage.sync.QUOTA_BYTESin the worker console. - Write a 9 KB value with
safeSyncSetand confirm it throwstoo-largeinstead of failing silently. - Fill
localpast 70% in a test profile, trigger the health alarm, and confirm caches are pruned and a warning is stored. - Confirm the options page shows usage and the clean-up button reduces it.
FAQ
Does getBytesInUse include IndexedDB?
No. It measures only chrome.storage. Use navigator.storage.estimate() for IndexedDB, OPFS and the Cache API.
Is usage shared between Chrome profiles?
No. Each profile has its own storage and quotas; sync quotas apply per synced account.
Can I raise the sync quota?
No. Store large data in local or on your own server and sync only small preferences.
How often should the health check run?
Weekly is plenty for most extensions; storage grows slowly. Run it additionally after migrations and imports, which can add a lot of data at once, and on demand from the options page so a user troubleshooting a problem gets a current number.
Why does usage drop after a browser restart?
chrome.storage.session is cleared when the browser exits, so its usage resets to zero. local and sync usage should be stable across restarts; a drop there means something deleted data.
Related
- Handling storage quota exceeded errors — what to do when a write does fail.
- Storing large blobs and files in an extension — moving bulk data out of chrome.storage.
- Inspecting and editing extension storage — looking at the keys by hand.
- chrome.storage API and sync — the parent topic.