Storing and manipulating data in the browser — also known as client-side storage — is useful when it’s not necessary or practical to send it to the web server.
Situations for storing and manipulating data in the browser include:
- retaining the state of a client-side application — such as the current screen, entered data, user preferences, etc.
- utilities which access local data or files and have strict privacy requirements
- progressive web apps (PWAs) which work offline
Here are ten ways to store browser data:
- with JavaScript variables
- via DOM Node Storage
- via Web Storage (
localStorage
andsessionStorage
) - with IndexedDB
- with the Cache API (don’t use AppCache!)
- via the File System Access API
- via the File and Directory Entries API
- with cookies
- by using window.name
- with WebSQL
This article digs into these ten different ways to store data in the browser, covering their limits, pros, cons, and the best uses of each technique.
Before we browse the options, a quick note about data persistence …
Data Persistence
Table of Contents
In general, data you store will either be:
- persistent: it remains until your code chooses to delete it, or
- volatile: it remains until the browser session ends, typically when the user closes the tab
The reality is more nuanced.
Persistent data can be blocked or deleted by the user, operating system, browser, or plugins at any point. The browser can decide to delete older or larger items as it approaches the capacity allocated to that storage type.
Browsers also record page state. You can navigate away from a site and click back or close and re-open a tab; the page should look identical. Variables and data regarded as session-only are still available.
1. JavaScript Variables
metric | comment |
---|---|
capacity | no strict limit but browser slowdowns or crashes could occur as you fill memory |
read/write speed | the fastest option |
persistence | poor: data is wiped by a browser refresh |
Storing state in JavaScript variables is the quickest and easiest option. I’m sure you don’t need an example, but …
const a = 1, b = 'two', state = { msg: 'Hello', name: 'Craig' };
Advantages:
- easy to use
- fast
- no need for serialization or de-serialization
Disadvantages:
- fragile: refreshing or closing the tab wipes everything
- third-party scripts can examine or overwrite global (
window
) values
You’re already using variables. You could consider permanently storing variable state when the page unloads.
2. DOM Node Storage
metric | comment |
---|---|
capacity | no strict limit but not ideal for lots of data |
read/write speed | fast |
persistence | poor: data can be wiped by other scripts or a refresh |
Most DOM elements, either on the page or in-memory, can store values in named attributes. It’s safer to use attribute names prefixed with data-
:
- the attribute will never have associated HTML functionality
- you can access values via a dataset property rather than the longer .setAttribute() and .getAttribute() methods.
Values are stored as strings so serialization and de-serialization may be required. For example:
// locate <main> element const main = document.querySelector('main'); // store values main.dataset.value1 = 1; main.dataset.state = JSON.stringify({ a:1, b:2 }); // retreive values console.log( main.dataset.value1 ); // "1" console.log( JSON.parse(main.dataset.state).a ); // 1
Advantages:
- you can define values in JavaScript or HTML — such as
<main data-value1="1">
- useful for storing the state of a specific component
- the DOM is fast! (contrary to popular opinion)
Disadvantages:
- fragile: refreshing or closing the tab wipes everything (unless a value is server-rendered into the HTML)
- strings only: requires serialization and de-serialization
- a larger DOM affects performance
- third-party scripts can examine or overwrite values
DOM node storage is slower than variables. Use it sparingly in situations where it’s practical to store a component’s state in HTML.
3. Web Storage (localStorage
and sessionStorage
)
metric | comment |
---|---|
capacity | 5MB per domain |
read/write speed | synchronous operation: can be slow |
persistence | data remains until cleared |
Web Storage provides two similar APIs to define name/value pairs. Use:
window.localStorage
to store persistent data, andwindow.sessionStorage
to retain session-only data while the browser tab remains open (but see Data Persistence)
Store or update named items with .setItem():
localStorage.setItem('value1', 123); localStorage.setItem('value2', 'abc'); localStorage.setItem('state', JSON.stringify({ a:1, b:2, c:3 }));
Retrieve them with .getItem():
const state = JSON.parse( localStorage.getItem('state') );
And delete them with .removeItem():
localStorage.removeItem('state')
Other properties and methods include:
- .length: the number of items stored
- .key(N): the name of the Nth key
- .clear(): delete all stored items
Changing any value raises a storage event in other browser tabs/windows connected to the same domain. Your application can respond accordingly:
window.addEventListener('storage', s => { console.log(`item changed: ${ s.key }`); console.log(`from value : ${ s.oldValue }`); console.log(`to new value: ${ s.newValue }`); });
Advantages:
- simple name/value pair API
- session and persistent storage options
- good browser support
Disadvantages:
- strings only: requires serialization and de-serialization
- unstructured data with no transactions, indexing, or search
- synchronous access will affect the performance of large datasets
Web Storage is ideal for simpler, smaller, ad-hoc values. It’s less practical for storing large volumes of structured information, but you may be able to avoid performance issues by writing data when the page unloads.
4. IndexedDB
metric | comment |
---|---|
capacity | depends on device. At least 1GB, but can be up to 60% of remaining disk space |
read/write speed | fast |
persistence | data remains until cleared |
IndexedDB offers a NoSQL-like low-level API for storing large volumes of data. The store can be indexed, updated using transactions, and searched using asynchronous methods.
The IndexedDB API is complex and requires some event juggling. The following function opens a database connection when passed a name, version number, and optional upgrade function (called when the version number changes):
// connect function dbConnect(dbName, version, upgrade) { return new Promise((resolve, reject) => { const request = indexedDB.open(dbName, version); request.onsuccess = e => { resolve(e.target.result); }; request.onerror = e => { console.error(`indexedDB error: ${ e.target.errorCode }`); }; request.onupgradeneeded = upgrade; }); }
The following code connects to a myDB
database and initializes a todo
object store (analogous to a SQL table or MongoDB collection). It then defines an auto-incrementing key named id
:
(async () => { const db = await dbConnect('myDB', 1.0, e => { db = e.target.result; const store = db.createObjectStore('todo', { keyPath: 'id', autoIncrement: true }); }) })();
Once the db
connection is ready, you can .add
new data items in a transaction:
db.transaction(['todo'], 'readwrite') .objectStore('todo') .add({ task: 'do something' }) .onsuccess = () => console.log( 'added' );
And you can retrieve values, such as the first item:
db.transaction(['todo'], 'readonly') .objectStore('todo') .get(1) .onsuccess = data => console.log( data.target.result ); // { id: 1, task: 'do something' }
Advantages:
- flexible data store with the largest space
- robust transactions, indexing, and search options
- good browser support
Disadvantages:
- a complex callback and event-based API
IndexedDB is the best option for reliably storing large quantities of data, but you’ll want to reach for a wrapper library such as idb, Dexie.js, or JsStore.
Continue reading 10 Client-side Storage Options and When to Use Them on SitePoint.