Chrome Storage Quotas for AI Chat Archives: What Fits and What Doesn't
Arjun Mehta
Senior Browser & Extensions Analyst
An AI assistant that forgets every conversation is a demo. An assistant that promises to save them runs into a storage ceiling that is smaller than most developers assume. chrome.storage.local gives an extension 10 MB by default, and a single long chat with code blocks can be hundreds of kilobytes. A few hundred archived conversations and the writes start failing with QUOTA_BYTES quota exceeded. Understanding the actual limits, and when to leave chrome.storage entirely, prevents a data-loss bug that only appears in long-term users.
The real limits, and why writes fail late and silently
chrome.storage.local defaults to 10 MB, expandable with the unlimitedStorage permission. chrome.storage.sync is far smaller — around 100 KB total and 8 KB per item — and is meant for settings and short prompt lists, not transcripts. chrome.storage.session is in-memory and capped at 10 MB.
The 10 MB is measured on serialized JSON, not raw text. Markdown with repeated keys, metadata objects, and message roles inflates quickly. A conversation with 40 turns, tool outputs, and syntax-highlighted code can serialize to 150–300 KB. At that rate, 10 MB holds roughly 30–60 long conversations.
unlimitedStorage removes the local cap but does not make the API fast or indexed. It also triggers a permission warning at install and a review question, because unlimited local storage can be used to hide content. Ask for it only if the product genuinely needs local archives.
chrome.storage.local.set is asynchronous and rejects on quota errors. Code that fires writes without awaiting them loses messages with no visible error. The typical bug: a chat component calls set on every token, hits the quota during a long response, and the last messages are missing after reload.
Debounce writes to conversation boundaries — after a completed assistant message, not per token. Wrap every write in a try/catch and surface a banner when it fails. If the write fails, keep the message in memory and offer export, so the user does not lose the response they just waited for.
Also watch the per-item write cost. Rewriting a single JSON blob containing all conversations means every save serializes the entire archive. That is O(n) per message and becomes visible at a few megabytes. Store one key per conversation and keep an index key listing conversation IDs.
When to move to IndexedDB
IndexedDB is the right home for transcripts above a few megabytes. It has no 10 MB ceiling in practice, supports indexes, and stores records without serializing the whole archive on each write. The API is verbose, so use a thin wrapper such as idb and define an object store keyed by conversation ID with an index on updatedAt.
An extension page can open IndexedDB directly. Content scripts share the page's origin, so they cannot open the extension's database; send data through chrome.runtime.sendMessage and let the worker or panel write it. That restriction trips up developers who test in a content script and later move the logic.
Search is the other reason to leave chrome.storage. Full-text search over an archive requires either an index or an in-memory scan. IndexedDB supports compound indexes on metadata; for text search, store a tokenized keyword array per conversation and query with IDBKeyRange on a multi-entry index.
What to sync and what to keep local
Chat history is sensitive. It can contain client names, medical details, and unreleased code. Syncing it to chrome.storage.sync puts it in Google's sync infrastructure, where the user may not expect it. Default to local-only, make export explicit, and if cloud sync is a feature, encrypt client-side before upload and say so in the UI.
For users who want portability, an export to JSON or Markdown beats silent sync. A single "Export all conversations" button that produces a zip is easy to implement with CompressionStream and removes the temptation to over-permission the extension.
Retention and eviction
Define retention before shipping. Options include keeping the last N conversations, keeping everything until quota pressure, or keeping nothing beyond the session. Whichever you choose, show it in settings and enforce it in one place.
If you auto-evict, never delete the conversation the user is currently viewing. Pin recent items, evict oldest-by-updatedAt, and log the eviction count so support can explain missing history. Silent deletion is the fastest way to lose trust in a tool whose entire value is memory.
Estimating size, and compression that pays for itself
Writes fail at the worst moment because nobody measured. Before saving a conversation, serialize it and measure the byte length with new Blob([JSON.stringify(conv)]).size. If the projected total exceeds 80% of the quota, evict or refuse before the write, not after.
Store a running total in an index key and update it on each write. Reading every conversation to compute size is O(n) and grows with the archive; a maintained counter costs one number and stays accurate if every mutation goes through one function.
JSON of chat transcripts compresses well because messages share structure. CompressionStream("gzip") is available in extension pages and reduces a typical archive by 60–75%. Store the compressed bytes in IndexedDB, not in chrome.storage.local, which serializes values as JSON and would base64 the bytes, inflating them again.
Compress at conversation boundaries. Per-token compression is wasteful, and per-message compression fragments the data so that search requires decompressing everything.
Export and import as a tested path
An export that has never been imported is a backup you cannot trust. Build both directions and test a round trip with a hundred conversations, including one with emoji, one with code fences, and one with a very long single message.
Store a schema version in the export header. When the format changes, import should migrate older files rather than reject them. Users keep exports for years, and an extension update that cannot read last year's backup is a data-loss event with a friendly error message.
The failure UX, and behaviour across profiles
When storage is full, the assistant must keep working for the current conversation and tell the user what will not be saved. Offer three actions: export now, delete old conversations, or continue without saving. Silently dropping the oldest conversation is the worst option because the user cannot tell which one disappeared.
chrome.storage.local is shared between normal and incognito contexts in split mode, which means private conversations can be written into the normal profile's archive. Gate writes on chrome.extension.inIncognitoContext and route incognito transcripts to storage.session only. That single check prevents the most damaging privacy bug an assistant can ship.
Related Guides & Reviews
Keeping an MV3 Service Worker Alive Through a Long AI Stream
Why Manifest V3 workers terminate mid-stream, which keepalive patterns Chrome actually accepts, and how to design an AI extension that survives a two-minute response.
Chrome Side Panel API for AI Assistants: Persistent Context Without a Popup Trap
How to build a persistent AI assistant with the Chrome Side Panel API: state retention, message routing, responsive layouts, and the failure modes popup UIs create.