Built-in Web Modules: How to Use KV Storage


While this tutorial has content that we believe is of great benefit to our community, we have not yet tested or edited it to ensure you have an error-free learning experience. It's on our list, and we're working on it! You can help us out by using the "report an issue" button at the bottom of the tutorial.

The release of KV Storage is a big deal for the web platform. It’s part of the Standard Library Proposal which could see a more extensive standard library being introduced for JavaScript.

Before jumping in to what kv-storage is, let’s first discuss how we can store data within the browser. If I wanted to store some local data right now, one of my main options would be the use of localStorage.

With that in mind, let’s create a simple Todo application with JavaScript that takes advantage of localStorage. We’ll need two files - index.html and main.js:

const TODOS_KEY = 'todos';
const ul = document.getElementsByTagName('ul')[0];

const showExistingTodos = todos => {
  if (todos.length > 0) {
    todos.map(todo => {

const addTodo = () => {
  const input = document.getElementById('todoInput').value;

  if (input.length > 0) {

    document.getElementById('todoInput').value = '';

const addLi = text => {
  const li = document.createElement('li');



const saveTodo = todo => {
  let loadedTodos = loadTodos();

  loadedTodos = [...loadedTodos, todo];

  localStorage.setItem(TODOS_KEY, JSON.stringify(loadedTodos));

const loadTodos = () => {
  const todos = JSON.parse(localStorage.getItem(TODOS_KEY));

  return todos != null ? todos : [];

const clearTodos = () => {

  const todos = Array.from(document.getElementsByTagName('li'));

  todos.map(todo => ul.removeChild(todo));

const todos = loadTodos();

<!DOCTYPE html>
<html lang="en">
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <input id="todoInput" type="text" />  
    <button onclick="addTodo()">Add Todo</button>
    <button onclick="clearTodos()">Clear todos</button>



  <script src="main.js"></script>

While this could definitely be improved, we now have an application that we can use as an example. If we try and add some Todo items and then refresh the page, we’ll see that they appear instantly!

Hmm… I did some research and found out that localStorage is synchronous. What does that mean for our application?

Essentially, this means that calls to localStorage will block rendering inside of the DOM. This may may represent a problem if we had lots of elements in localStorage and will significantly impact performance.

Let’s take a look at an experimental built-in module named KV storage (for key/value storage). This is built on IndexedDB, an asynchronous storage API.

Why not use IndexedDB natively then?

The use of KV storage gives us a more intuitive API that is similar to localStorage. We also don’t need to turn to a third party IndexedDB library, we can now use this directly from within the browser!

KV Storage

For this example we’ll need to enable Experimental Features within Chrome. Inside of your Chrome browser, navigate to the following address:

Select “Enabled” on Experimental Web Platform features: [chrome://flags/#enable-experimental-web-platform-features](chrome://flags/#enable-experimental-web-platform-features).

It’s also a good idea to use Chrome Canary for any and all experimental/new Web features. I’ll be using it for our example.

We’ll now need to perform the following updates:

Inside of index.html, import main.js as a module:

<script type="module" src="main.js"></script>

Next, we can update our saveTodo function to use storage.set() instead of localStorage.setItem()

const saveTodo = async todo => {
  let loadedTodos = await loadTodos();

  loadedTodos = [...loadedTodos, todo];

  await storage.set(TODOS_KEY, loadedTodos);

Our loadTodos function uses storage.get() instead of localStorage.getItem():

const loadTodos = async () => {
  const todos = await storage.get(TODOS_KEY);

  return todos != null ? todos : [];

Notice here how we’re dealing with the asynchronicity with ease using async/await functions.

Finally, we can improve our clearTodos function by using storage.delete() instead of localStorage.removeItem():

const clearTodos = () => {

  const todos = Array.from(document.getElementsByTagName('li'));

  todos.map(todo => ul.removeChild(todo));

We’ll also need to expose these to the window object:

window.addTodo = addTodo;
window.clearTodos = clearTodos;

Our application now works once again, but instead of localStorage it uses the std:kv-storage built-in Web module. The best thing about this? It uses IndexedDB under the hood!

This means everything is asynchronous (as referenced by our async and await promise calls, too).

Client support

What if the browser doesn’t support kv-storage? At the moment, this is extremely likely. Luckily, there’s a polyfill available here: https://github.com/GoogleChromeLabs/kv-storage-polyfill.

I’d recommend you add this to your application if you plan on using kv-storage in production at this stage.

Further reading

Google has prepared a demo on kv-storage that you may find interesting to read. Here’s the URL: https://rollup-built-in-modules.glitch.me/

Creative Commons License