CINXE.COM
Client-side storage - Learn web development | MDN
<!doctype html><html lang="en-US" prefix="og: https://ogp.me/ns#"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="icon" href="https://developer.mozilla.org/favicon-48x48.bc390275e955dacb2e65.png"/><link rel="apple-touch-icon" href="https://developer.mozilla.org/apple-touch-icon.528534bba673c38049c2.png"/><meta name="theme-color" content="#ffffff"/><link rel="manifest" href="https://developer.mozilla.org/manifest.f42880861b394dd4dc9b.json"/><link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="MDN Web Docs"/><title>Client-side storage - Learn web development | MDN</title><link rel="alternate" title="Client-side storage" href="https://developer.mozilla.org/de/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" hrefLang="de"/><link rel="alternate" title="Almacenamiento del lado cliente" href="https://developer.mozilla.org/es/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" hrefLang="es"/><link rel="alternate" title="Stockage côté client" href="https://developer.mozilla.org/fr/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" hrefLang="fr"/><link rel="alternate" title="クライアント側ストレージ" href="https://developer.mozilla.org/ja/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" hrefLang="ja"/><link rel="alternate" title="Client-side storage" href="https://developer.mozilla.org/pt-BR/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" hrefLang="pt"/><link rel="alternate" title="Client-side storage" href="https://developer.mozilla.org/ru/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" hrefLang="ru"/><link rel="alternate" title="客户端存储" href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" hrefLang="zh"/><link rel="alternate" title="Client-side storage" href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" hrefLang="en"/><link rel="preload" as="font" type="font/woff2" href="/static/media/Inter.var.c2fe3cb2b7c746f7966a.woff2" crossorigin=""/><link rel="alternate" type="application/rss+xml" title="MDN Blog RSS Feed" href="https://developer.mozilla.org/en-US/blog/rss.xml" hrefLang="en"/><meta name="description" content="That's it for now. We hope you've found our rundown of client-side storage technologies useful."/><meta property="og:url" content="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage"/><meta property="og:title" content="Client-side storage - Learn web development | MDN"/><meta property="og:type" content="website"/><meta property="og:locale" content="en_US"/><meta property="og:description" content="That's it for now. We hope you've found our rundown of client-side storage technologies useful."/><meta property="og:image" content="https://developer.mozilla.org/mdn-social-share.d893525a4fb5fb1f67a2.png"/><meta property="og:image:type" content="image/png"/><meta property="og:image:height" content="1080"/><meta property="og:image:width" content="1920"/><meta property="og:image:alt" content="The MDN Web Docs logo, featuring a blue accent color, displayed on a solid black background."/><meta property="og:site_name" content="MDN Web Docs"/><meta name="twitter:card" content="summary_large_image"/><meta name="twitter:creator" content="MozDevNet"/><link rel="canonical" href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage"/><style media="print">.article-actions-container,.document-toc-container,.language-menu,.main-menu-toggle,.on-github,.page-footer,.place,.sidebar,.top-banner,.top-navigation-main,ul.prev-next{display:none!important}.main-page-content,.main-page-content pre{padding:2px}.main-page-content pre{border-left-width:2px}</style><script src="/static/js/gtag.js" defer=""></script><script defer="" src="/static/js/main.5e889624.js"></script><link href="/static/css/main.26c64ea7.css" rel="stylesheet"/></head><body><script>if(document.body.addEventListener("load",(t=>{t.target.classList.contains("interactive")&&t.target.setAttribute("data-readystate","complete")}),{capture:!0}),window&&document.documentElement){const t={light:"#ffffff",dark:"#1b1b1b"};try{const e=window.localStorage.getItem("theme");e&&(document.documentElement.className=e,document.documentElement.style.backgroundColor=t[e]);const o=window.localStorage.getItem("nop");o&&(document.documentElement.dataset.nop=o)}catch(t){console.warn("Unable to read theme from localStorage",t)}}</script><div id="root"><ul id="nav-access" class="a11y-nav"><li><a id="skip-main" href="#content">Skip to main content</a></li><li><a id="skip-search" href="#top-nav-search-input">Skip to search</a></li><li><a id="skip-select-language" href="#languages-switcher-button">Skip to select language</a></li></ul><div class="page-wrapper category-javascript document-page"><div class="top-banner loading"><section class="place top container"></section></div><div class="sticky-header-container"><header class="top-navigation "><div class="container "><div class="top-navigation-wrap"><a href="/en-US/" class="logo" aria-label="MDN homepage"><svg id="mdn-docs-logo" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 694.9 104.4" style="enable-background:new 0 0 694.9 104.4" xml:space="preserve" role="img"><title>MDN Web Docs</title><path d="M40.3 0 11.7 92.1H0L28.5 0h11.8zm10.4 0v92.1H40.3V0h10.4zM91 0 62.5 92.1H50.8L79.3 0H91zm10.4 0v92.1H91V0h10.4z" class="logo-m"></path><path d="M627.9 95.6h67v8.8h-67v-8.8z" class="logo-_"></path><path d="M367 42h-4l-10.7 30.8h-5.5l-10.8-26h-.4l-10.5 26h-5.2L308.7 42h-3.8v-5.6H323V42h-6.5l6.8 20.4h.4l10.3-26h4.7l11.2 26h.5l5.7-20.3h-6.2v-5.6H367V42zm34.9 20c-.4 3.2-2 5.9-4.7 8.2-2.8 2.3-6.5 3.4-11.3 3.4-5.4 0-9.7-1.6-13.1-4.7-3.3-3.2-5-7.7-5-13.7 0-5.7 1.6-10.3 4.7-14s7.4-5.5 12.9-5.5c5.1 0 9.1 1.6 11.9 4.7s4.3 6.9 4.3 11.3c0 1.5-.2 3-.5 4.7h-25.6c.3 7.7 4 11.6 10.9 11.6 2.9 0 5.1-.7 6.5-2 1.5-1.4 2.5-3 3-4.9l6 .9zM394 51.3c.2-2.4-.4-4.7-1.8-6.9s-3.8-3.3-7-3.3c-3.1 0-5.3 1-6.9 3-1.5 2-2.5 4.4-2.8 7.2H394zm51 2.4c0 5-1.3 9.5-4 13.7s-6.9 6.2-12.7 6.2c-6 0-10.3-2.2-12.7-6.7-.1.4-.2 1.4-.4 2.9s-.3 2.5-.4 2.9h-7.3c.3-1.7.6-3.5.8-5.3.3-1.8.4-3.7.4-5.5V22.3h-6v-5.6H416v27c1.1-2.2 2.7-4.1 4.7-5.7 2-1.6 4.8-2.4 8.4-2.4 4.6 0 8.4 1.6 11.4 4.7 3 3.2 4.5 7.6 4.5 13.4zm-7.7.6c0-4.2-1-7.4-3-9.5-2-2.2-4.4-3.3-7.4-3.3-3.4 0-6 1.2-8 3.7-1.9 2.4-2.9 5-3 7.7V57c0 3 1 5.6 3 7.7s4.5 3.1 7.6 3.1c3.6 0 6.3-1.3 8.1-3.9 1.8-2.7 2.7-5.9 2.7-9.6zm69.2 18.5h-13.2v-7.2c-1.2 2.2-2.8 4.1-4.9 5.6-2.1 1.6-4.8 2.4-8.3 2.4-4.8 0-8.7-1.6-11.6-4.9-2.9-3.2-4.3-7.7-4.3-13.3 0-5 1.3-9.6 4-13.7 2.6-4.1 6.9-6.2 12.8-6.2 5.7 0 9.8 2.2 12.3 6.5V22.3h-8.6v-5.6h15.8v50.6h6v5.5zM493.2 56v-4.4c-.1-3-1.2-5.5-3.2-7.3s-4.4-2.8-7.2-2.8c-3.6 0-6.3 1.3-8.2 3.9-1.9 2.6-2.8 5.8-2.8 9.6 0 4.1 1 7.3 3 9.5s4.5 3.3 7.4 3.3c3.2 0 5.8-1.3 7.8-3.8 2.1-2.6 3.1-5.3 3.2-8zm53.1-1.4c0 5.6-1.8 10.2-5.3 13.7s-8.2 5.3-13.9 5.3-10.1-1.7-13.4-5.1c-3.3-3.4-5-7.9-5-13.5 0-5.3 1.6-9.9 4.7-13.7 3.2-3.8 7.9-5.7 14.2-5.7s11 1.9 14.1 5.7c3 3.7 4.6 8.1 4.6 13.3zm-7.7-.2c0-4-1-7.2-3-9.5s-4.8-3.5-8.2-3.5c-3.6 0-6.4 1.2-8.3 3.7s-2.9 5.6-2.9 9.5c0 3.7.9 6.8 2.8 9.4 1.9 2.6 4.6 3.9 8.3 3.9 3.6 0 6.4-1.3 8.4-3.8 1.9-2.6 2.9-5.8 2.9-9.7zm45 5.8c-.4 3.2-1.9 6.3-4.4 9.1-2.5 2.9-6.4 4.3-11.8 4.3-5.2 0-9.4-1.6-12.6-4.8-3.2-3.2-4.8-7.7-4.8-13.7 0-5.5 1.6-10.1 4.7-13.9 3.2-3.8 7.6-5.7 13.2-5.7 2.3 0 4.6.3 6.7.8 2.2.5 4.2 1.5 6.2 2.9l1.5 9.5-5.9.7-1.3-6.1c-2.1-1.2-4.5-1.8-7.2-1.8-3.5 0-6.1 1.2-7.7 3.7-1.7 2.5-2.5 5.7-2.5 9.6 0 4.1.9 7.3 2.7 9.5 1.8 2.3 4.4 3.4 7.8 3.4 5.2 0 8.2-2.9 9.2-8.8l6.2 1.3zm34.7 1.9c0 3.6-1.5 6.5-4.6 8.5s-7 3-11.7 3c-5.7 0-10.6-1.2-14.6-3.6l1.2-8.8 5.7.6-.2 4.7c1.1.5 2.3.9 3.6 1.1s2.6.3 3.9.3c2.4 0 4.5-.4 6.5-1.3 1.9-.9 2.9-2.2 2.9-4.1 0-1.8-.8-3.1-2.3-3.8s-3.5-1.3-5.8-1.7-4.6-.9-6.9-1.4c-2.3-.6-4.2-1.6-5.7-2.9-1.6-1.4-2.3-3.5-2.3-6.3 0-4.1 1.5-6.9 4.6-8.5s6.4-2.4 9.9-2.4c2.6 0 5 .3 7.2.9 2.2.6 4.3 1.4 6.1 2.4l.8 8.8-5.8.7-.8-5.7c-2.3-1-4.7-1.6-7.2-1.6-2.1 0-3.7.4-5.1 1.1-1.3.8-2 2-2 3.8 0 1.7.8 2.9 2.3 3.6 1.5.7 3.4 1.2 5.7 1.6 2.2.4 4.5.8 6.7 1.4 2.2.6 4.1 1.6 5.7 3 1.4 1.6 2.2 3.7 2.2 6.6zM197.6 73.2h-17.1v-5.5h3.8V51.9c0-3.7-.7-6.3-2.1-7.9-1.4-1.6-3.3-2.3-5.7-2.3-3.2 0-5.6 1.1-7.2 3.4s-2.4 4.6-2.5 6.9v15.6h6v5.5h-17.1v-5.5h3.8V51.9c0-3.8-.7-6.4-2.1-7.9-1.4-1.5-3.3-2.3-5.6-2.3-3.2 0-5.5 1.1-7.2 3.3-1.6 2.2-2.4 4.5-2.5 6.9v15.8h6.9v5.5h-20.2v-5.5h6V42.4h-6.1v-5.6h13.4v6.4c1.2-2.1 2.7-3.8 4.7-5.2 2-1.3 4.4-2 7.3-2s5.3.7 7.5 2.1c2.2 1.4 3.7 3.5 4.5 6.4 1.1-2.5 2.7-4.5 4.9-6.1s4.8-2.4 7.9-2.4c3.5 0 6.5 1.1 8.9 3.3s3.7 5.6 3.7 10.2v18.2h6.1v5.5zm42.5 0h-13.2V66c-1.2 2.2-2.8 4.1-4.9 5.6-2.1 1.6-4.8 2.4-8.3 2.4-4.8 0-8.7-1.6-11.6-4.9-2.9-3.2-4.3-7.7-4.3-13.3 0-5 1.3-9.6 4-13.7 2.6-4.1 6.9-6.2 12.8-6.2s9.8 2.2 12.3 6.5V22.7h-8.6v-5.6h15.8v50.6h6v5.5zm-13.3-16.8V52c-.1-3-1.2-5.5-3.2-7.3s-4.4-2.8-7.2-2.8c-3.6 0-6.3 1.3-8.2 3.9-1.9 2.6-2.8 5.8-2.8 9.6 0 4.1 1 7.3 3 9.5s4.5 3.3 7.4 3.3c3.2 0 5.8-1.3 7.8-3.8 2.1-2.6 3.1-5.3 3.2-8zm61.5 16.8H269v-5.5h6V51.9c0-3.7-.7-6.3-2.2-7.9-1.4-1.6-3.4-2.3-5.7-2.3-3.1 0-5.6 1-7.4 3s-2.8 4.4-2.9 7v15.9h6v5.5h-19.3v-5.5h6V42.4h-6.2v-5.6h13.6V43c2.6-4.6 6.8-6.9 12.7-6.9 3.6 0 6.7 1.1 9.2 3.3s3.7 5.6 3.7 10.2v18.2h6v5.4h-.2z" class="logo-text"></path></svg></a><button title="Open main menu" type="button" class="button action has-icon main-menu-toggle" aria-haspopup="menu" aria-label="Open main menu" aria-expanded="false"><span class="button-wrap"><span class="icon icon-menu "></span><span class="visually-hidden">Open main menu</span></span></button></div><div class="top-navigation-main"><nav class="main-nav" aria-label="Main menu"><ul class="main-menu nojs"><li class="top-level-entry-container "><button type="button" id="references-button" class="top-level-entry menu-toggle" aria-controls="references-menu" aria-expanded="false">References</button><a href="/en-US/docs/Web" class="top-level-entry">References</a><ul id="references-menu" class="submenu references hidden inline-submenu-lg" aria-labelledby="references-button"><li class="apis-link-container mobile-only "><a href="/en-US/docs/Web" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Overview / Web Technology</div><p class="submenu-item-description">Web technology reference for developers</p></div></a></li><li class="html-link-container "><a href="/en-US/docs/Web/HTML" class="submenu-item "><div class="submenu-icon html"></div><div class="submenu-content-container"><div class="submenu-item-heading">HTML</div><p class="submenu-item-description">Structure of content on the web</p></div></a></li><li class="css-link-container "><a href="/en-US/docs/Web/CSS" class="submenu-item "><div class="submenu-icon css"></div><div class="submenu-content-container"><div class="submenu-item-heading">CSS</div><p class="submenu-item-description">Code used to describe document style</p></div></a></li><li class="javascript-link-container "><a href="/en-US/docs/Web/JavaScript" class="submenu-item "><div class="submenu-icon javascript"></div><div class="submenu-content-container"><div class="submenu-item-heading">JavaScript</div><p class="submenu-item-description">General-purpose scripting language</p></div></a></li><li class="http-link-container "><a href="/en-US/docs/Web/HTTP" class="submenu-item "><div class="submenu-icon http"></div><div class="submenu-content-container"><div class="submenu-item-heading">HTTP</div><p class="submenu-item-description">Protocol for transmitting web resources</p></div></a></li><li class="apis-link-container "><a href="/en-US/docs/Web/API" class="submenu-item "><div class="submenu-icon apis"></div><div class="submenu-content-container"><div class="submenu-item-heading">Web APIs</div><p class="submenu-item-description">Interfaces for building web applications</p></div></a></li><li class="apis-link-container "><a href="/en-US/docs/Mozilla/Add-ons/WebExtensions" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Web Extensions</div><p class="submenu-item-description">Developing extensions for web browsers</p></div></a></li><li class="apis-link-container desktop-only "><a href="/en-US/docs/Web" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Web Technology</div><p class="submenu-item-description">Web technology reference for developers</p></div></a></li></ul></li><li class="top-level-entry-container active"><button type="button" id="guides-button" class="top-level-entry menu-toggle" aria-controls="guides-menu" aria-expanded="false">Guides</button><a href="/en-US/docs/Learn" class="top-level-entry">Guides</a><ul id="guides-menu" class="submenu guides hidden inline-submenu-lg" aria-labelledby="guides-button"><li class="apis-link-container mobile-only "><a href="/en-US/docs/Learn" class="submenu-item "><div class="submenu-icon learn"></div><div class="submenu-content-container"><div class="submenu-item-heading">Overview / MDN Learning Area</div><p class="submenu-item-description">Learn web development</p></div></a></li><li class="apis-link-container desktop-only "><a href="/en-US/docs/Learn" class="submenu-item "><div class="submenu-icon learn"></div><div class="submenu-content-container"><div class="submenu-item-heading">MDN Learning Area</div><p class="submenu-item-description">Learn web development</p></div></a></li><li class="html-link-container "><a href="/en-US/docs/Learn/HTML" class="submenu-item "><div class="submenu-icon html"></div><div class="submenu-content-container"><div class="submenu-item-heading">HTML</div><p class="submenu-item-description">Learn to structure web content with HTML</p></div></a></li><li class="css-link-container "><a href="/en-US/docs/Learn/CSS" class="submenu-item "><div class="submenu-icon css"></div><div class="submenu-content-container"><div class="submenu-item-heading">CSS</div><p class="submenu-item-description">Learn to style content using CSS</p></div></a></li><li class="javascript-link-container "><a href="/en-US/docs/Learn/JavaScript" class="submenu-item "><div class="submenu-icon javascript"></div><div class="submenu-content-container"><div class="submenu-item-heading">JavaScript</div><p class="submenu-item-description">Learn to run scripts in the browser</p></div></a></li><li class=" "><a href="/en-US/docs/Web/Accessibility" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Accessibility</div><p class="submenu-item-description">Learn to make the web accessible to all</p></div></a></li></ul></li><li class="top-level-entry-container "><button type="button" id="mdn-plus-button" class="top-level-entry menu-toggle" aria-controls="mdn-plus-menu" aria-expanded="false">Plus</button><a href="/en-US/plus" class="top-level-entry">Plus</a><ul id="mdn-plus-menu" class="submenu mdn-plus hidden inline-submenu-lg" aria-labelledby="mdn-plus-button"><li class=" "><a href="/en-US/plus" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Overview</div><p class="submenu-item-description">A customized MDN experience</p></div></a></li><li class=" "><a href="/en-US/plus/ai-help" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">AI Help</div><p class="submenu-item-description">Get real-time assistance and support</p></div></a></li><li class=" "><a href="/en-US/plus/updates" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Updates</div><p class="submenu-item-description">All browser compatibility updates at a glance</p></div></a></li><li class=" "><a href="/en-US/plus/docs/features/overview" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Documentation</div><p class="submenu-item-description">Learn how to use MDN Plus</p></div></a></li><li class=" "><a href="/en-US/plus/docs/faq" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">FAQ</div><p class="submenu-item-description">Frequently asked questions about MDN Plus</p></div></a></li></ul></li><li class="top-level-entry-container "><a class="top-level-entry menu-link" href="/en-US/curriculum/">Curriculum <sup class="new">New</sup></a></li><li class="top-level-entry-container "><a class="top-level-entry menu-link" href="/en-US/blog/">Blog</a></li><li class="top-level-entry-container "><button type="button" id="tools-button" class="top-level-entry menu-toggle" aria-controls="tools-menu" aria-expanded="false">Tools</button><ul id="tools-menu" class="submenu tools hidden inline-submenu-lg" aria-labelledby="tools-button"><li class=" "><a href="/en-US/play" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Playground</div><p class="submenu-item-description">Write, test and share your code</p></div></a></li><li class=" "><a href="/en-US/observatory" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">HTTP Observatory</div><p class="submenu-item-description">Scan a website for free</p></div></a></li><li class=" "><a href="/en-US/plus/ai-help" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">AI Help</div><p class="submenu-item-description">Get real-time assistance and support</p></div></a></li></ul></li></ul></nav><div class="header-search"><form action="/en-US/search" class="search-form search-widget" id="top-nav-search-form" role="search"><label id="top-nav-search-label" for="top-nav-search-input" class="visually-hidden">Search MDN</label><input aria-activedescendant="" aria-autocomplete="list" aria-controls="top-nav-search-menu" aria-expanded="false" aria-labelledby="top-nav-search-label" autoComplete="off" id="top-nav-search-input" role="combobox" type="search" class="search-input-field" name="q" placeholder=" " required="" value=""/><button type="button" class="button action has-icon clear-search-button"><span class="button-wrap"><span class="icon icon-cancel "></span><span class="visually-hidden">Clear search input</span></span></button><button type="submit" class="button action has-icon search-button"><span class="button-wrap"><span class="icon icon-search "></span><span class="visually-hidden">Search</span></span></button><div id="top-nav-search-menu" role="listbox" aria-labelledby="top-nav-search-label"></div></form></div><div class="theme-switcher-menu"><button type="button" class="button action has-icon theme-switcher-menu small" aria-haspopup="menu"><span class="button-wrap"><span class="icon icon-theme-os-default "></span>Theme</span></button></div><ul class="auth-container"><li><a href="/users/fxa/login/authenticate/?next=%2Fen-US%2Fdocs%2FLearn%2FJavaScript%2FClient-side_web_APIs%2FClient-side_storage" class="login-link" rel="nofollow">Log in</a></li><li><a href="/users/fxa/login/authenticate/?next=%2Fen-US%2Fdocs%2FLearn%2FJavaScript%2FClient-side_web_APIs%2FClient-side_storage" target="_self" rel="nofollow" class="button primary mdn-plus-subscribe-link"><span class="button-wrap">Sign up for free</span></a></li></ul></div></div></header><div class="article-actions-container"><div class="container"><button type="button" class="button action has-icon sidebar-button" aria-label="Expand sidebar" aria-expanded="false" aria-controls="sidebar-quicklinks"><span class="button-wrap"><span class="icon icon-sidebar "></span></span></button><nav class="breadcrumbs-container" aria-label="Breadcrumb"><ol typeof="BreadcrumbList" vocab="https://schema.org/" aria-label="breadcrumbs"><li property="itemListElement" typeof="ListItem"><a href="/en-US/docs/Learn" class="breadcrumb" property="item" typeof="WebPage"><span property="name">Guides</span></a><meta property="position" content="1"/></li><li property="itemListElement" typeof="ListItem"><a href="/en-US/docs/Learn/JavaScript" class="breadcrumb" property="item" typeof="WebPage"><span property="name">JavaScript — Dynamic client-side scripting</span></a><meta property="position" content="2"/></li><li property="itemListElement" typeof="ListItem"><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs" class="breadcrumb" property="item" typeof="WebPage"><span property="name">Client-side web APIs</span></a><meta property="position" content="3"/></li><li property="itemListElement" typeof="ListItem"><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" class="breadcrumb-current-page" property="item" typeof="WebPage"><span property="name">Client-side storage</span></a><meta property="position" content="4"/></li></ol></nav><div class="article-actions"><button type="button" class="button action has-icon article-actions-toggle" aria-label="Article actions"><span class="button-wrap"><span class="icon icon-ellipses "></span><span class="article-actions-dialog-heading">Article Actions</span></span></button><ul class="article-actions-entries"><li class="article-actions-entry"><div class="languages-switcher-menu open-on-focus-within"><button id="languages-switcher-button" type="button" class="button action small has-icon languages-switcher-menu" aria-haspopup="menu"><span class="button-wrap"><span class="icon icon-language "></span>English (US)</span></button><div class="hidden"><ul class="submenu language-menu " aria-labelledby="language-menu-button"><li class=" "><form class="submenu-item locale-redirect-setting"><div class="group"><label class="switch"><input type="checkbox" name="locale-redirect"/><span class="slider"></span><span class="label">Remember language</span></label><a href="https://github.com/orgs/mdn/discussions/739" rel="external noopener noreferrer" target="_blank" title="Enable this setting to automatically switch to this language when it's available. (Click to learn more.)"><span class="icon icon-question-mark "></span></a></div></form></li><li class=" "><a data-locale="de" href="/de/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" class="button submenu-item"><span>Deutsch</span><span title="Diese Übersetzung ist Teil eines Experiments."><span class="icon icon-experimental "></span></span></a></li><li class=" "><a data-locale="es" href="/es/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" class="button submenu-item"><span>Español</span></a></li><li class=" "><a data-locale="fr" href="/fr/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" class="button submenu-item"><span>Français</span></a></li><li class=" "><a data-locale="ja" href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" class="button submenu-item"><span>日本語</span></a></li><li class=" "><a data-locale="pt-BR" href="/pt-BR/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" class="button submenu-item"><span>Português (do Brasil)</span></a></li><li class=" "><a data-locale="ru" href="/ru/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" class="button submenu-item"><span>Русский</span></a></li><li class=" "><a data-locale="zh-CN" href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" class="button submenu-item"><span>中文 (简体)</span></a></li></ul></div></div></li></ul></div></div></div></div><div class="main-wrapper"><div class="sidebar-container"><aside id="sidebar-quicklinks" class="sidebar" data-macro="LearnSidebar"><button type="button" class="button action backdrop" aria-label="Collapse sidebar"><span class="button-wrap"></span></button><nav aria-label="Related Topics" class="sidebar-inner"><header class="sidebar-actions"><section class="sidebar-filter-container"><div class="sidebar-filter "><label id="sidebar-filter-label" class="sidebar-filter-label" for="sidebar-filter-input"><span class="icon icon-filter"></span><span class="visually-hidden">Filter sidebar</span></label><input id="sidebar-filter-input" autoComplete="off" class="sidebar-filter-input-field false" type="text" placeholder="Filter" value=""/><button type="button" class="button action has-icon clear-sidebar-filter-button"><span class="button-wrap"><span class="icon icon-cancel "></span><span class="visually-hidden">Clear filter input</span></span></button></div></section></header><div class="sidebar-inner-nav"><div class="in-nav-toc"><div class="document-toc-container"><section class="document-toc"><header><h2 class="document-toc-heading">In this article</h2></header><ul class="document-toc-list"><li class="document-toc-item "><a class="document-toc-link" href="#client-side_storage">Client-side storage?</a></li><li class="document-toc-item "><a class="document-toc-link" href="#storing_simple_data_—_web_storage">Storing simple data — web storage</a></li><li class="document-toc-item "><a class="document-toc-link" href="#storing_complex_data_—_indexeddb">Storing complex data — IndexedDB</a></li><li class="document-toc-item "><a class="document-toc-link" href="#offline_asset_storage">Offline asset storage</a></li><li class="document-toc-item "><a class="document-toc-link" href="#summary">Summary</a></li><li class="document-toc-item "><a class="document-toc-link" href="#see_also">See also</a></li></ul></section></div></div><div class="sidebar-body"><ol><li class="section"><a href="/en-US/docs/Learn/Getting_started_with_the_web">Complete beginners start here!</a></li><li><details><summary>Getting started with the web</summary><ol><li><a href="/en-US/docs/Learn/Getting_started_with_the_web">Getting started with the web</a></li><li><a href="/en-US/docs/Learn/Getting_started_with_the_web/Installing_basic_software">Installing basic software</a></li><li><a href="/en-US/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like">What will your website look like?</a></li><li><a href="/en-US/docs/Learn/Getting_started_with_the_web/Dealing_with_files">Dealing with files</a></li><li><a href="/en-US/docs/Learn/Getting_started_with_the_web/HTML_basics">HTML basics</a></li><li><a href="/en-US/docs/Learn/Getting_started_with_the_web/CSS_basics">CSS basics</a></li><li><a href="/en-US/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript basics</a></li><li><a href="/en-US/docs/Learn/Getting_started_with_the_web/Publishing_your_website">Publishing your website</a></li><li><a href="/en-US/docs/Learn/Getting_started_with_the_web/How_the_Web_works">How the web works</a></li></ol></details></li><li class="section"><a href="/en-US/docs/Learn/HTML">HTML — Structuring the web</a></li><li><details><summary>Introduction to HTML</summary><ol><li><a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a></li><li><a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/Getting_started">Getting started with HTML</a></li><li><a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">What's in the head? Metadata in HTML</a></li><li><a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML text fundamentals</a></li><li><a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">Creating hyperlinks</a></li><li><a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting">Advanced text formatting</a></li><li><a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/Document_and_website_structure">Document and website structure</a></li><li><a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">Debugging HTML</a></li><li><a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/Marking_up_a_letter">Marking up a letter</a></li><li><a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content">Structuring a page of content</a></li></ol></details></li><li><details><summary>Multimedia and embedding</summary><ol><li><a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding">Multimedia and embedding</a></li><li><a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML">Images in HTML</a></li><li><a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content">Video and audio content</a></li><li><a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies">From object to iframe — other embedding technologies</a></li><li><a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web">Adding vector graphics to the web</a></li><li><a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">Responsive images</a></li><li><a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Mozilla_splash_page">Mozilla splash page</a></li></ol></details></li><li><details><summary>HTML tables</summary><ol><li><a href="/en-US/docs/Learn/HTML/Tables">HTML tables</a></li><li><a href="/en-US/docs/Learn/HTML/Tables/Basics">HTML table basics</a></li><li><a href="/en-US/docs/Learn/HTML/Tables/Advanced">HTML table advanced features and accessibility</a></li><li><a href="/en-US/docs/Learn/HTML/Tables/Structuring_planet_data">Structuring planet data</a></li></ol></details></li><li class="section"><a href="/en-US/docs/Learn/CSS">CSS — Styling the web</a></li><li><details><summary>CSS first steps</summary><ol><li><a href="/en-US/docs/Learn/CSS/First_steps">CSS first steps</a></li><li><a href="/en-US/docs/Learn/CSS/First_steps/What_is_CSS">What is CSS?</a></li><li><a href="/en-US/docs/Learn/CSS/First_steps/Getting_started">Getting started with CSS</a></li><li><a href="/en-US/docs/Learn/CSS/First_steps/How_CSS_is_structured">How CSS is structured</a></li><li><a href="/en-US/docs/Learn/CSS/First_steps/How_CSS_works">How CSS works</a></li><li><a href="/en-US/docs/Learn/CSS/First_steps/Styling_a_biography_page">Styling a biography page</a></li></ol></details></li><li><details><summary>CSS building blocks</summary><ol><li><a href="/en-US/docs/Learn/CSS/Building_blocks">CSS building blocks</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Selectors">CSS selectors</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">Type, class, and ID selectors</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">Attribute selectors</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">Pseudo-classes and pseudo-elements</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Selectors/Combinators">Combinators</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">Cascade, specificity, and inheritance</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Cascade_layers">Cascade layers</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/The_box_model">The box model</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">Backgrounds and borders</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Handling_different_text_directions">Handling different text directions</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Overflowing_content">Overflowing content</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Values_and_units">CSS values and units</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">Sizing items in CSS</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Images_media_form_elements">Images, media, and form elements</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Styling_tables">Styling tables</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Advanced_styling_effects">Advanced styling effects</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Debugging_CSS">Debugging CSS</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Organizing">Organizing your CSS</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Fundamental_CSS_comprehension">Fundamental CSS comprehension</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/Creating_fancy_letterheaded_paper">Creating fancy letterheaded paper</a></li><li><a href="/en-US/docs/Learn/CSS/Building_blocks/A_cool_looking_box">A cool-looking box</a></li></ol></details></li><li><details><summary>Styling text</summary><ol><li><a href="/en-US/docs/Learn/CSS/Styling_text">CSS styling text</a></li><li><a href="/en-US/docs/Learn/CSS/Styling_text/Fundamentals">Fundamental text and font styling</a></li><li><a href="/en-US/docs/Learn/CSS/Styling_text/Styling_lists">Styling lists</a></li><li><a href="/en-US/docs/Learn/CSS/Styling_text/Styling_links">Styling links</a></li><li><a href="/en-US/docs/Learn/CSS/Styling_text/Web_fonts">Web fonts</a></li><li><a href="/en-US/docs/Learn/CSS/Styling_text/Typesetting_a_homepage">Typesetting a community school homepage</a></li></ol></details></li><li><details><summary>CSS layout</summary><ol><li><a href="/en-US/docs/Learn/CSS/CSS_layout">CSS layout</a></li><li><a href="/en-US/docs/Learn/CSS/CSS_layout/Introduction">Introduction to CSS layout</a></li><li><a href="/en-US/docs/Learn/CSS/CSS_layout/Normal_Flow">Normal Flow</a></li><li><a href="/en-US/docs/Learn/CSS/CSS_layout/Flexbox">Flexbox</a></li><li><a href="/en-US/docs/Learn/CSS/CSS_layout/Grids">Grids</a></li><li><a href="/en-US/docs/Learn/CSS/CSS_layout/Floats">Floats</a></li><li><a href="/en-US/docs/Learn/CSS/CSS_layout/Positioning">Positioning</a></li><li><a href="/en-US/docs/Learn/CSS/CSS_layout/Multiple-column_Layout">Multiple-column layout</a></li><li><a href="/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design">Responsive design</a></li><li><a href="/en-US/docs/Learn/CSS/CSS_layout/Media_queries">Beginner's guide to media queries</a></li><li><a href="/en-US/docs/Learn/CSS/CSS_layout/Legacy_Layout_Methods">Legacy layout methods</a></li><li><a href="/en-US/docs/Learn/CSS/CSS_layout/Supporting_Older_Browsers">Supporting older browsers</a></li><li><a href="/en-US/docs/Learn/CSS/CSS_layout/Fundamental_Layout_Comprehension">Fundamental layout comprehension</a></li></ol></details></li><li class="section"><a href="/en-US/docs/Learn/JavaScript">JavaScript — Dynamic client-side scripting</a></li><li><details><summary>JavaScript first steps</summary><ol><li><a href="/en-US/docs/Learn/JavaScript/First_steps">JavaScript first steps</a></li><li><a href="/en-US/docs/Learn/JavaScript/First_steps/What_is_JavaScript">What is JavaScript?</a></li><li><a href="/en-US/docs/Learn/JavaScript/First_steps/A_first_splash">A first splash into JavaScript</a></li><li><a href="/en-US/docs/Learn/JavaScript/First_steps/What_went_wrong">What went wrong? Troubleshooting JavaScript</a></li><li><a href="/en-US/docs/Learn/JavaScript/First_steps/Variables">Storing the information you need — Variables</a></li><li><a href="/en-US/docs/Learn/JavaScript/First_steps/Math">Basic math in JavaScript — numbers and operators</a></li><li><a href="/en-US/docs/Learn/JavaScript/First_steps/Strings">Handling text — strings in JavaScript</a></li><li><a href="/en-US/docs/Learn/JavaScript/First_steps/Useful_string_methods">Useful string methods</a></li><li><a href="/en-US/docs/Learn/JavaScript/First_steps/Arrays">Arrays</a></li><li><a href="/en-US/docs/Learn/JavaScript/First_steps/Silly_story_generator">Silly story generator</a></li></ol></details></li><li><details><summary>JavaScript building blocks</summary><ol><li><a href="/en-US/docs/Learn/JavaScript/Building_blocks">JavaScript building blocks</a></li><li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/conditionals">Making decisions in your code — conditionals</a></li><li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Looping_code">Looping code</a></li><li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions">Functions — reusable blocks of code</a></li><li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Build_your_own_function">Build your own function</a></li><li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Return_values">Function return values</a></li><li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Events">Introduction to events</a></li><li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Event_bubbling">Event bubbling</a></li><li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Image_gallery">Image gallery</a></li></ol></details></li><li><details><summary>Introducing JavaScript objects</summary><ol><li><a href="/en-US/docs/Learn/JavaScript/Objects">Introducing JavaScript objects</a></li><li><a href="/en-US/docs/Learn/JavaScript/Objects/Basics">JavaScript object basics</a></li><li><a href="/en-US/docs/Learn/JavaScript/Objects/Object_prototypes">Object prototypes</a></li><li><a href="/en-US/docs/Learn/JavaScript/Objects/Object-oriented_programming">Object-oriented programming</a></li><li><a href="/en-US/docs/Learn/JavaScript/Objects/Classes_in_JavaScript">Classes in JavaScript</a></li><li><a href="/en-US/docs/Learn/JavaScript/Objects/JSON">Working with JSON</a></li><li><a href="/en-US/docs/Learn/JavaScript/Objects/Object_building_practice">Object building practice</a></li><li><a href="/en-US/docs/Learn/JavaScript/Objects/Adding_bouncing_balls_features">Adding features to our bouncing balls demo</a></li></ol></details></li><li><details><summary>Asynchronous JavaScript</summary><ol><li><a href="/en-US/docs/Learn/JavaScript/Asynchronous">Asynchronous JavaScript</a></li><li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing">Introducing asynchronous JavaScript</a></li><li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises">How to use promises</a></li><li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Implementing_a_promise-based_API">How to implement a promise-based API</a></li><li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing_workers">Introducing workers</a></li><li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Sequencing_animations">Sequencing animations</a></li></ol></details></li><li><details open=""><summary>Client-side web APIs</summary><ol><li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs">Client-side web APIs</a></li><li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">Introduction to web APIs</a></li><li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">Manipulating documents</a></li><li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">Fetching data from the server</a></li><li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">Third-party APIs</a></li><li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Drawing graphics</a></li><li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">Video and Audio APIs</a></li><li><em><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" aria-current="page">Client-side storage</a></em></li></ol></details></li><li class="section"><a href="/en-US/docs/Learn/Forms">Web forms — Working with user data</a></li><li><details><summary>Web form building blocks</summary><ol><li><a href="/en-US/docs/Learn/Forms">Web form building blocks</a></li><li><a href="/en-US/docs/Learn/Forms/Your_first_form">Your first form</a></li><li><a href="/en-US/docs/Learn/Forms/How_to_structure_a_web_form">How to structure a web form</a></li><li><a href="/en-US/docs/Learn/Forms/Basic_native_form_controls">Basic native form controls</a></li><li><a href="/en-US/docs/Learn/Forms/HTML5_input_types">The HTML5 input types</a></li><li><a href="/en-US/docs/Learn/Forms/Other_form_controls">Other form controls</a></li><li><a href="/en-US/docs/Learn/Forms/Styling_web_forms">Styling web forms</a></li><li><a href="/en-US/docs/Learn/Forms/Advanced_form_styling">Advanced form styling</a></li><li><a href="/en-US/docs/Learn/Forms/UI_pseudo-classes">UI pseudo-classes</a></li><li><a href="/en-US/docs/Learn/Forms/Form_validation">Client-side form validation</a></li><li><a href="/en-US/docs/Learn/Forms/Sending_and_retrieving_form_data">Sending form data</a></li></ol></details></li><li><details><summary>Advanced web form techniques</summary><ol><li><a href="/en-US/docs/Learn/Forms/How_to_build_custom_form_controls">How to build custom form controls</a></li><li><a href="/en-US/docs/Learn/Forms/Sending_forms_through_JavaScript">Sending forms through JavaScript</a></li><li><a href="/en-US/docs/Learn/Forms/Property_compatibility_table_for_form_controls">CSS property compatibility table for form controls</a></li><li><a href="/en-US/docs/Learn/Forms/HTML_forms_in_legacy_browsers">HTML forms in legacy browsers</a></li></ol></details></li><li class="section"><a href="/en-US/docs/Learn/Accessibility">Accessibility — Make the web usable by everyone</a></li><li><details><summary>Accessibility guides</summary><ol><li><a href="/en-US/docs/Learn/Accessibility">Accessibility</a></li><li><a href="/en-US/docs/Learn/Accessibility/What_is_accessibility">What is accessibility?</a></li><li><a href="/en-US/docs/Learn/Accessibility/HTML">HTML: A good basis for accessibility</a></li><li><a href="/en-US/docs/Learn/Accessibility/CSS_and_JavaScript">CSS and JavaScript accessibility best practices</a></li><li><a href="/en-US/docs/Learn/Accessibility/WAI-ARIA_basics">WAI-ARIA basics</a></li><li><a href="/en-US/docs/Learn/Accessibility/Multimedia">Accessible multimedia</a></li><li><a href="/en-US/docs/Learn/Accessibility/Mobile">Mobile accessibility</a></li><li><a href="/en-US/docs/Learn/Accessibility/Accessibility_troubleshooting">Assessment: Accessibility troubleshooting</a></li></ol></details></li><li class="section"><a href="/en-US/docs/Learn/Performance">Performance — Making websites fast and responsive</a></li><li><details><summary>Performance guides</summary><ol><li><a href="/en-US/docs/Learn/Performance">Web performance</a></li><li><a href="/en-US/docs/Learn/Performance/why_web_performance">The "why" of web performance</a></li><li><a href="/en-US/docs/Learn/Performance/What_is_web_performance">What is web performance?</a></li><li><a href="/en-US/docs/Learn/Performance/Perceived_performance">Perceived performance</a></li><li><a href="/en-US/docs/Learn/Performance/Measuring_performance">Measuring performance</a></li><li><a href="/en-US/docs/Learn/Performance/Multimedia">Multimedia: Images</a></li><li><a href="/en-US/docs/Learn/Performance/video">Multimedia: video</a></li><li><a href="/en-US/docs/Learn/Performance/JavaScript">JavaScript performance optimization</a></li><li><a href="/en-US/docs/Learn/Performance/HTML">HTML performance optimization</a></li><li><a href="/en-US/docs/Learn/Performance/CSS">CSS performance optimization</a></li><li><a href="/en-US/docs/Learn/Performance/business_case_for_performance">The business case for web performance</a></li></ol></details></li><li class="section"><a href="/en-US/docs/Learn/MathML">MathML — Writing mathematics with MathML</a></li><li><details><summary>MathML first steps</summary><ol><li><a href="/en-US/docs/Learn/MathML/First_steps">MathML first steps</a></li><li><a href="/en-US/docs/Learn/MathML/First_steps/Getting_started">Getting started with MathML</a></li><li><a href="/en-US/docs/Learn/MathML/First_steps/Text_containers">MathML Text Containers</a></li><li><a href="/en-US/docs/Learn/MathML/First_steps/Fractions_and_roots">MathML fractions and roots</a></li><li><a href="/en-US/docs/Learn/MathML/First_steps/Scripts">MathML scripted elements</a></li><li><a href="/en-US/docs/Learn/MathML/First_steps/Tables">MathML tables</a></li><li><a href="/en-US/docs/Learn/MathML/First_steps/Three_famous_mathematical_formulas">Three famous mathematical formulas</a></li></ol></details></li><li class="section"><a href="/en-US/docs/Learn/../Games">Games — Developing games for the web</a></li><li><details><summary>Guides and tutorials</summary><ol><li><a href="/en-US/docs/Games/Introduction">Introduction to game development for the Web</a></li><li><a href="/en-US/docs/Games/Techniques">Techniques for game development</a></li><li><a href="/en-US/docs/Games/Tutorials">Tutorials</a></li><li><a href="/en-US/docs/Games/Publishing_games">Publishing games</a></li></ol></details></li><li class="section"><a href="/en-US/docs/Learn/Tools_and_testing">Tools and testing</a></li><li><details><summary>Client-side web development tools</summary><ol><li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools">Understanding client-side web development tools</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview">Client-side tooling overview</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">Command line crash course</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management">Package management basics</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Introducing_complete_toolchain">Introducing a complete toolchain</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Deployment">Deploying our app</a></li></ol></details></li><li><details><summary>Introduction to client-side frameworks</summary><ol><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li></ol></details></li><li><details><summary>React</summary><ol><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li></ol></details></li><li><details><summary>Ember</summary><ol><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li></ol></details></li><li><details><summary>Vue</summary><ol><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">Getting started with Vue</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">Creating our first Vue component</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">Rendering a list of Vue components</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">Adding a new todo form: Vue events, methods, and models</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">Styling Vue components with CSS</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">Using Vue computed properties</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue conditional rendering: editing existing todos</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">Vue refs and lifecycle methods for focus management</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue resources</a></li></ol></details></li><li><details><summary>Svelte</summary><ol><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started">Getting started with Svelte</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning">Starting our Svelte to-do list app</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props">Dynamic behavior in Svelte: working with variables and props</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components">Componentizing our Svelte app</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility">Advanced Svelte: Reactivity, lifecycle, accessibility</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores">Working with Svelte stores</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript">TypeScript support in Svelte</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next">Deployment and next steps</a></li></ol></details></li><li><details><summary>Angular</summary><ol><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_getting_started">Getting started with Angular</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_todo_list_beginning">Beginning our Angular todo list app</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_styling">Styling our Angular app</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_item_component">Creating an item component</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_filtering">Filtering our to-do items</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_building">Building Angular applications and further resources</a></li></ol></details></li><li><details><summary>Git and GitHub</summary><ol><li><a href="/en-US/docs/Learn/Tools_and_testing/GitHub">Git and GitHub</a></li></ol></details></li><li><details><summary>Cross browser testing</summary><ol><li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing">Cross browser testing</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">Introduction to cross-browser testing</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies">Strategies for carrying out testing</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS">Handling common HTML and CSS problems</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">Handling common JavaScript problems</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">Handling common accessibility problems</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">Implementing feature detection</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">Introduction to automated testing</a></li><li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">Setting up your own test automation environment</a></li></ol></details></li><li class="section"><a href="/en-US/docs/Learn/Server-side">Server-side website programming</a></li><li><details><summary>First steps</summary><ol><li><a href="/en-US/docs/Learn/Server-side/First_steps">Server-side website programming first steps</a></li><li><a href="/en-US/docs/Learn/Server-side/First_steps/Introduction">Introduction to the server side</a></li><li><a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">Client-Server Overview</a></li><li><a href="/en-US/docs/Learn/Server-side/First_steps/Web_frameworks">Server-side web frameworks</a></li><li><a href="/en-US/docs/Learn/Server-side/First_steps/Website_security">Website security</a></li></ol></details></li><li><details><summary>Django web framework (Python)</summary><ol><li><a href="/en-US/docs/Learn/Server-side/Django">Django Web Framework (Python)</a></li><li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li><li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li><li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li><li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li><li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li><li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li><li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li><li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li><li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li><li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li><li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li><li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li><li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li><li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li><li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">Assessment: DIY Django mini blog</a></li></ol></details></li><li><details><summary>Express Web Framework (Node.js/JavaScript)</summary><ol><li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs">Express web framework (Node.js/JavaScript)</a></li><li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li><li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node development environment</a></li><li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li><li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li><li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li><li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li><li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li><li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li><li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li></ol></details></li><li class="section"><a href="/en-US/docs/Learn/Common_questions">Further resources</a></li><li><details><summary>Common questions</summary><ol><li><a href="/en-US/docs/Learn/Common_questions">Common questions</a></li><li><a href="/en-US/docs/Learn/HTML/Howto">Use HTML to solve common problems</a></li><li><a href="/en-US/docs/Learn/CSS/Howto">Use CSS to solve common problems</a></li><li><a href="/en-US/docs/Learn/JavaScript/Howto">Solve common problems in your JavaScript code</a></li><li><a href="/en-US/docs/Learn/Common_questions/Web_mechanics">Web mechanics</a></li><li><a href="/en-US/docs/Learn/Common_questions/Tools_and_setup">Tools and setup</a></li><li><a href="/en-US/docs/Learn/Common_questions/Design_and_accessibility">Design and accessibility</a></li></ol></details></li></ol></div></div><section class="place side"></section></nav></aside><div class="toc-container"><aside class="toc"><nav><div class="document-toc-container"><section class="document-toc"><header><h2 class="document-toc-heading">In this article</h2></header><ul class="document-toc-list"><li class="document-toc-item "><a class="document-toc-link" href="#client-side_storage">Client-side storage?</a></li><li class="document-toc-item "><a class="document-toc-link" href="#storing_simple_data_—_web_storage">Storing simple data — web storage</a></li><li class="document-toc-item "><a class="document-toc-link" href="#storing_complex_data_—_indexeddb">Storing complex data — IndexedDB</a></li><li class="document-toc-item "><a class="document-toc-link" href="#offline_asset_storage">Offline asset storage</a></li><li class="document-toc-item "><a class="document-toc-link" href="#summary">Summary</a></li><li class="document-toc-item "><a class="document-toc-link" href="#see_also">See also</a></li></ul></section></div></nav></aside><section class="place side"></section></div></div><main id="content" class="main-content "><article class="main-page-content" lang="en-US"><header><h1>Client-side storage</h1></header><div class="section-content"><ul class="prev-next"> <li><a class="button secondary" href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs"><span class="button-wrap"> Previous </span></a></li> <li><a class="button secondary" href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs"><span class="button-wrap"> Overview: Client-side web APIs</span></a></li> </ul> <p>Modern web browsers support a number of ways for websites to store data on the user's computer — with the user's permission — then retrieve it when necessary. This lets you persist data for long-term storage, save sites or documents for offline use, retain user-specific settings for your site, and more. This article explains the very basics of how these work.</p> <figure class="table-container"><table> <tbody> <tr> <th scope="row">Prerequisites:</th> <td> JavaScript basics (see <a href="/en-US/docs/Learn/JavaScript/First_steps">first steps</a>, <a href="/en-US/docs/Learn/JavaScript/Building_blocks">building blocks</a>, <a href="/en-US/docs/Learn/JavaScript/Objects">JavaScript objects</a>), the <a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">basics of Client-side APIs</a> </td> </tr> <tr> <th scope="row">Objective:</th> <td>To learn how to use client-side storage APIs to store application data.</td> </tr> </tbody> </table></figure></div><section aria-labelledby="client-side_storage"><h2 id="client-side_storage"><a href="#client-side_storage">Client-side storage?</a></h2><div class="section-content"><p>Elsewhere in the MDN learning area, we talked about the difference between <a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview#static_sites">static sites</a> and <a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview#dynamic_sites">dynamic sites</a>. Most major modern websites are dynamic — they store data on the server using some kind of database (server-side storage), then run <a href="/en-US/docs/Learn/Server-side">server-side</a> code to retrieve needed data, insert it into static page templates, and serve the resulting HTML to the client to be displayed by the user's browser.</p> <p>Client-side storage works on similar principles, but has different uses. It consists of JavaScript APIs that allow you to store data on the client (i.e. on the user's machine) and then retrieve it when needed. This has many distinct uses, such as:</p> <ul> <li>Personalizing site preferences (e.g. showing a user's choice of custom widgets, color scheme, or font size).</li> <li>Persisting previous site activity (e.g. storing the contents of a shopping cart from a previous session, remembering if a user was previously logged in).</li> <li>Saving data and assets locally so a site will be quicker (and potentially less expensive) to download, or be usable without a network connection.</li> <li>Saving web application generated documents locally for use offline</li> </ul> <p>Often client-side and server-side storage are used together. For example, you could download a batch of music files (perhaps used by a web game or music player application), store them inside a client-side database, and play them as needed. The user would only have to download the music files once — on subsequent visits they would be retrieved from the database instead.</p> <div class="notecard note"> <p><strong>Note:</strong> There are limits to the amount of data you can store using client-side storage APIs (possibly both per individual API and cumulatively); the exact limit varies depending on the browser and possibly based on user settings. See <a href="/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria">Browser storage quotas and eviction criteria</a> for more information.</p> </div></div></section><section aria-labelledby="old_school_cookies"><h3 id="old_school_cookies"><a href="#old_school_cookies">Old school: Cookies</a></h3><div class="section-content"><p>The concept of client-side storage has been around for a long time. Since the early days of the web, sites have used <a href="/en-US/docs/Web/HTTP/Cookies">cookies</a> to store information to personalize user experience on websites. They're the earliest form of client-side storage commonly used on the web.</p> <p>These days, there are easier mechanisms available for storing client-side data, therefore we won't be teaching you how to use cookies in this article. However, this does not mean cookies are completely useless on the modern-day web — they are still used commonly to store data related to user personalization and state, e.g. session IDs and access tokens. For more information on cookies see our <a href="/en-US/docs/Web/HTTP/Cookies">Using HTTP cookies</a> article.</p></div></section><section aria-labelledby="new_school_web_storage_and_indexeddb"><h3 id="new_school_web_storage_and_indexeddb"><a href="#new_school_web_storage_and_indexeddb">New school: Web Storage and IndexedDB</a></h3><div class="section-content"><p>The "easier" features we mentioned above are as follows:</p> <ul> <li>The <a href="/en-US/docs/Web/API/Web_Storage_API">Web Storage API</a> provides a mechanism for storing and retrieving smaller, data items consisting of a name and a corresponding value. This is useful when you just need to store some simple data, like the user's name, whether they are logged in, what color to use for the background of the screen, etc.</li> <li>The <a href="/en-US/docs/Web/API/IndexedDB_API">IndexedDB API</a> provides the browser with a complete database system for storing complex data. This can be used for things from complete sets of customer records to even complex data types like audio or video files.</li> </ul> <p>You'll learn more about these APIs below.</p></div></section><section aria-labelledby="the_cache_api"><h3 id="the_cache_api"><a href="#the_cache_api">The Cache API</a></h3><div class="section-content"><p>The <a href="/en-US/docs/Web/API/Cache"><code>Cache</code></a> API is designed for storing HTTP responses to specific requests, and is very useful for doing things like storing website assets offline so the site can subsequently be used without a network connection. Cache is usually used in combination with the <a href="/en-US/docs/Web/API/Service_Worker_API">Service Worker API</a>, although it doesn't have to be.</p> <p>The use of Cache and Service Workers is an advanced topic, and we won't be covering it in great detail in this article, although we will show an example in the <a href="#offline_asset_storage">Offline asset storage</a> section below.</p></div></section><section aria-labelledby="storing_simple_data_—_web_storage"><h2 id="storing_simple_data_—_web_storage"><a href="#storing_simple_data_—_web_storage">Storing simple data — web storage</a></h2><div class="section-content"><p>The <a href="/en-US/docs/Web/API/Web_Storage_API">Web Storage API</a> is very easy to use — you store simple name/value pairs of data (limited to strings, numbers, etc.) and retrieve these values when needed.</p></div></section><section aria-labelledby="basic_syntax"><h3 id="basic_syntax"><a href="#basic_syntax">Basic syntax</a></h3><div class="section-content"><p>Let's show you how:</p> <ol> <li> <p>First, go to our <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/index.html" class="external" target="_blank">web storage blank template</a> on GitHub (open this in a new tab).</p> </li> <li> <p>Open the JavaScript console of your browser's developer tools.</p> </li> <li> <p>All of your web storage data is contained within two object-like structures inside the browser: <a href="/en-US/docs/Web/API/Window/sessionStorage" title="sessionStorage"><code>sessionStorage</code></a> and <a href="/en-US/docs/Web/API/Window/localStorage" title="localStorage"><code>localStorage</code></a>. The first one persists data for as long as the browser is open (the data is lost when the browser is closed) and the second one persists data even after the browser is closed and then opened again. We'll use the second one in this article as it is generally more useful.</p> <p>The <a href="/en-US/docs/Web/API/Storage/setItem"><code>Storage.setItem()</code></a> method allows you to save a data item in storage — it takes two parameters: the name of the item, and its value. Try typing this into your JavaScript console (change the value to your own name, if you wish!):</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>localStorage.setItem("name", "Chris"); </code></pre></div> </li> <li> <p>The <a href="/en-US/docs/Web/API/Storage/getItem"><code>Storage.getItem()</code></a> method takes one parameter — the name of a data item you want to retrieve — and returns the item's value. Now type these lines into your JavaScript console:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>let myName = localStorage.getItem("name"); myName; </code></pre></div> <p>Upon typing in the second line, you should see that the <code>myName</code> variable now contains the value of the <code>name</code> data item.</p> </li> <li> <p>The <a href="/en-US/docs/Web/API/Storage/removeItem"><code>Storage.removeItem()</code></a> method takes one parameter — the name of a data item you want to remove — and removes that item out of web storage. Type the following lines into your JavaScript console:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>localStorage.removeItem("name"); myName = localStorage.getItem("name"); myName; </code></pre></div> <p>The third line should now return <code>null</code> — the <code>name</code> item no longer exists in the web storage.</p> </li> </ol></div></section><section aria-labelledby="the_data_persists!"><h3 id="the_data_persists!"><a href="#the_data_persists!">The data persists!</a></h3><div class="section-content"><p>One key feature of web storage is that the data persists between page loads (and even when the browser is shut down, in the case of <code>localStorage</code>). Let's look at this in action.</p> <ol> <li> <p>Open our web storage blank template again, but this time in a different browser to the one you've got this tutorial open in! This will make it easier to deal with.</p> </li> <li> <p>Type these lines into the browser's JavaScript console:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>localStorage.setItem("name", "Chris"); let myName = localStorage.getItem("name"); myName; </code></pre></div> <p>You should see the name item returned.</p> </li> <li> <p>Now close down the browser and open it up again.</p> </li> <li> <p>Enter the following lines again:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>let myName = localStorage.getItem("name"); myName; </code></pre></div> <p>You should see that the value is still available, even though the browser has been closed and then opened again.</p> </li> </ol></div></section><section aria-labelledby="separate_storage_for_each_domain"><h3 id="separate_storage_for_each_domain"><a href="#separate_storage_for_each_domain">Separate storage for each domain</a></h3><div class="section-content"><p>There is a separate data store for each domain (each separate web address loaded in the browser). You will see that if you load two websites (say google.com and amazon.com) and try storing an item on one website, it won't be available to the other website.</p> <p>This makes sense — you can imagine the security issues that would arise if websites could see each other's data!</p></div></section><section aria-labelledby="a_more_involved_example"><h3 id="a_more_involved_example"><a href="#a_more_involved_example">A more involved example</a></h3><div class="section-content"><p>Let's apply this new-found knowledge by writing a working example to give you an idea of how web storage can be used. Our example will allow you to enter a name, after which the page will update to give you a personalized greeting. This state will also persist across page/browser reloads, because the name is stored in web storage.</p> <p>You can find the example HTML at <a href="https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/web-storage/personal-greeting.html" class="external" target="_blank">personal-greeting.html</a> — this contains a website with a header, content, and footer, and a form for entering your name.</p> <p> <img src="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/web-storage-demo.png" alt="A Screenshot of a website that has a header, content and footer sections. The header has a welcome text to the left-hand side and a button labelled 'forget' to the right-hand side. The content has an heading followed by a two paragraphs of dummy text. The footer reads 'Copyright nobody. Use the code as you like'." width="1024" height="550" loading="lazy"> </p> <p>Let's build up the example, so you can understand how it works.</p> <ol> <li> <p>First, make a local copy of our <a href="https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/web-storage/personal-greeting.html" class="external" target="_blank">personal-greeting.html</a> file in a new directory on your computer.</p> </li> <li> <p>Next, note how our HTML references a JavaScript file called <code>index.js</code>, with a line like <code><script src="index.js" defer></script></code>. We need to create this and write our JavaScript code into it. Create an <code>index.js</code> file in the same directory as your HTML file.</p> </li> <li> <p>We'll start off by creating references to all the HTML features we need to manipulate in this example — we'll create them all as constants, as these references do not need to change in the lifecycle of the app. Add the following lines to your JavaScript file:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// create needed constants const rememberDiv = document.querySelector(".remember"); const forgetDiv = document.querySelector(".forget"); const form = document.querySelector("form"); const nameInput = document.querySelector("#entername"); const submitBtn = document.querySelector("#submitname"); const forgetBtn = document.querySelector("#forgetname"); const h1 = document.querySelector("h1"); const personalGreeting = document.querySelector(".personal-greeting"); </code></pre></div> </li> <li> <p>Next up, we need to include a small event listener to stop the form from actually submitting itself when the submit button is pressed, as this is not the behavior we want. Add this snippet below your previous code:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// Stop the form from submitting when a button is pressed form.addEventListener("submit", (e) => e.preventDefault()); </code></pre></div> </li> <li> <p>Now we need to add an event listener, the handler function of which will run when the "Say hello" button is clicked. The comments explain in detail what each bit does, but in essence here we are taking the name the user has entered into the text input box and saving it in web storage using <code>setItem()</code>, then running a function called <code>nameDisplayCheck()</code> that will handle updating the actual website text. Add this to the bottom of your code:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// run function when the 'Say hello' button is clicked submitBtn.addEventListener("click", () => { // store the entered name in web storage localStorage.setItem("name", nameInput.value); // run nameDisplayCheck() to sort out displaying the personalized greetings and updating the form display nameDisplayCheck(); }); </code></pre></div> </li> <li> <p>At this point we also need an event handler to run a function when the "Forget" button is clicked — this is only displayed after the "Say hello" button has been clicked (the two form states toggle back and forth). In this function we remove the <code>name</code> item from web storage using <code>removeItem()</code>, then again run <code>nameDisplayCheck()</code> to update the display. Add this to the bottom:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// run function when the 'Forget' button is clicked forgetBtn.addEventListener("click", () => { // Remove the stored name from web storage localStorage.removeItem("name"); // run nameDisplayCheck() to sort out displaying the generic greeting again and updating the form display nameDisplayCheck(); }); </code></pre></div> </li> <li> <p>It is now time to define the <code>nameDisplayCheck()</code> function itself. Here we check whether the name item has been stored in web storage by using <code>localStorage.getItem('name')</code> as a conditional test. If the name has been stored, this call will evaluate to <code>true</code>; if not, the call will evaluate to <code>false</code>. If the call evaluates to <code>true</code>, we display a personalized greeting, display the "forget" part of the form, and hide the "Say hello" part of the form. If the call evaluates to <code>false</code>, we display a generic greeting and do the opposite. Again, put the following code at the bottom:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// define the nameDisplayCheck() function function nameDisplayCheck() { // check whether the 'name' data item is stored in web Storage if (localStorage.getItem("name")) { // If it is, display personalized greeting const name = localStorage.getItem("name"); h1.textContent = `Welcome, ${name}`; personalGreeting.textContent = `Welcome to our website, ${name}! We hope you have fun while you are here.`; // hide the 'remember' part of the form and show the 'forget' part forgetDiv.style.display = "block"; rememberDiv.style.display = "none"; } else { // if not, display generic greeting h1.textContent = "Welcome to our website "; personalGreeting.textContent = "Welcome to our website. We hope you have fun while you are here."; // hide the 'forget' part of the form and show the 'remember' part forgetDiv.style.display = "none"; rememberDiv.style.display = "block"; } } </code></pre></div> </li> <li> <p>Last but not least, we need to run the <code>nameDisplayCheck()</code> function when the page is loaded. If we don't do this, then the personalized greeting will not persist across page reloads. Add the following to the bottom of your code:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>nameDisplayCheck(); </code></pre></div> </li> </ol> <p>Your example is finished — well done! All that remains now is to save your code and test your HTML page in a browser. You can see our <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/personal-greeting.html" class="external" target="_blank">finished version running live here</a>.</p> <div class="notecard note"> <p><strong>Note:</strong> There is another, slightly more complex example to explore at <a href="/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API">Using the Web Storage API</a>.</p> </div> <div class="notecard note"> <p><strong>Note:</strong> In the line <code><script src="index.js" defer></script></code> of the source for our finished version, the <code>defer</code> attribute specifies that the contents of the <a href="/en-US/docs/Web/HTML/Element/script"><code><script></code></a> element will not execute until the page has finished loading.</p> </div></div></section><section aria-labelledby="storing_complex_data_—_indexeddb"><h2 id="storing_complex_data_—_indexeddb"><a href="#storing_complex_data_—_indexeddb">Storing complex data — IndexedDB</a></h2><div class="section-content"><p>The <a href="/en-US/docs/Web/API/IndexedDB_API">IndexedDB API</a> (sometimes abbreviated IDB) is a complete database system available in the browser in which you can store complex related data, the types of which aren't limited to simple values like strings or numbers. You can store videos, images, and pretty much anything else in an IndexedDB instance.</p> <p> The IndexedDB API allows you to create a database, then create object stores within that database. Object stores are like tables in a relational database, and each object store can contain a number of objects. To learn more about the IndexedDB API, see <a href="/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB">Using IndexedDB</a>. </p> <p>However, this does come at a cost: IndexedDB is much more complex to use than the Web Storage API. In this section, we'll really only scratch the surface of what it is capable of, but we will give you enough to get started.</p></div></section><section aria-labelledby="working_through_a_note_storage_example"><h3 id="working_through_a_note_storage_example"><a href="#working_through_a_note_storage_example">Working through a note storage example</a></h3><div class="section-content"><p>Here we'll run you through an example that allows you to store notes in your browser and view and delete them whenever you like, getting you to build it up for yourself and explaining the most fundamental parts of IDB as we go along.</p> <p>The app looks something like this:</p> <p> <img src="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/idb-demo.png" alt="IndexDB notes demo screenshot with 4 sections. The first section is the header. The second section lists all the notes that have been created. It has two notes, each with a delete button. A third section is a form with 2 input fields for 'Note title' and 'Note text' and a button labeled 'Create new note'. The bottom section footer reads 'Copyright nobody. Use the code as you like'." width="803" height="736" loading="lazy"> </p> <p>Each note has a title and some body text, each individually editable. The JavaScript code we'll go through below has detailed comments to help you understand what's going on.</p></div></section><section aria-labelledby="getting_started"><h3 id="getting_started"><a href="#getting_started">Getting started</a></h3><div class="section-content"><ol> <li>First of all, make local copies of our <a href="https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/index.html" class="external" target="_blank"><code>index.html</code></a>, <a href="https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/style.css" class="external" target="_blank"><code>style.css</code></a>, and <a href="https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/index-start.js" class="external" target="_blank"><code>index-start.js</code></a> files into a new directory on your local machine.</li> <li>Have a look at the files. You'll see that the HTML defines a website with a header and footer, as well as a main content area that contains a place to display notes, and a form for entering new notes into the database. The CSS provides some styling to make it clearer what is going on. The JavaScript file contains five declared constants containing references to the <a href="/en-US/docs/Web/HTML/Element/ul"><code><ul></code></a> element the notes will be displayed in, the title and body <a href="/en-US/docs/Web/HTML/Element/input"><code><input></code></a> elements, the <a href="/en-US/docs/Web/HTML/Element/form"><code><form></code></a> itself, and the <a href="/en-US/docs/Web/HTML/Element/button"><code><button></code></a>.</li> <li>Rename your JavaScript file to <code>index.js</code>. You are now ready to start adding code to it.</li> </ol></div></section><section aria-labelledby="database_initial_setup"><h3 id="database_initial_setup"><a href="#database_initial_setup">Database initial setup</a></h3><div class="section-content"><p>Now let's look at what we have to do in the first place, to actually set up a database.</p> <ol> <li> <p>Below the constant declarations, add the following lines:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// Create an instance of a db object for us to store the open database in let db; </code></pre></div> <p>Here we are declaring a variable called <code>db</code> — this will later be used to store an object representing our database. We will use this in a few places, so we've declared it globally here to make things easier.</p> </li> <li> <p>Next, add the following:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// Open our database; it is created if it doesn't already exist // (see the upgradeneeded handler below) const openRequest = window.indexedDB.open("notes_db", 1); </code></pre></div> <p>This line creates a request to open version <code>1</code> of a database called <code>notes_db</code>. If this doesn't already exist, it will be created for you by subsequent code. You will see this request pattern used very often throughout IndexedDB. Database operations take time. You don't want to hang the browser while you wait for the results, so database operations are <a href="/en-US/docs/Glossary/Asynchronous">asynchronous</a>, meaning that instead of happening immediately, they will happen at some point in the future, and you get notified when they're done.</p> <p>To handle this in IndexedDB, you create a request object (which can be called anything you like — we called it <code>openRequest</code> here, so it is obvious what it is for). You then use event handlers to run code when the request completes, fails, etc., which you'll see in use below.</p> <div class="notecard note"> <p><strong>Note:</strong> The version number is important. If you want to upgrade your database (for example, by changing the table structure), you have to run your code again with an increased version number, different schema specified inside the <code>upgradeneeded</code> handler (see below), etc. We won't cover upgrading databases in this tutorial.</p> </div> </li> <li> <p>Now add the following event handlers just below your previous addition:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// error handler signifies that the database didn't open successfully openRequest.addEventListener("error", () => console.error("Database failed to open"), ); // success handler signifies that the database opened successfully openRequest.addEventListener("success", () => { console.log("Database opened successfully"); // Store the opened database object in the db variable. This is used a lot below db = openRequest.result; // Run the displayData() function to display the notes already in the IDB displayData(); }); </code></pre></div> <p>The <a href="/en-US/docs/Web/API/IDBRequest/error_event" title="error"><code>error</code></a> event handler will run if the system comes back saying that the request failed. This allows you to respond to this problem. In our example, we just print a message to the JavaScript console.</p> <p>The <a href="/en-US/docs/Web/API/IDBRequest/success_event" title="success"><code>success</code></a> event handler will run if the request returns successfully, meaning the database was successfully opened. If this is the case, an object representing the opened database becomes available in the <a href="/en-US/docs/Web/API/IDBRequest/result" title="openRequest.result"><code>openRequest.result</code></a> property, allowing us to manipulate the database. We store this in the <code>db</code> variable we created earlier for later use. We also run a function called <code>displayData()</code>, which displays the data in the database inside the <a href="/en-US/docs/Web/HTML/Element/ul"><code><ul></code></a>. We run it now so that the notes already in the database are displayed as soon as the page loads. You'll see <code>displayData()</code> defined later on.</p> </li> <li> <p>Finally for this section, we'll add probably the most important event handler for setting up the database: <a href="/en-US/docs/Web/API/IDBOpenDBRequest/upgradeneeded_event" title="upgradeneeded"><code>upgradeneeded</code></a>. This handler runs if the database has not already been set up, or if the database is opened with a bigger version number than the existing stored database (when performing an upgrade). Add the following code, below your previous handler:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// Set up the database tables if this has not already been done openRequest.addEventListener("upgradeneeded", (e) => { // Grab a reference to the opened database db = e.target.result; // Create an objectStore in our database to store notes and an auto-incrementing key // An objectStore is similar to a 'table' in a relational database const objectStore = db.createObjectStore("notes_os", { keyPath: "id", autoIncrement: true, }); // Define what data items the objectStore will contain objectStore.createIndex("title", "title", { unique: false }); objectStore.createIndex("body", "body", { unique: false }); console.log("Database setup complete"); }); </code></pre></div> <p>This is where we define the schema (structure) of our database; that is, the set of columns (or fields) it contains. Here we first grab a reference to the existing database from the <code>result</code> property of the event's target (<code>e.target.result</code>), which is the <code>request</code> object. This is equivalent to the line <code>db = openRequest.result;</code> inside the <code>success</code> event handler, but we need to do this separately here because the <code>upgradeneeded</code> event handler (if needed) will run before the <code>success</code> event handler, meaning that the <code>db</code> value wouldn't be available if we didn't do this.</p> <p>We then use <a href="/en-US/docs/Web/API/IDBDatabase/createObjectStore"><code>IDBDatabase.createObjectStore()</code></a> to create a new object store inside our opened database called <code>notes_os</code>. This is equivalent to a single table in a conventional database system. We've given it the name notes, and also specified an <code>autoIncrement</code> key field called <code>id</code> — in each new record this will automatically be given an incremented value — the developer doesn't need to set this explicitly. Being the key, the <code>id</code> field will be used to uniquely identify records, such as when deleting or displaying a record.</p> <p>We also create two other indexes (fields) using the <a href="/en-US/docs/Web/API/IDBObjectStore/createIndex"><code>IDBObjectStore.createIndex()</code></a> method: <code>title</code> (which will contain a title for each note), and <code>body</code> (which will contain the body text of the note).</p> </li> </ol> <p>So with this database schema set up, when we start adding records to the database, each one will be represented as an object along these lines:</p> <div class="code-example"><div class="example-header"><span class="language-name">json</span></div><pre class="brush: json notranslate"><code>{ "title": "Buy milk", "body": "Need both cows milk and soy.", "id": 8 } </code></pre></div></div></section><section aria-labelledby="adding_data_to_the_database"><h3 id="adding_data_to_the_database"><a href="#adding_data_to_the_database">Adding data to the database</a></h3><div class="section-content"><p>Now let's look at how we can add records to the database. This will be done using the form on our page.</p> <p>Below your previous event handler, add the following line, which sets up a <code>submit</code> event handler that runs a function called <code>addData()</code> when the form is submitted (when the submit <a href="/en-US/docs/Web/HTML/Element/button"><code><button></code></a> is pressed leading to a successful form submission):</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// Create a submit event handler so that when the form is submitted the addData() function is run form.addEventListener("submit", addData); </code></pre></div> <p>Now let's define the <code>addData()</code> function. Add this below your previous line:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// Define the addData() function function addData(e) { // prevent default - we don't want the form to submit in the conventional way e.preventDefault(); // grab the values entered into the form fields and store them in an object ready for being inserted into the DB const newItem = { title: titleInput.value, body: bodyInput.value }; // open a read/write db transaction, ready for adding the data const transaction = db.transaction(["notes_os"], "readwrite"); // call an object store that's already been added to the database const objectStore = transaction.objectStore("notes_os"); // Make a request to add our newItem object to the object store const addRequest = objectStore.add(newItem); addRequest.addEventListener("success", () => { // Clear the form, ready for adding the next entry titleInput.value = ""; bodyInput.value = ""; }); // Report on the success of the transaction completing, when everything is done transaction.addEventListener("complete", () => { console.log("Transaction completed: database modification finished."); // update the display of data to show the newly added item, by running displayData() again. displayData(); }); transaction.addEventListener("error", () => console.log("Transaction not opened due to error"), ); } </code></pre></div> <p>This is quite complex; breaking it down, we:</p> <ul> <li>Run <a href="/en-US/docs/Web/API/Event/preventDefault"><code>Event.preventDefault()</code></a> on the event object to stop the form actually submitting in the conventional manner (this would cause a page refresh and spoil the experience).</li> <li>Create an object representing a record to enter into the database, populating it with values from the form inputs. Note that we don't have to explicitly include an <code>id</code> value — as we explained earlier, this is auto-populated.</li> <li>Open a <code>readwrite</code> transaction against the <code>notes_os</code> object store using the <a href="/en-US/docs/Web/API/IDBDatabase/transaction"><code>IDBDatabase.transaction()</code></a> method. This transaction object allows us to access the object store so we can do something to it, e.g. add a new record.</li> <li>Access the object store using the <a href="/en-US/docs/Web/API/IDBTransaction/objectStore"><code>IDBTransaction.objectStore()</code></a> method, saving the result in the <code>objectStore</code> variable.</li> <li>Add the new record to the database using <a href="/en-US/docs/Web/API/IDBObjectStore/add"><code>IDBObjectStore.add()</code></a>. This creates a request object, in the same fashion as we've seen before.</li> <li>Add a bunch of event handlers to the <code>request</code> and the <code>transaction</code> objects to run code at critical points in the lifecycle. Once the request has succeeded, we clear the form inputs ready for entering the next note. Once the transaction has completed, we run the <code>displayData()</code> function again to update the display of notes on the page.</li> </ul></div></section><section aria-labelledby="displaying_the_data"><h3 id="displaying_the_data"><a href="#displaying_the_data">Displaying the data</a></h3><div class="section-content"><p>We've referenced <code>displayData()</code> twice in our code already, so we'd probably better define it. Add this to your code, below the previous function definition:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// Define the displayData() function function displayData() { // Here we empty the contents of the list element each time the display is updated // If you didn't do this, you'd get duplicates listed each time a new note is added while (list.firstChild) { list.removeChild(list.firstChild); } // Open our object store and then get a cursor - which iterates through all the // different data items in the store const objectStore = db.transaction("notes_os").objectStore("notes_os"); objectStore.openCursor().addEventListener("success", (e) => { // Get a reference to the cursor const cursor = e.target.result; // If there is still another data item to iterate through, keep running this code if (cursor) { // Create a list item, h3, and p to put each data item inside when displaying it // structure the HTML fragment, and append it inside the list const listItem = document.createElement("li"); const h3 = document.createElement("h3"); const para = document.createElement("p"); listItem.appendChild(h3); listItem.appendChild(para); list.appendChild(listItem); // Put the data from the cursor inside the h3 and para h3.textContent = cursor.value.title; para.textContent = cursor.value.body; // Store the ID of the data item inside an attribute on the listItem, so we know // which item it corresponds to. This will be useful later when we want to delete items listItem.setAttribute("data-note-id", cursor.value.id); // Create a button and place it inside each listItem const deleteBtn = document.createElement("button"); listItem.appendChild(deleteBtn); deleteBtn.textContent = "Delete"; // Set an event handler so that when the button is clicked, the deleteItem() // function is run deleteBtn.addEventListener("click", deleteItem); // Iterate to the next item in the cursor cursor.continue(); } else { // Again, if list item is empty, display a 'No notes stored' message if (!list.firstChild) { const listItem = document.createElement("li"); listItem.textContent = "No notes stored."; list.appendChild(listItem); } // if there are no more cursor items to iterate through, say so console.log("Notes all displayed"); } }); } </code></pre></div> <p>Again, let's break this down:</p> <ul> <li>First, we empty out the <a href="/en-US/docs/Web/HTML/Element/ul"><code><ul></code></a> element's content, before then filling it with the updated content. If you didn't do this, you'd end up with a huge list of duplicated content being added to with each update.</li> <li>Next, we get a reference to the <code>notes_os</code> object store using <a href="/en-US/docs/Web/API/IDBDatabase/transaction"><code>IDBDatabase.transaction()</code></a> and <a href="/en-US/docs/Web/API/IDBTransaction/objectStore"><code>IDBTransaction.objectStore()</code></a> like we did in <code>addData()</code>, except here we are chaining them together in one line.</li> <li>The next step is to use the <a href="/en-US/docs/Web/API/IDBObjectStore/openCursor"><code>IDBObjectStore.openCursor()</code></a> method to open a request for a cursor — this is a construct that can be used to iterate over the records in an object store. We chain a <code>success</code> event handler onto the end of this line to make the code more concise — when the cursor is successfully returned, the handler is run.</li> <li>We get a reference to the cursor itself (an <a href="/en-US/docs/Web/API/IDBCursor"><code>IDBCursor</code></a> object) using <code>const cursor = e.target.result</code>.</li> <li>Next, we check to see if the cursor contains a record from the datastore (<code>if (cursor){ }</code>) — if so, we create a DOM fragment, populate it with the data from the record, and insert it into the page (inside the <code><ul></code> element). We also include a delete button that, when clicked, will delete that note by running the <code>deleteItem()</code> function, which we will look at in the next section.</li> <li>At the end of the <code>if</code> block, we use the <a href="/en-US/docs/Web/API/IDBCursor/continue"><code>IDBCursor.continue()</code></a> method to advance the cursor to the next record in the datastore, and run the content of the <code>if</code> block again. If there is another record to iterate to, this causes it to be inserted into the page, and then <code>continue()</code> is run again, and so on.</li> <li>When there are no more records to iterate over, <code>cursor</code> will return <code>undefined</code>, and therefore the <code>else</code> block will run instead of the <code>if</code> block. This block checks whether any notes were inserted into the <code><ul></code> — if not, it inserts a message to say no note was stored.</li> </ul></div></section><section aria-labelledby="deleting_a_note"><h3 id="deleting_a_note"><a href="#deleting_a_note">Deleting a note</a></h3><div class="section-content"><p>As stated above, when a note's delete button is pressed, the note is deleted. This is achieved by the <code>deleteItem()</code> function, which looks like so:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// Define the deleteItem() function function deleteItem(e) { // retrieve the name of the task we want to delete. We need // to convert it to a number before trying to use it with IDB; IDB key // values are type-sensitive. const noteId = Number(e.target.parentNode.getAttribute("data-note-id")); // open a database transaction and delete the task, finding it using the id we retrieved above const transaction = db.transaction(["notes_os"], "readwrite"); const objectStore = transaction.objectStore("notes_os"); const deleteRequest = objectStore.delete(noteId); // report that the data item has been deleted transaction.addEventListener("complete", () => { // delete the parent of the button // which is the list item, so it is no longer displayed e.target.parentNode.parentNode.removeChild(e.target.parentNode); console.log(`Note ${noteId} deleted.`); // Again, if list item is empty, display a 'No notes stored' message if (!list.firstChild) { const listItem = document.createElement("li"); listItem.textContent = "No notes stored."; list.appendChild(listItem); } }); } </code></pre></div> <ul> <li>The first part of this could use some explaining — we retrieve the ID of the record to be deleted using <code>Number(e.target.parentNode.getAttribute('data-note-id'))</code> — recall that the ID of the record was saved in a <code>data-note-id</code> attribute on the <code><li></code> when it was first displayed. We do however need to pass the attribute through the global built-in <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number"><code>Number()</code></a> object as it is of datatype string, and therefore wouldn't be recognized by the database, which expects a number.</li> <li>We then get a reference to the object store using the same pattern we've seen previously, and use the <a href="/en-US/docs/Web/API/IDBObjectStore/delete"><code>IDBObjectStore.delete()</code></a> method to delete the record from the database, passing it the ID.</li> <li>When the database transaction is complete, we delete the note's <code><li></code> from the DOM, and again do the check to see if the <code><ul></code> is now empty, inserting a note as appropriate.</li> </ul> <p>So that's it! Your example should now work.</p> <p>If you are having trouble with it, feel free to <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/indexeddb/notes/" class="external" target="_blank">check it against our live example</a> (see the <a href="https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/index.js" class="external" target="_blank">source code</a> also).</p></div></section><section aria-labelledby="storing_complex_data_via_indexeddb"><h3 id="storing_complex_data_via_indexeddb"><a href="#storing_complex_data_via_indexeddb">Storing complex data via IndexedDB</a></h3><div class="section-content"><p>As we mentioned above, IndexedDB can be used to store more than just text strings. You can store just about anything you want, including complex objects such as video or image blobs. And it isn't much more difficult to achieve than any other type of data.</p> <p>To demonstrate how to do it, we've written another example called <a href="https://github.com/mdn/learning-area/tree/main/javascript/apis/client-side-storage/indexeddb/video-store" class="external" target="_blank">IndexedDB video store</a> (see it <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/indexeddb/video-store/" class="external" target="_blank">running live here also</a>). When you first run the example, it downloads all the videos from the network, stores them in an IndexedDB database, and then displays the videos in the UI inside <a href="/en-US/docs/Web/HTML/Element/video"><code><video></code></a> elements. The second time you run it, it finds the videos in the database and gets them from there instead before displaying them — this makes subsequent loads much quicker and less bandwidth-hungry.</p> <p>Let's walk through the most interesting parts of the example. We won't look at it all — a lot of it is similar to the previous example, and the code is well-commented.</p> <ol> <li> <p>For this example, we've stored the names of the videos to fetch in an array of objects:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>const videos = [ { name: "crystal" }, { name: "elf" }, { name: "frog" }, { name: "monster" }, { name: "pig" }, { name: "rabbit" }, ]; </code></pre></div> </li> <li> <p>To start with, once the database is successfully opened we run an <code>init()</code> function. This loops through the different video names, trying to load a record identified by each name from the <code>videos</code> database.</p> <p>If each video is found in the database (checked by seeing whether <code>request.result</code> evaluates to <code>true</code> — if the record is not present, it will be <code>undefined</code>), its video files (stored as blobs) and the video name are passed straight to the <code>displayVideo()</code> function to place them in the UI. If not, the video name is passed to the <code>fetchVideoFromNetwork()</code> function to, you guessed it, fetch the video from the network.</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>function init() { // Loop through the video names one by one for (const video of videos) { // Open transaction, get object store, and get() each video by name const objectStore = db.transaction("videos_os").objectStore("videos_os"); const request = objectStore.get(video.name); request.addEventListener("success", () => { // If the result exists in the database (is not undefined) if (request.result) { // Grab the videos from IDB and display them using displayVideo() console.log("taking videos from IDB"); displayVideo( request.result.mp4, request.result.webm, request.result.name, ); } else { // Fetch the videos from the network fetchVideoFromNetwork(video); } }); } } </code></pre></div> </li> <li> <p>The following snippet is taken from inside <code>fetchVideoFromNetwork()</code> — here we fetch MP4 and WebM versions of the video using two separate <a href="/en-US/docs/Web/API/Window/fetch" title="fetch()"><code>fetch()</code></a> requests. We then use the <a href="/en-US/docs/Web/API/Response/blob"><code>Response.blob()</code></a> method to extract each response's body as a blob, giving us an object representation of the videos that can be stored and displayed later on.</p> <p>We have a problem here though — these two requests are both asynchronous, but we only want to try to display or store the video when both promises have fulfilled. Fortunately there is a built-in method that handles such a problem — <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all"><code>Promise.all()</code></a>. This takes one argument — references to all the individual promises you want to check for fulfillment placed in an array — and returns a promise which is fulfilled when all the individual promises are fulfilled.</p> <p>Inside the <code>then()</code> handler for this promise, we call the <code>displayVideo()</code> function like we did before to display the videos in the UI, then we also call the <code>storeVideo()</code> function to store those videos inside the database.</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// Fetch the MP4 and WebM versions of the video using the fetch() function, // then expose their response bodies as blobs const mp4Blob = fetch(`videos/${video.name}.mp4`).then((response) => response.blob(), ); const webmBlob = fetch(`videos/${video.name}.webm`).then((response) => response.blob(), ); // Only run the next code when both promises have fulfilled Promise.all([mp4Blob, webmBlob]).then((values) => { // display the video fetched from the network with displayVideo() displayVideo(values[0], values[1], video.name); // store it in the IDB using storeVideo() storeVideo(values[0], values[1], video.name); }); </code></pre></div> </li> <li> <p>Let's look at <code>storeVideo()</code> first. This is very similar to the pattern you saw in the previous example for adding data to the database — we open a <code>readwrite</code> transaction and get a reference to our <code>videos_os</code> object store, create an object representing the record to add to the database, then add it using <a href="/en-US/docs/Web/API/IDBObjectStore/add"><code>IDBObjectStore.add()</code></a>.</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// Define the storeVideo() function function storeVideo(mp4, webm, name) { // Open transaction, get object store; make it a readwrite so we can write to the IDB const objectStore = db .transaction(["videos_os"], "readwrite") .objectStore("videos_os"); // Add the record to the IDB using add() const request = objectStore.add({ mp4, webm, name }); request.addEventListener("success", () => console.log("Record addition attempt finished"), ); request.addEventListener("error", () => console.error(request.error)); } </code></pre></div> </li> <li> <p>Finally, we have <code>displayVideo()</code>, which creates the DOM elements needed to insert the video in the UI and then appends them to the page. The most interesting parts of this are those shown below — to actually display our video blobs in a <code><video></code> element, we need to create object URLs (internal URLs that point to the video blobs stored in memory) using the <a href="/en-US/docs/Web/API/URL/createObjectURL_static" title="URL.createObjectURL()"><code>URL.createObjectURL()</code></a> method. Once that is done, we can set the object URLs to be the values of our <a href="/en-US/docs/Web/HTML/Element/source"><code><source></code></a> element's <code>src</code> attributes, and it works fine.</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// Define the displayVideo() function function displayVideo(mp4Blob, webmBlob, title) { // Create object URLs out of the blobs const mp4URL = URL.createObjectURL(mp4Blob); const webmURL = URL.createObjectURL(webmBlob); // Create DOM elements to embed video in the page const article = document.createElement("article"); const h2 = document.createElement("h2"); h2.textContent = title; const video = document.createElement("video"); video.controls = true; const source1 = document.createElement("source"); source1.src = mp4URL; source1.type = "video/mp4"; const source2 = document.createElement("source"); source2.src = webmURL; source2.type = "video/webm"; // Embed DOM elements into page section.appendChild(article); article.appendChild(h2); article.appendChild(video); video.appendChild(source1); video.appendChild(source2); } </code></pre></div> </li> </ol></div></section><section aria-labelledby="offline_asset_storage"><h2 id="offline_asset_storage"><a href="#offline_asset_storage">Offline asset storage</a></h2><div class="section-content"><p>The above example already shows how to create an app that will store large assets in an IndexedDB database, avoiding the need to download them more than once. This is already a great improvement to the user experience, but there is still one thing missing — the main HTML, CSS, and JavaScript files still need to be downloaded each time the site is accessed, meaning that it won't work when there is no network connection.</p> <p> <img src="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/ff-offline.png" alt="Firefox offline screen with an illustration of a cartoon character to the left-hand side holding a two-pin plug in its right hand and a two-pin socket in its left hand. On the right-hand side there is an Offline Mode message and a button labeled 'Try again'." width="765" height="307" loading="lazy"> </p> <p>This is where <a href="/en-US/docs/Web/API/Service_Worker_API">Service workers</a> and the closely-related <a href="/en-US/docs/Web/API/Cache">Cache API</a> come in.</p> <p>A service worker is a JavaScript file that is registered against a particular origin (website, or part of a website at a certain domain) when it is accessed by a browser. When registered, it can control pages available at that origin. It does this by sitting between a loaded page and the network and intercepting network requests aimed at that origin.</p> <p>When it intercepts a request, it can do anything you wish to it (see <a href="/en-US/docs/Web/API/Service_Worker_API#other_use_case_ideas">use case ideas</a>), but the classic example is saving the network responses offline and then providing those in response to a request instead of the responses from the network. In effect, it allows you to make a website work completely offline.</p> <p>The Cache API is another client-side storage mechanism, with a bit of a difference — it is designed to save HTTP responses, and so works very well with service workers.</p></div></section><section aria-labelledby="a_service_worker_example"><h3 id="a_service_worker_example"><a href="#a_service_worker_example">A service worker example</a></h3><div class="section-content"><p>Let's look at an example, to give you a bit of an idea of what this might look like. We have created another version of the video store example we saw in the previous section — this functions identically, except that it also saves the HTML, CSS, and JavaScript in the Cache API via a service worker, allowing the example to run offline!</p> <p>See <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/" class="external" target="_blank">IndexedDB video store with service worker running live</a>, and also <a href="https://github.com/mdn/learning-area/tree/main/javascript/apis/client-side-storage/cache-sw/video-store-offline" class="external" target="_blank">see the source code</a>.</p> <h4 id="registering_the_service_worker">Registering the service worker</h4> <p>The first thing to note is that there's an extra bit of code placed in the main JavaScript file (see <a href="https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.js" class="external" target="_blank">index.js</a>). First, we do a feature detection test to see if the <code>serviceWorker</code> member is available in the <a href="/en-US/docs/Web/API/Navigator"><code>Navigator</code></a> object. If this returns true, then we know that at least the basics of service workers are supported. Inside here we use the <a href="/en-US/docs/Web/API/ServiceWorkerContainer/register"><code>ServiceWorkerContainer.register()</code></a> method to register a service worker contained in the <code>sw.js</code> file against the origin it resides at, so it can control pages in the same directory as it, or subdirectories. When its promise fulfills, the service worker is deemed registered.</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// Register service worker to control making site work offline if ("serviceWorker" in navigator) { navigator.serviceWorker .register( "/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js", ) .then(() => console.log("Service Worker Registered")); } </code></pre></div> <div class="notecard note"> <p><strong>Note:</strong> The given path to the <code>sw.js</code> file is relative to the site origin, not the JavaScript file that contains the code. The service worker is at <code>https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js</code>. The origin is <code>https://mdn.github.io</code>, and therefore the given path has to be <code>/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js</code>. If you wanted to host this example on your own server, you'd have to change this accordingly. This is rather confusing, but it has to work this way for security reasons.</p> </div> <h4 id="installing_the_service_worker">Installing the service worker</h4> <p>The next time any page under the service worker's control is accessed (e.g. when the example is reloaded), the service worker is installed against that page, meaning that it will start controlling it. When this occurs, an <code>install</code> event is fired against the service worker; you can write code inside the service worker itself that will respond to the installation.</p> <p>Let's look at an example, in the <a href="https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js" class="external" target="_blank">sw.js</a> file (the service worker). You'll see that the install listener is registered against <code>self</code>. This <code>self</code> keyword is a way to refer to the global scope of the service worker from inside the service worker file.</p> <p>Inside the <code>install</code> handler, we use the <a href="/en-US/docs/Web/API/ExtendableEvent/waitUntil"><code>ExtendableEvent.waitUntil()</code></a> method, available on the event object, to signal that the browser shouldn't complete installation of the service worker until after the promise inside it has fulfilled successfully.</p> <p>Here is where we see the Cache API in action. We use the <a href="/en-US/docs/Web/API/CacheStorage/open"><code>CacheStorage.open()</code></a> method to open a new cache object in which responses can be stored (similar to an IndexedDB object store). This promise fulfills with a <a href="/en-US/docs/Web/API/Cache"><code>Cache</code></a> object representing the <code>video-store</code> cache. We then use the <a href="/en-US/docs/Web/API/Cache/addAll"><code>Cache.addAll()</code></a> method to fetch a series of assets and add their responses to the cache.</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>self.addEventListener("install", (e) => { e.waitUntil( caches .open("video-store") .then((cache) => cache.addAll([ "/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/", "/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.html", "/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.js", "/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/style.css", ]), ), ); }); </code></pre></div> <p>That's it for now, installation done.</p> <h4 id="responding_to_further_requests">Responding to further requests</h4> <p>With the service worker registered and installed against our HTML page, and the relevant assets all added to our cache, we are nearly ready to go. There is only one more thing to do: write some code to respond to further network requests.</p> <p>This is what the second bit of code in <code>sw.js</code> does. We add another listener to the service worker global scope, which runs the handler function when the <code>fetch</code> event is raised. This happens whenever the browser makes a request for an asset in the directory the service worker is registered against.</p> <p>Inside the handler, we first log the URL of the requested asset. We then provide a custom response to the request, using the <a href="/en-US/docs/Web/API/FetchEvent/respondWith"><code>FetchEvent.respondWith()</code></a> method.</p> <p>Inside this block, we use <a href="/en-US/docs/Web/API/CacheStorage/match"><code>CacheStorage.match()</code></a> to check whether a matching request (i.e. matches the URL) can be found in any cache. This promise fulfills with the matching response if a match is found, or <code>undefined</code> if it isn't.</p> <p>If a match is found, we return it as the custom response. If not, we <a href="/en-US/docs/Web/API/Window/fetch">fetch()</a> the response from the network and return that instead.</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>self.addEventListener("fetch", (e) => { console.log(e.request.url); e.respondWith( caches.match(e.request).then((response) => response || fetch(e.request)), ); }); </code></pre></div> <p> And that is it for our service worker. There is a whole load more you can do with them — for a lot more detail, see the <a href="https://github.com/mdn/serviceworker-cookbook" class="external" target="_blank">service worker cookbook</a>. Many thanks to Paul Kinlan for his article <a href="https://developers.google.com/codelabs/pwa-training/pwa03--going-offline#0" class="external" target="_blank">Adding a Service Worker and Offline into your Web App</a>, which inspired this example. </p> <h4 id="testing_the_example_offline">Testing the example offline</h4> <p>To test our <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/" class="external" target="_blank">service worker example</a>, you'll need to load it a couple of times to make sure it is installed. Once this is done, you can:</p> <ul> <li>Try unplugging your network/turning your Wi-Fi off.</li> <li>Select <em>File > Work Offline</em> if you are using Firefox.</li> <li>Go to the devtools, then choose <em>Application > Service Workers</em>, then check the <em>Offline</em> checkbox if you are using Chrome.</li> </ul> <p>If you refresh your example page again, you should still see it load just fine. Everything is stored offline — the page assets in a cache, and the videos in an IndexedDB database.</p></div></section><section aria-labelledby="summary"><h2 id="summary"><a href="#summary">Summary</a></h2><div class="section-content"><p>That's it for now. We hope you've found our rundown of client-side storage technologies useful.</p></div></section><section aria-labelledby="see_also"><h2 id="see_also"><a href="#see_also">See also</a></h2><div class="section-content"><ul> <li><a href="/en-US/docs/Web/API/Web_Storage_API">Web storage API</a></li> <li><a href="/en-US/docs/Web/API/IndexedDB_API">IndexedDB API</a></li> <li><a href="/en-US/docs/Web/HTTP/Cookies">Cookies</a></li> <li><a href="/en-US/docs/Web/API/Service_Worker_API">Service worker API</a></li> </ul><ul class="prev-next"> <li><a class="button secondary" href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs"><span class="button-wrap"> Previous </span></a></li> <li><a class="button secondary" href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs"><span class="button-wrap"> Overview: Client-side web APIs</span></a></li> </ul></div></section></article><aside class="article-footer"><div class="article-footer-inner"><div class="svg-container"><svg xmlns="http://www.w3.org/2000/svg" width="162" height="162" viewBox="0 0 162 162" fill="none" role="none"><mask id="b" fill="#fff"><path d="M97.203 47.04c8.113-7.886 18.004-13.871 28.906-17.492a78 78 0 0 1 33.969-3.39c11.443 1.39 22.401 5.295 32.024 11.411s17.656 14.28 23.476 23.86c5.819 9.579 9.269 20.318 10.083 31.385a69.85 69.85 0 0 1-5.387 32.44c-4.358 10.272-11.115 19.443-19.747 26.801-8.632 7.359-18.908 12.709-30.034 15.637l-6.17-21.698c7.666-2.017 14.746-5.703 20.694-10.773 5.948-5.071 10.603-11.389 13.606-18.467a48.14 48.14 0 0 0 3.712-22.352c-.561-7.625-2.938-15.025-6.948-21.625s-9.544-12.226-16.175-16.44-14.181-6.904-22.065-7.863a53.75 53.75 0 0 0-23.405 2.336c-7.513 2.495-14.327 6.62-19.918 12.053z"></path></mask><path stroke="url(#a)" stroke-dasharray="6, 6" stroke-width="2" d="M97.203 47.04c8.113-7.886 18.004-13.871 28.906-17.492a78 78 0 0 1 33.969-3.39c11.443 1.39 22.401 5.295 32.024 11.411s17.656 14.28 23.476 23.86c5.819 9.579 9.269 20.318 10.083 31.385a69.85 69.85 0 0 1-5.387 32.44c-4.358 10.272-11.115 19.443-19.747 26.801-8.632 7.359-18.908 12.709-30.034 15.637l-6.17-21.698c7.666-2.017 14.746-5.703 20.694-10.773 5.948-5.071 10.603-11.389 13.606-18.467a48.14 48.14 0 0 0 3.712-22.352c-.561-7.625-2.938-15.025-6.948-21.625s-9.544-12.226-16.175-16.44-14.181-6.904-22.065-7.863a53.75 53.75 0 0 0-23.405 2.336c-7.513 2.495-14.327 6.62-19.918 12.053z" mask="url(#b)" style="stroke:url(#a)" transform="translate(-63.992 -25.587)"></path><ellipse cx="8.066" cy="111.597" fill="var(--background-tertiary)" rx="53.677" ry="53.699" transform="matrix(.71707 -.697 .7243 .6895 0 0)"></ellipse><g clip-path="url(#c)" transform="translate(-63.992 -25.587)"><path fill="#9abff5" d="m144.256 137.379 32.906 12.434a4.41 4.41 0 0 1 2.559 5.667l-9.326 24.679a4.41 4.41 0 0 1-5.667 2.559l-8.226-3.108-2.332 6.17c-.466 1.233-.375 1.883-1.609 1.417l-2.253-.527c-.411-.155-.95-.594-1.206-1.161l-4.734-10.484-12.545-4.741a4.41 4.41 0 0 1-2.559-5.667l9.325-24.679a4.41 4.41 0 0 1 5.667-2.559m9.961 29.617 8.227 3.108 3.264-8.638-.498-6.768-4.113-1.555.548 7.258-4.319-1.632zm-12.339-4.663 8.226 3.108 3.264-8.637-.498-6.769-4.113-1.554.548 7.257-4.319-1.632z"></path></g><g clip-path="url(#d)" transform="translate(-63.992 -25.587)"><path fill="#81b0f3" d="M135.35 60.136 86.67 41.654c-3.346-1.27-7.124.428-8.394 3.775L64.414 81.938c-1.27 3.347.428 7.125 3.774 8.395l12.17 4.62-3.465 9.128c-.693 1.826-1.432 2.457.394 3.15l3.014 1.625c.609.231 1.637.274 2.477-.104l15.53-6.983 18.56 7.047c3.346 1.27 7.124-.428 8.395-3.775l13.862-36.51c1.27-3.346-.428-7.124-3.775-8.395M95.261 83.207l-12.17-4.62 4.852-12.779 7.19-7.017 6.085 2.31-7.725 7.51 6.389 2.426zm18.255 6.93-12.17-4.62 4.852-12.778 7.189-7.017 6.085 2.31-7.725 7.51 6.39 2.426z"></path></g><defs><clipPath id="c"><path fill="#fff" d="m198.638 146.586-65.056-24.583-24.583 65.057 65.056 24.582z"></path></clipPath><clipPath id="d"><path fill="#fff" d="m66.438 14.055 96.242 36.54-36.54 96.243-96.243-36.54z"></path></clipPath><linearGradient id="a" x1="97.203" x2="199.995" y1="47.04" y2="152.793" gradientUnits="userSpaceOnUse"><stop stop-color="#086DFC"></stop><stop offset="0.246" stop-color="#2C81FA"></stop><stop offset="0.516" stop-color="#5497F8"></stop><stop offset="0.821" stop-color="#80B0F6"></stop><stop offset="1" stop-color="#9ABFF5"></stop></linearGradient></defs></svg></div><h2>Help improve MDN</h2><fieldset class="feedback"><label>Was this page helpful to you?</label><div class="button-container"><button type="button" class="button primary has-icon yes"><span class="button-wrap"><span class="icon icon-thumbs-up "></span>Yes</span></button><button type="button" class="button primary has-icon no"><span class="button-wrap"><span class="icon icon-thumbs-down "></span>No</span></button></div></fieldset><a class="contribute" href="https://github.com/mdn/content/blob/main/CONTRIBUTING.md" title="This will take you to our contribution guidelines on GitHub." target="_blank" rel="noopener noreferrer">Learn how to contribute</a>.<p class="last-modified-date">This page was last modified on<!-- --> <time dateTime="2024-07-25T21:20:22.000Z">Jul 25, 2024</time> by<!-- --> <a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/contributors.txt" rel="nofollow">MDN contributors</a>.</p><div id="on-github" class="on-github"><a href="https://github.com/mdn/content/blob/main/files/en-us/learn/javascript/client-side_web_apis/client-side_storage/index.md?plain=1" title="Folder: en-us/learn/javascript/client-side_web_apis/client-side_storage (Opens in a new tab)" target="_blank" rel="noopener noreferrer">View this page on GitHub</a> <!-- -->•<!-- --> <a href="https://github.com/mdn/content/issues/new?template=page-report.yml&mdn-url=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FLearn%2FJavaScript%2FClient-side_web_APIs%2FClient-side_storage&metadata=%3C%21--+Do+not+make+changes+below+this+line+--%3E%0A%3Cdetails%3E%0A%3Csummary%3EPage+report+details%3C%2Fsummary%3E%0A%0A*+Folder%3A+%60en-us%2Flearn%2Fjavascript%2Fclient-side_web_apis%2Fclient-side_storage%60%0A*+MDN+URL%3A+https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FLearn%2FJavaScript%2FClient-side_web_APIs%2FClient-side_storage%0A*+GitHub+URL%3A+https%3A%2F%2Fgithub.com%2Fmdn%2Fcontent%2Fblob%2Fmain%2Ffiles%2Fen-us%2Flearn%2Fjavascript%2Fclient-side_web_apis%2Fclient-side_storage%2Findex.md%0A*+Last+commit%3A+https%3A%2F%2Fgithub.com%2Fmdn%2Fcontent%2Fcommit%2Fbc0d0d1ef796435e969f6d65c7e5d3c08f4023aa%0A*+Document+last+modified%3A+2024-07-25T21%3A20%3A22.000Z%0A%0A%3C%2Fdetails%3E" title="This will take you to GitHub to file a new issue." target="_blank" rel="noopener noreferrer">Report a problem with this content</a></div></div></aside></main></div></div><footer id="nav-footer" class="page-footer"><div class="page-footer-grid"><div class="page-footer-logo-col"><a href="/" class="mdn-footer-logo" aria-label="MDN homepage"><svg width="48" height="17" viewBox="0 0 48 17" fill="none" xmlns="http://www.w3.org/2000/svg"><title id="mdn-footer-logo-svg">MDN logo</title><path d="M20.04 16.512H15.504V10.416C15.504 9.488 15.344 8.824 15.024 8.424C14.72 8.024 14.264 7.824 13.656 7.824C12.92 7.824 12.384 8.064 12.048 8.544C11.728 9.024 11.568 9.64 11.568 10.392V14.184H13.008V16.512H8.472V10.416C8.472 9.488 8.312 8.824 7.992 8.424C7.688 8.024 7.232 7.824 6.624 7.824C5.872 7.824 5.336 8.064 5.016 8.544C4.696 9.024 4.536 9.64 4.536 10.392V14.184H6.6V16.512H0V14.184H1.44V8.04H0.024V5.688H4.536V7.32C5.224 6.088 6.32 5.472 7.824 5.472C8.608 5.472 9.328 5.664 9.984 6.048C10.64 6.432 11.096 7.016 11.352 7.8C11.992 6.248 13.168 5.472 14.88 5.472C15.856 5.472 16.72 5.776 17.472 6.384C18.224 6.992 18.6 7.936 18.6 9.216V14.184H20.04V16.512Z" fill="currentColor"></path><path d="M33.6714 16.512H29.1354V14.496C28.8314 15.12 28.3834 15.656 27.7914 16.104C27.1994 16.536 26.4154 16.752 25.4394 16.752C24.0154 16.752 22.8954 16.264 22.0794 15.288C21.2634 14.312 20.8554 12.984 20.8554 11.304C20.8554 9.688 21.2554 8.312 22.0554 7.176C22.8554 6.04 24.0634 5.472 25.6794 5.472C26.5594 5.472 27.2794 5.648 27.8394 6C28.3994 6.352 28.8314 6.8 29.1354 7.344V2.352H26.9754V0H32.2314V14.184H33.6714V16.512ZM29.1354 11.04V10.776C29.1354 9.88 28.8954 9.184 28.4154 8.688C27.9514 8.176 27.3674 7.92 26.6634 7.92C25.9754 7.92 25.3674 8.176 24.8394 8.688C24.3274 9.2 24.0714 10.008 24.0714 11.112C24.0714 12.152 24.3114 12.944 24.7914 13.488C25.2714 14.032 25.8394 14.304 26.4954 14.304C27.3114 14.304 27.9514 13.96 28.4154 13.272C28.8954 12.584 29.1354 11.84 29.1354 11.04Z" fill="currentColor"></path><path d="M47.9589 16.512H41.9829V14.184H43.4229V10.416C43.4229 9.488 43.2629 8.824 42.9429 8.424C42.6389 8.024 42.1829 7.824 41.5749 7.824C40.8389 7.824 40.2709 8.056 39.8709 8.52C39.4709 8.968 39.2629 9.56 39.2469 10.296V14.184H40.6869V16.512H34.7109V14.184H36.1509V8.04H34.5909V5.688H39.2469V7.344C39.9669 6.096 41.1269 5.472 42.7269 5.472C43.7509 5.472 44.6389 5.776 45.3909 6.384C46.1429 6.992 46.5189 7.936 46.5189 9.216V14.184H47.9589V16.512Z" fill="currentColor"></path></svg></a><p>Your blueprint for a better internet.</p><ul class="social-icons"><li><a href="https://mozilla.social/@mdn" target="_blank" rel="me noopener noreferrer"><span class="icon icon-mastodon"></span><span class="visually-hidden">MDN on Mastodon</span></a></li><li><a href="https://twitter.com/mozdevnet" target="_blank" rel="noopener noreferrer"><span class="icon icon-twitter-x"></span><span class="visually-hidden">MDN on X (formerly Twitter)</span></a></li><li><a href="https://github.com/mdn/" target="_blank" rel="noopener noreferrer"><span class="icon icon-github-mark-small"></span><span class="visually-hidden">MDN on GitHub</span></a></li><li><a href="/en-US/blog/rss.xml" target="_blank"><span class="icon icon-feed"></span><span class="visually-hidden">MDN Blog RSS Feed</span></a></li></ul></div><div class="page-footer-nav-col-1"><h2 class="footer-nav-heading">MDN</h2><ul class="footer-nav-list"><li class="footer-nav-item"><a href="/en-US/about">About</a></li><li class="footer-nav-item"><a href="/en-US/blog/">Blog</a></li><li class="footer-nav-item"><a href="https://www.mozilla.org/en-US/careers/listings/?team=ProdOps" target="_blank" rel="noopener noreferrer">Careers</a></li><li class="footer-nav-item"><a href="/en-US/advertising">Advertise with us</a></li></ul></div><div class="page-footer-nav-col-2"><h2 class="footer-nav-heading">Support</h2><ul class="footer-nav-list"><li class="footer-nav-item"><a class="footer-nav-link" href="https://support.mozilla.org/products/mdn-plus">Product help</a></li><li class="footer-nav-item"><a class="footer-nav-link" href="/en-US/docs/MDN/Community/Issues">Report an issue</a></li></ul></div><div class="page-footer-nav-col-3"><h2 class="footer-nav-heading">Our communities</h2><ul class="footer-nav-list"><li class="footer-nav-item"><a class="footer-nav-link" href="/en-US/community">MDN Community</a></li><li class="footer-nav-item"><a class="footer-nav-link" href="https://discourse.mozilla.org/c/mdn/236" target="_blank" rel="noopener noreferrer">MDN Forum</a></li><li class="footer-nav-item"><a class="footer-nav-link" href="/discord" target="_blank" rel="noopener noreferrer">MDN Chat</a></li></ul></div><div class="page-footer-nav-col-4"><h2 class="footer-nav-heading">Developers</h2><ul class="footer-nav-list"><li class="footer-nav-item"><a class="footer-nav-link" href="/en-US/docs/Web">Web Technologies</a></li><li class="footer-nav-item"><a class="footer-nav-link" href="/en-US/docs/Learn">Learn Web Development</a></li><li class="footer-nav-item"><a class="footer-nav-link" href="/en-US/plus">MDN Plus</a></li><li class="footer-nav-item"><a href="https://hacks.mozilla.org/" target="_blank" rel="noopener noreferrer">Hacks Blog</a></li></ul></div><div class="page-footer-moz"><a href="https://www.mozilla.org/" class="footer-moz-logo-link" target="_blank" rel="noopener noreferrer"><svg width="112" height="32" fill="none" xmlns="http://www.w3.org/2000/svg"><title id="mozilla-footer-logo-svg">Mozilla logo</title><path d="M41.753 14.218c-2.048 0-3.324 1.522-3.324 4.157 0 2.423 1.119 4.286 3.29 4.286 2.082 0 3.447-1.678 3.447-4.347 0-2.826-1.522-4.096-3.413-4.096Zm54.89 7.044c0 .901.437 1.618 1.645 1.618 1.427 0 2.949-1.024 3.044-3.352-.649-.095-1.365-.185-2.02-.185-1.426-.005-2.668.397-2.668 1.92Z" fill="currentColor"></path><path d="M0 0v32h111.908V0H0Zm32.56 25.426h-5.87v-7.884c0-2.423-.806-3.352-2.39-3.352-1.924 0-2.702 1.365-2.702 3.324v4.868h1.864v3.044h-5.864v-7.884c0-2.423-.806-3.352-2.39-3.352-1.924 0-2.702 1.365-2.702 3.324v4.868h2.669v3.044H6.642v-3.044h1.863v-7.918H6.642V11.42h5.864v2.11c.839-1.489 2.3-2.39 4.252-2.39 2.02 0 3.878.963 4.566 3.01.778-1.862 2.361-3.01 4.566-3.01 2.512 0 4.812 1.522 4.812 4.84v6.402h1.863v3.044h-.005Zm9.036.307c-4.314 0-7.296-2.635-7.296-7.106 0-4.096 2.484-7.481 7.514-7.481s7.481 3.38 7.481 7.29c0 4.472-3.228 7.297-7.699 7.297Zm22.578-.307H51.942l-.403-2.11 7.7-8.846h-4.376l-.621 2.17-2.888-.313.498-4.907h12.294l.313 2.11-7.767 8.852h4.533l.654-2.172 3.167.308-.872 4.908Zm7.99 0h-4.191v-5.03h4.19v5.03Zm0-8.976h-4.191v-5.03h4.19v5.03Zm2.618 8.976 6.054-21.358h3.945l-6.054 21.358h-3.945Zm8.136 0 6.048-21.358h3.945l-6.054 21.358h-3.939Zm21.486.307c-1.863 0-2.887-1.085-3.072-2.792-.805 1.427-2.232 2.792-4.498 2.792-2.02 0-4.314-1.085-4.314-4.006 0-3.447 3.323-4.253 6.518-4.253.778 0 1.584.034 2.3.124v-.465c0-1.427-.034-3.133-2.3-3.133-.84 0-1.488.061-2.143.402l-.453 1.578-3.195-.34.549-3.224c2.45-.996 3.692-1.27 5.992-1.27 3.01 0 5.556 1.55 5.556 4.75v6.083c0 .805.314 1.085.963 1.085.184 0 .375-.034.587-.095l.034 2.11a5.432 5.432 0 0 1-2.524.654Z" fill="currentColor"></path></svg></a><ul class="footer-moz-list"><li class="footer-moz-item"><a href="https://www.mozilla.org/privacy/websites/" class="footer-moz-link" target="_blank" rel="noopener noreferrer">Website Privacy Notice</a></li><li class="footer-moz-item"><a href="https://www.mozilla.org/privacy/websites/#cookies" class="footer-moz-link" target="_blank" rel="noopener noreferrer">Cookies</a></li><li class="footer-moz-item"><a href="https://www.mozilla.org/about/legal/terms/mozilla" class="footer-moz-link" target="_blank" rel="noopener noreferrer">Legal</a></li><li class="footer-moz-item"><a href="https://www.mozilla.org/about/governance/policies/participation/" class="footer-moz-link" target="_blank" rel="noopener noreferrer">Community Participation Guidelines</a></li></ul></div><div class="page-footer-legal"><p id="license" class="page-footer-legal-text">Visit<!-- --> <a href="https://www.mozilla.org" target="_blank" rel="noopener noreferrer">Mozilla Corporation’s</a> <!-- -->not-for-profit parent, the<!-- --> <a target="_blank" rel="noopener noreferrer" href="https://foundation.mozilla.org/">Mozilla Foundation</a>.<br/>Portions of this content are ©1998–<!-- -->2024<!-- --> by individual mozilla.org contributors. Content available under<!-- --> <a href="/en-US/docs/MDN/Writing_guidelines/Attrib_copyright_license">a Creative Commons license</a>.</p></div></div></footer></div><script type="application/json" id="hydration">{"url":"/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage","doc":{"isMarkdown":true,"isTranslated":false,"isActive":true,"flaws":{},"title":"Client-side storage","mdn_url":"/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage","locale":"en-US","native":"English (US)","sidebarHTML":"<ol><li class=\"section\"><a href=\"/en-US/docs/Learn/Getting_started_with_the_web\">Complete beginners start here!</a></li><li><details><summary>Getting started with the web</summary><ol><li><a href=\"/en-US/docs/Learn/Getting_started_with_the_web\">Getting started with the web</a></li><li><a href=\"/en-US/docs/Learn/Getting_started_with_the_web/Installing_basic_software\">Installing basic software</a></li><li><a href=\"/en-US/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like\">What will your website look like?</a></li><li><a href=\"/en-US/docs/Learn/Getting_started_with_the_web/Dealing_with_files\">Dealing with files</a></li><li><a href=\"/en-US/docs/Learn/Getting_started_with_the_web/HTML_basics\">HTML basics</a></li><li><a href=\"/en-US/docs/Learn/Getting_started_with_the_web/CSS_basics\">CSS basics</a></li><li><a href=\"/en-US/docs/Learn/Getting_started_with_the_web/JavaScript_basics\">JavaScript basics</a></li><li><a href=\"/en-US/docs/Learn/Getting_started_with_the_web/Publishing_your_website\">Publishing your website</a></li><li><a href=\"/en-US/docs/Learn/Getting_started_with_the_web/How_the_Web_works\">How the web works</a></li></ol></details></li><li class=\"section\"><a href=\"/en-US/docs/Learn/HTML\">HTML — Structuring the web</a></li><li><details><summary>Introduction to HTML</summary><ol><li><a href=\"/en-US/docs/Learn/HTML/Introduction_to_HTML\">Introduction to HTML</a></li><li><a href=\"/en-US/docs/Learn/HTML/Introduction_to_HTML/Getting_started\">Getting started with HTML</a></li><li><a href=\"/en-US/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML\">What's in the head? Metadata in HTML</a></li><li><a href=\"/en-US/docs/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals\">HTML text fundamentals</a></li><li><a href=\"/en-US/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks\">Creating hyperlinks</a></li><li><a href=\"/en-US/docs/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting\">Advanced text formatting</a></li><li><a href=\"/en-US/docs/Learn/HTML/Introduction_to_HTML/Document_and_website_structure\">Document and website structure</a></li><li><a href=\"/en-US/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML\">Debugging HTML</a></li><li><a href=\"/en-US/docs/Learn/HTML/Introduction_to_HTML/Marking_up_a_letter\">Marking up a letter</a></li><li><a href=\"/en-US/docs/Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content\">Structuring a page of content</a></li></ol></details></li><li><details><summary>Multimedia and embedding</summary><ol><li><a href=\"/en-US/docs/Learn/HTML/Multimedia_and_embedding\">Multimedia and embedding</a></li><li><a href=\"/en-US/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML\">Images in HTML</a></li><li><a href=\"/en-US/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content\">Video and audio content</a></li><li><a href=\"/en-US/docs/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies\">From object to iframe — other embedding technologies</a></li><li><a href=\"/en-US/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web\">Adding vector graphics to the web</a></li><li><a href=\"/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images\">Responsive images</a></li><li><a href=\"/en-US/docs/Learn/HTML/Multimedia_and_embedding/Mozilla_splash_page\">Mozilla splash page</a></li></ol></details></li><li><details><summary>HTML tables</summary><ol><li><a href=\"/en-US/docs/Learn/HTML/Tables\">HTML tables</a></li><li><a href=\"/en-US/docs/Learn/HTML/Tables/Basics\">HTML table basics</a></li><li><a href=\"/en-US/docs/Learn/HTML/Tables/Advanced\">HTML table advanced features and accessibility</a></li><li><a href=\"/en-US/docs/Learn/HTML/Tables/Structuring_planet_data\">Structuring planet data</a></li></ol></details></li><li class=\"section\"><a href=\"/en-US/docs/Learn/CSS\">CSS — Styling the web</a></li><li><details><summary>CSS first steps</summary><ol><li><a href=\"/en-US/docs/Learn/CSS/First_steps\">CSS first steps</a></li><li><a href=\"/en-US/docs/Learn/CSS/First_steps/What_is_CSS\">What is CSS?</a></li><li><a href=\"/en-US/docs/Learn/CSS/First_steps/Getting_started\">Getting started with CSS</a></li><li><a href=\"/en-US/docs/Learn/CSS/First_steps/How_CSS_is_structured\">How CSS is structured</a></li><li><a href=\"/en-US/docs/Learn/CSS/First_steps/How_CSS_works\">How CSS works</a></li><li><a href=\"/en-US/docs/Learn/CSS/First_steps/Styling_a_biography_page\">Styling a biography page</a></li></ol></details></li><li><details><summary>CSS building blocks</summary><ol><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks\">CSS building blocks</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Selectors\">CSS selectors</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors\">Type, class, and ID selectors</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors\">Attribute selectors</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements\">Pseudo-classes and pseudo-elements</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Selectors/Combinators\">Combinators</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance\">Cascade, specificity, and inheritance</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Cascade_layers\">Cascade layers</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/The_box_model\">The box model</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders\">Backgrounds and borders</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Handling_different_text_directions\">Handling different text directions</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Overflowing_content\">Overflowing content</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Values_and_units\">CSS values and units</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS\">Sizing items in CSS</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Images_media_form_elements\">Images, media, and form elements</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Styling_tables\">Styling tables</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Advanced_styling_effects\">Advanced styling effects</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Debugging_CSS\">Debugging CSS</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Organizing\">Organizing your CSS</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Fundamental_CSS_comprehension\">Fundamental CSS comprehension</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/Creating_fancy_letterheaded_paper\">Creating fancy letterheaded paper</a></li><li><a href=\"/en-US/docs/Learn/CSS/Building_blocks/A_cool_looking_box\">A cool-looking box</a></li></ol></details></li><li><details><summary>Styling text</summary><ol><li><a href=\"/en-US/docs/Learn/CSS/Styling_text\">CSS styling text</a></li><li><a href=\"/en-US/docs/Learn/CSS/Styling_text/Fundamentals\">Fundamental text and font styling</a></li><li><a href=\"/en-US/docs/Learn/CSS/Styling_text/Styling_lists\">Styling lists</a></li><li><a href=\"/en-US/docs/Learn/CSS/Styling_text/Styling_links\">Styling links</a></li><li><a href=\"/en-US/docs/Learn/CSS/Styling_text/Web_fonts\">Web fonts</a></li><li><a href=\"/en-US/docs/Learn/CSS/Styling_text/Typesetting_a_homepage\">Typesetting a community school homepage</a></li></ol></details></li><li><details><summary>CSS layout</summary><ol><li><a href=\"/en-US/docs/Learn/CSS/CSS_layout\">CSS layout</a></li><li><a href=\"/en-US/docs/Learn/CSS/CSS_layout/Introduction\">Introduction to CSS layout</a></li><li><a href=\"/en-US/docs/Learn/CSS/CSS_layout/Normal_Flow\">Normal Flow</a></li><li><a href=\"/en-US/docs/Learn/CSS/CSS_layout/Flexbox\">Flexbox</a></li><li><a href=\"/en-US/docs/Learn/CSS/CSS_layout/Grids\">Grids</a></li><li><a href=\"/en-US/docs/Learn/CSS/CSS_layout/Floats\">Floats</a></li><li><a href=\"/en-US/docs/Learn/CSS/CSS_layout/Positioning\">Positioning</a></li><li><a href=\"/en-US/docs/Learn/CSS/CSS_layout/Multiple-column_Layout\">Multiple-column layout</a></li><li><a href=\"/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design\">Responsive design</a></li><li><a href=\"/en-US/docs/Learn/CSS/CSS_layout/Media_queries\">Beginner's guide to media queries</a></li><li><a href=\"/en-US/docs/Learn/CSS/CSS_layout/Legacy_Layout_Methods\">Legacy layout methods</a></li><li><a href=\"/en-US/docs/Learn/CSS/CSS_layout/Supporting_Older_Browsers\">Supporting older browsers</a></li><li><a href=\"/en-US/docs/Learn/CSS/CSS_layout/Fundamental_Layout_Comprehension\">Fundamental layout comprehension</a></li></ol></details></li><li class=\"section\"><a href=\"/en-US/docs/Learn/JavaScript\">JavaScript — Dynamic client-side scripting</a></li><li><details><summary>JavaScript first steps</summary><ol><li><a href=\"/en-US/docs/Learn/JavaScript/First_steps\">JavaScript first steps</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/First_steps/What_is_JavaScript\">What is JavaScript?</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/First_steps/A_first_splash\">A first splash into JavaScript</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/First_steps/What_went_wrong\">What went wrong? Troubleshooting JavaScript</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/First_steps/Variables\">Storing the information you need — Variables</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/First_steps/Math\">Basic math in JavaScript — numbers and operators</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/First_steps/Strings\">Handling text — strings in JavaScript</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/First_steps/Useful_string_methods\">Useful string methods</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/First_steps/Arrays\">Arrays</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/First_steps/Silly_story_generator\">Silly story generator</a></li></ol></details></li><li><details><summary>JavaScript building blocks</summary><ol><li><a href=\"/en-US/docs/Learn/JavaScript/Building_blocks\">JavaScript building blocks</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Building_blocks/conditionals\">Making decisions in your code — conditionals</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Building_blocks/Looping_code\">Looping code</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Building_blocks/Functions\">Functions — reusable blocks of code</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Building_blocks/Build_your_own_function\">Build your own function</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Building_blocks/Return_values\">Function return values</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Building_blocks/Events\">Introduction to events</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Building_blocks/Event_bubbling\">Event bubbling</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Building_blocks/Image_gallery\">Image gallery</a></li></ol></details></li><li><details><summary>Introducing JavaScript objects</summary><ol><li><a href=\"/en-US/docs/Learn/JavaScript/Objects\">Introducing JavaScript objects</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Objects/Basics\">JavaScript object basics</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Objects/Object_prototypes\">Object prototypes</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Objects/Object-oriented_programming\">Object-oriented programming</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Objects/Classes_in_JavaScript\">Classes in JavaScript</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Objects/JSON\">Working with JSON</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Objects/Object_building_practice\">Object building practice</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Objects/Adding_bouncing_balls_features\">Adding features to our bouncing balls demo</a></li></ol></details></li><li><details><summary>Asynchronous JavaScript</summary><ol><li><a href=\"/en-US/docs/Learn/JavaScript/Asynchronous\">Asynchronous JavaScript</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Asynchronous/Introducing\">Introducing asynchronous JavaScript</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Asynchronous/Promises\">How to use promises</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Asynchronous/Implementing_a_promise-based_API\">How to implement a promise-based API</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Asynchronous/Introducing_workers\">Introducing workers</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Asynchronous/Sequencing_animations\">Sequencing animations</a></li></ol></details></li><li><details open=\"\"><summary>Client-side web APIs</summary><ol><li><a href=\"/en-US/docs/Learn/JavaScript/Client-side_web_APIs\">Client-side web APIs</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction\">Introduction to web APIs</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents\">Manipulating documents</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data\">Fetching data from the server</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs\">Third-party APIs</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics\">Drawing graphics</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs\">Video and Audio APIs</a></li><li><em><a href=\"/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage\" aria-current=\"page\">Client-side storage</a></em></li></ol></details></li><li class=\"section\"><a href=\"/en-US/docs/Learn/Forms\">Web forms — Working with user data</a></li><li><details><summary>Web form building blocks</summary><ol><li><a href=\"/en-US/docs/Learn/Forms\">Web form building blocks</a></li><li><a href=\"/en-US/docs/Learn/Forms/Your_first_form\">Your first form</a></li><li><a href=\"/en-US/docs/Learn/Forms/How_to_structure_a_web_form\">How to structure a web form</a></li><li><a href=\"/en-US/docs/Learn/Forms/Basic_native_form_controls\">Basic native form controls</a></li><li><a href=\"/en-US/docs/Learn/Forms/HTML5_input_types\">The HTML5 input types</a></li><li><a href=\"/en-US/docs/Learn/Forms/Other_form_controls\">Other form controls</a></li><li><a href=\"/en-US/docs/Learn/Forms/Styling_web_forms\">Styling web forms</a></li><li><a href=\"/en-US/docs/Learn/Forms/Advanced_form_styling\">Advanced form styling</a></li><li><a href=\"/en-US/docs/Learn/Forms/UI_pseudo-classes\">UI pseudo-classes</a></li><li><a href=\"/en-US/docs/Learn/Forms/Form_validation\">Client-side form validation</a></li><li><a href=\"/en-US/docs/Learn/Forms/Sending_and_retrieving_form_data\">Sending form data</a></li></ol></details></li><li><details><summary>Advanced web form techniques</summary><ol><li><a href=\"/en-US/docs/Learn/Forms/How_to_build_custom_form_controls\">How to build custom form controls</a></li><li><a href=\"/en-US/docs/Learn/Forms/Sending_forms_through_JavaScript\">Sending forms through JavaScript</a></li><li><a href=\"/en-US/docs/Learn/Forms/Property_compatibility_table_for_form_controls\">CSS property compatibility table for form controls</a></li><li><a href=\"/en-US/docs/Learn/Forms/HTML_forms_in_legacy_browsers\">HTML forms in legacy browsers</a></li></ol></details></li><li class=\"section\"><a href=\"/en-US/docs/Learn/Accessibility\">Accessibility — Make the web usable by everyone</a></li><li><details><summary>Accessibility guides</summary><ol><li><a href=\"/en-US/docs/Learn/Accessibility\">Accessibility</a></li><li><a href=\"/en-US/docs/Learn/Accessibility/What_is_accessibility\">What is accessibility?</a></li><li><a href=\"/en-US/docs/Learn/Accessibility/HTML\">HTML: A good basis for accessibility</a></li><li><a href=\"/en-US/docs/Learn/Accessibility/CSS_and_JavaScript\">CSS and JavaScript accessibility best practices</a></li><li><a href=\"/en-US/docs/Learn/Accessibility/WAI-ARIA_basics\">WAI-ARIA basics</a></li><li><a href=\"/en-US/docs/Learn/Accessibility/Multimedia\">Accessible multimedia</a></li><li><a href=\"/en-US/docs/Learn/Accessibility/Mobile\">Mobile accessibility</a></li><li><a href=\"/en-US/docs/Learn/Accessibility/Accessibility_troubleshooting\">Assessment: Accessibility troubleshooting</a></li></ol></details></li><li class=\"section\"><a href=\"/en-US/docs/Learn/Performance\">Performance — Making websites fast and responsive</a></li><li><details><summary>Performance guides</summary><ol><li><a href=\"/en-US/docs/Learn/Performance\">Web performance</a></li><li><a href=\"/en-US/docs/Learn/Performance/why_web_performance\">The \"why\" of web performance</a></li><li><a href=\"/en-US/docs/Learn/Performance/What_is_web_performance\">What is web performance?</a></li><li><a href=\"/en-US/docs/Learn/Performance/Perceived_performance\">Perceived performance</a></li><li><a href=\"/en-US/docs/Learn/Performance/Measuring_performance\">Measuring performance</a></li><li><a href=\"/en-US/docs/Learn/Performance/Multimedia\">Multimedia: Images</a></li><li><a href=\"/en-US/docs/Learn/Performance/video\">Multimedia: video</a></li><li><a href=\"/en-US/docs/Learn/Performance/JavaScript\">JavaScript performance optimization</a></li><li><a href=\"/en-US/docs/Learn/Performance/HTML\">HTML performance optimization</a></li><li><a href=\"/en-US/docs/Learn/Performance/CSS\">CSS performance optimization</a></li><li><a href=\"/en-US/docs/Learn/Performance/business_case_for_performance\">The business case for web performance</a></li></ol></details></li><li class=\"section\"><a href=\"/en-US/docs/Learn/MathML\">MathML — Writing mathematics with MathML</a></li><li><details><summary>MathML first steps</summary><ol><li><a href=\"/en-US/docs/Learn/MathML/First_steps\">MathML first steps</a></li><li><a href=\"/en-US/docs/Learn/MathML/First_steps/Getting_started\">Getting started with MathML</a></li><li><a href=\"/en-US/docs/Learn/MathML/First_steps/Text_containers\">MathML Text Containers</a></li><li><a href=\"/en-US/docs/Learn/MathML/First_steps/Fractions_and_roots\">MathML fractions and roots</a></li><li><a href=\"/en-US/docs/Learn/MathML/First_steps/Scripts\">MathML scripted elements</a></li><li><a href=\"/en-US/docs/Learn/MathML/First_steps/Tables\">MathML tables</a></li><li><a href=\"/en-US/docs/Learn/MathML/First_steps/Three_famous_mathematical_formulas\">Three famous mathematical formulas</a></li></ol></details></li><li class=\"section\"><a href=\"/en-US/docs/Learn/../Games\">Games — Developing games for the web</a></li><li><details><summary>Guides and tutorials</summary><ol><li><a href=\"/en-US/docs/Games/Introduction\">Introduction to game development for the Web</a></li><li><a href=\"/en-US/docs/Games/Techniques\">Techniques for game development</a></li><li><a href=\"/en-US/docs/Games/Tutorials\">Tutorials</a></li><li><a href=\"/en-US/docs/Games/Publishing_games\">Publishing games</a></li></ol></details></li><li class=\"section\"><a href=\"/en-US/docs/Learn/Tools_and_testing\">Tools and testing</a></li><li><details><summary>Client-side web development tools</summary><ol><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools\">Understanding client-side web development tools</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview\">Client-side tooling overview</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line\">Command line crash course</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management\">Package management basics</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Introducing_complete_toolchain\">Introducing a complete toolchain</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Deployment\">Deploying our app</a></li></ol></details></li><li><details><summary>Introduction to client-side frameworks</summary><ol><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction\">Introduction to client-side frameworks</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features\">Framework main features</a></li></ol></details></li><li><details><summary>React</summary><ol><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started\">Getting started with React</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning\">Beginning our React todo list</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components\">Componentizing our React app</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state\">React interactivity: Events and state</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering\">React interactivity: Editing, filtering, conditional rendering</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility\">Accessibility in React</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources\">React resources</a></li></ol></details></li><li><details><summary>Ember</summary><ol><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started\">Getting started with Ember</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization\">Ember app structure and componentization</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state\">Ember interactivity: Events, classes and state</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer\">Ember Interactivity: Footer functionality, conditional rendering</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing\">Routing in Ember</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources\">Ember resources and troubleshooting</a></li></ol></details></li><li><details><summary>Vue</summary><ol><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started\">Getting started with Vue</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component\">Creating our first Vue component</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists\">Rendering a list of Vue components</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models\">Adding a new todo form: Vue events, methods, and models</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling\">Styling Vue components with CSS</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties\">Using Vue computed properties</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering\">Vue conditional rendering: editing existing todos</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management\">Vue refs and lifecycle methods for focus management</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources\">Vue resources</a></li></ol></details></li><li><details><summary>Svelte</summary><ol><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started\">Getting started with Svelte</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning\">Starting our Svelte to-do list app</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props\">Dynamic behavior in Svelte: working with variables and props</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components\">Componentizing our Svelte app</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility\">Advanced Svelte: Reactivity, lifecycle, accessibility</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores\">Working with Svelte stores</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript\">TypeScript support in Svelte</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next\">Deployment and next steps</a></li></ol></details></li><li><details><summary>Angular</summary><ol><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_getting_started\">Getting started with Angular</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_todo_list_beginning\">Beginning our Angular todo list app</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_styling\">Styling our Angular app</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_item_component\">Creating an item component</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_filtering\">Filtering our to-do items</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_building\">Building Angular applications and further resources</a></li></ol></details></li><li><details><summary>Git and GitHub</summary><ol><li><a href=\"/en-US/docs/Learn/Tools_and_testing/GitHub\">Git and GitHub</a></li></ol></details></li><li><details><summary>Cross browser testing</summary><ol><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing\">Cross browser testing</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction\">Introduction to cross-browser testing</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies\">Strategies for carrying out testing</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS\">Handling common HTML and CSS problems</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript\">Handling common JavaScript problems</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility\">Handling common accessibility problems</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection\">Implementing feature detection</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing\">Introduction to automated testing</a></li><li><a href=\"/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment\">Setting up your own test automation environment</a></li></ol></details></li><li class=\"section\"><a href=\"/en-US/docs/Learn/Server-side\">Server-side website programming</a></li><li><details><summary>First steps</summary><ol><li><a href=\"/en-US/docs/Learn/Server-side/First_steps\">Server-side website programming first steps</a></li><li><a href=\"/en-US/docs/Learn/Server-side/First_steps/Introduction\">Introduction to the server side</a></li><li><a href=\"/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview\">Client-Server Overview</a></li><li><a href=\"/en-US/docs/Learn/Server-side/First_steps/Web_frameworks\">Server-side web frameworks</a></li><li><a href=\"/en-US/docs/Learn/Server-side/First_steps/Website_security\">Website security</a></li></ol></details></li><li><details><summary>Django web framework (Python)</summary><ol><li><a href=\"/en-US/docs/Learn/Server-side/Django\">Django Web Framework (Python)</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Django/Introduction\">Django introduction</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Django/development_environment\">Setting up a Django development environment</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website\">Django Tutorial: The Local Library website</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Django/skeleton_website\">Django Tutorial Part 2: Creating a skeleton website</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Django/Models\">Django Tutorial Part 3: Using models</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Django/Admin_site\">Django Tutorial Part 4: Django admin site</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Django/Home_page\">Django Tutorial Part 5: Creating our home page</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Django/Generic_views\">Django Tutorial Part 6: Generic list and detail views</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Django/Sessions\">Django Tutorial Part 7: Sessions framework</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Django/Authentication\">Django Tutorial Part 8: User authentication and permissions</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Django/Forms\">Django Tutorial Part 9: Working with forms</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Django/Testing\">Django Tutorial Part 10: Testing a Django web application</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Django/Deployment\">Django Tutorial Part 11: Deploying Django to production</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Django/web_application_security\">Django web application security</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Django/django_assessment_blog\">Assessment: DIY Django mini blog</a></li></ol></details></li><li><details><summary>Express Web Framework (Node.js/JavaScript)</summary><ol><li><a href=\"/en-US/docs/Learn/Server-side/Express_Nodejs\">Express web framework (Node.js/JavaScript)</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction\">Express/Node introduction</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment\">Setting up a Node development environment</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website\">Express Tutorial: The Local Library website</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website\">Express Tutorial Part 2: Creating a skeleton website</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose\">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Express_Nodejs/routes\">Express Tutorial Part 4: Routes and controllers</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data\">Express Tutorial Part 5: Displaying library data</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Express_Nodejs/forms\">Express Tutorial Part 6: Working with forms</a></li><li><a href=\"/en-US/docs/Learn/Server-side/Express_Nodejs/deployment\">Express Tutorial Part 7: Deploying to production</a></li></ol></details></li><li class=\"section\"><a href=\"/en-US/docs/Learn/Common_questions\">Further resources</a></li><li><details><summary>Common questions</summary><ol><li><a href=\"/en-US/docs/Learn/Common_questions\">Common questions</a></li><li><a href=\"/en-US/docs/Learn/HTML/Howto\">Use HTML to solve common problems</a></li><li><a href=\"/en-US/docs/Learn/CSS/Howto\">Use CSS to solve common problems</a></li><li><a href=\"/en-US/docs/Learn/JavaScript/Howto\">Solve common problems in your JavaScript code</a></li><li><a href=\"/en-US/docs/Learn/Common_questions/Web_mechanics\">Web mechanics</a></li><li><a href=\"/en-US/docs/Learn/Common_questions/Tools_and_setup\">Tools and setup</a></li><li><a href=\"/en-US/docs/Learn/Common_questions/Design_and_accessibility\">Design and accessibility</a></li></ol></details></li></ol>","sidebarMacro":"LearnSidebar","body":[{"type":"prose","value":{"id":null,"title":null,"isH3":false,"content":"<ul class=\"prev-next\">\n <li><a class=\"button secondary\" href=\"/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs\"><span class=\"button-wrap\"> Previous </span></a></li>\n <li><a class=\"button secondary\" href=\"/en-US/docs/Learn/JavaScript/Client-side_web_APIs\"><span class=\"button-wrap\"> Overview: Client-side web APIs</span></a></li>\n \n</ul>\n<p>Modern web browsers support a number of ways for websites to store data on the user's computer — with the user's permission — then retrieve it when necessary. This lets you persist data for long-term storage, save sites or documents for offline use, retain user-specific settings for your site, and more. This article explains the very basics of how these work.</p>\n<figure class=\"table-container\"><table>\n <tbody>\n <tr>\n <th scope=\"row\">Prerequisites:</th>\n <td>\n JavaScript basics (see\n <a href=\"/en-US/docs/Learn/JavaScript/First_steps\">first steps</a>,\n <a href=\"/en-US/docs/Learn/JavaScript/Building_blocks\">building blocks</a>,\n <a href=\"/en-US/docs/Learn/JavaScript/Objects\">JavaScript objects</a>),\n the\n <a href=\"/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction\">basics of Client-side APIs</a>\n </td>\n </tr>\n <tr>\n <th scope=\"row\">Objective:</th>\n <td>To learn how to use client-side storage APIs to store application data.</td>\n </tr>\n </tbody>\n</table></figure>"}},{"type":"prose","value":{"id":"client-side_storage","title":"Client-side storage?","isH3":false,"content":"<p>Elsewhere in the MDN learning area, we talked about the difference between <a href=\"/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview#static_sites\">static sites</a> and <a href=\"/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview#dynamic_sites\">dynamic sites</a>. Most major modern websites are dynamic — they store data on the server using some kind of database (server-side storage), then run <a href=\"/en-US/docs/Learn/Server-side\">server-side</a> code to retrieve needed data, insert it into static page templates, and serve the resulting HTML to the client to be displayed by the user's browser.</p>\n<p>Client-side storage works on similar principles, but has different uses. It consists of JavaScript APIs that allow you to store data on the client (i.e. on the user's machine) and then retrieve it when needed. This has many distinct uses, such as:</p>\n<ul>\n <li>Personalizing site preferences (e.g. showing a user's choice of custom widgets, color scheme, or font size).</li>\n <li>Persisting previous site activity (e.g. storing the contents of a shopping cart from a previous session, remembering if a user was previously logged in).</li>\n <li>Saving data and assets locally so a site will be quicker (and potentially less expensive) to download, or be usable without a network connection.</li>\n <li>Saving web application generated documents locally for use offline</li>\n</ul>\n<p>Often client-side and server-side storage are used together. For example, you could download a batch of music files (perhaps used by a web game or music player application), store them inside a client-side database, and play them as needed. The user would only have to download the music files once — on subsequent visits they would be retrieved from the database instead.</p>\n<div class=\"notecard note\">\n <p><strong>Note:</strong> There are limits to the amount of data you can store using client-side storage APIs (possibly both per individual API and cumulatively); the exact limit varies depending on the browser and possibly based on user settings. See <a href=\"/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria\">Browser storage quotas and eviction criteria</a> for more information.</p>\n</div>"}},{"type":"prose","value":{"id":"old_school_cookies","title":"Old school: Cookies","isH3":true,"content":"<p>The concept of client-side storage has been around for a long time. Since the early days of the web, sites have used <a href=\"/en-US/docs/Web/HTTP/Cookies\">cookies</a> to store information to personalize user experience on websites. They're the earliest form of client-side storage commonly used on the web.</p>\n<p>These days, there are easier mechanisms available for storing client-side data, therefore we won't be teaching you how to use cookies in this article. However, this does not mean cookies are completely useless on the modern-day web — they are still used commonly to store data related to user personalization and state, e.g. session IDs and access tokens. For more information on cookies see our <a href=\"/en-US/docs/Web/HTTP/Cookies\">Using HTTP cookies</a> article.</p>"}},{"type":"prose","value":{"id":"new_school_web_storage_and_indexeddb","title":"New school: Web Storage and IndexedDB","isH3":true,"content":"<p>The \"easier\" features we mentioned above are as follows:</p>\n<ul>\n <li>The <a href=\"/en-US/docs/Web/API/Web_Storage_API\">Web Storage API</a> provides a mechanism for storing and retrieving smaller, data items consisting of a name and a corresponding value. This is useful when you just need to store some simple data, like the user's name, whether they are logged in, what color to use for the background of the screen, etc.</li>\n <li>The <a href=\"/en-US/docs/Web/API/IndexedDB_API\">IndexedDB API</a> provides the browser with a complete database system for storing complex data. This can be used for things from complete sets of customer records to even complex data types like audio or video files.</li>\n</ul>\n<p>You'll learn more about these APIs below.</p>"}},{"type":"prose","value":{"id":"the_cache_api","title":"The Cache API","isH3":true,"content":"<p>The <a href=\"/en-US/docs/Web/API/Cache\"><code>Cache</code></a> API is designed for storing HTTP responses to specific requests, and is very useful for doing things like storing website assets offline so the site can subsequently be used without a network connection. Cache is usually used in combination with the <a href=\"/en-US/docs/Web/API/Service_Worker_API\">Service Worker API</a>, although it doesn't have to be.</p>\n<p>The use of Cache and Service Workers is an advanced topic, and we won't be covering it in great detail in this article, although we will show an example in the <a href=\"#offline_asset_storage\">Offline asset storage</a> section below.</p>"}},{"type":"prose","value":{"id":"storing_simple_data_—_web_storage","title":"Storing simple data — web storage","isH3":false,"content":"<p>The <a href=\"/en-US/docs/Web/API/Web_Storage_API\">Web Storage API</a> is very easy to use — you store simple name/value pairs of data (limited to strings, numbers, etc.) and retrieve these values when needed.</p>"}},{"type":"prose","value":{"id":"basic_syntax","title":"Basic syntax","isH3":true,"content":"<p>Let's show you how:</p>\n<ol>\n <li>\n <p>First, go to our <a href=\"https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/index.html\" class=\"external\" target=\"_blank\">web storage blank template</a> on GitHub (open this in a new tab).</p>\n </li>\n <li>\n <p>Open the JavaScript console of your browser's developer tools.</p>\n </li>\n <li>\n <p>All of your web storage data is contained within two object-like structures inside the browser: <a href=\"/en-US/docs/Web/API/Window/sessionStorage\" title=\"sessionStorage\"><code>sessionStorage</code></a> and <a href=\"/en-US/docs/Web/API/Window/localStorage\" title=\"localStorage\"><code>localStorage</code></a>. The first one persists data for as long as the browser is open (the data is lost when the browser is closed) and the second one persists data even after the browser is closed and then opened again. We'll use the second one in this article as it is generally more useful.</p>\n <p>The <a href=\"/en-US/docs/Web/API/Storage/setItem\"><code>Storage.setItem()</code></a> method allows you to save a data item in storage — it takes two parameters: the name of the item, and its value. Try typing this into your JavaScript console (change the value to your own name, if you wish!):</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>localStorage.setItem(\"name\", \"Chris\");\n</code></pre></div>\n </li>\n <li>\n <p>The <a href=\"/en-US/docs/Web/API/Storage/getItem\"><code>Storage.getItem()</code></a> method takes one parameter — the name of a data item you want to retrieve — and returns the item's value. Now type these lines into your JavaScript console:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>let myName = localStorage.getItem(\"name\");\nmyName;\n</code></pre></div>\n <p>Upon typing in the second line, you should see that the <code>myName</code> variable now contains the value of the <code>name</code> data item.</p>\n </li>\n <li>\n <p>The <a href=\"/en-US/docs/Web/API/Storage/removeItem\"><code>Storage.removeItem()</code></a> method takes one parameter — the name of a data item you want to remove — and removes that item out of web storage. Type the following lines into your JavaScript console:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>localStorage.removeItem(\"name\");\nmyName = localStorage.getItem(\"name\");\nmyName;\n</code></pre></div>\n <p>The third line should now return <code>null</code> — the <code>name</code> item no longer exists in the web storage.</p>\n </li>\n</ol>"}},{"type":"prose","value":{"id":"the_data_persists!","title":"The data persists!","isH3":true,"content":"<p>One key feature of web storage is that the data persists between page loads (and even when the browser is shut down, in the case of <code>localStorage</code>). Let's look at this in action.</p>\n<ol>\n <li>\n <p>Open our web storage blank template again, but this time in a different browser to the one you've got this tutorial open in! This will make it easier to deal with.</p>\n </li>\n <li>\n <p>Type these lines into the browser's JavaScript console:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>localStorage.setItem(\"name\", \"Chris\");\nlet myName = localStorage.getItem(\"name\");\nmyName;\n</code></pre></div>\n <p>You should see the name item returned.</p>\n </li>\n <li>\n <p>Now close down the browser and open it up again.</p>\n </li>\n <li>\n <p>Enter the following lines again:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>let myName = localStorage.getItem(\"name\");\nmyName;\n</code></pre></div>\n <p>You should see that the value is still available, even though the browser has been closed and then opened again.</p>\n </li>\n</ol>"}},{"type":"prose","value":{"id":"separate_storage_for_each_domain","title":"Separate storage for each domain","isH3":true,"content":"<p>There is a separate data store for each domain (each separate web address loaded in the browser). You will see that if you load two websites (say google.com and amazon.com) and try storing an item on one website, it won't be available to the other website.</p>\n<p>This makes sense — you can imagine the security issues that would arise if websites could see each other's data!</p>"}},{"type":"prose","value":{"id":"a_more_involved_example","title":"A more involved example","isH3":true,"content":"<p>Let's apply this new-found knowledge by writing a working example to give you an idea of how web storage can be used. Our example will allow you to enter a name, after which the page will update to give you a personalized greeting. This state will also persist across page/browser reloads, because the name is stored in web storage.</p>\n<p>You can find the example HTML at <a href=\"https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/web-storage/personal-greeting.html\" class=\"external\" target=\"_blank\">personal-greeting.html</a> — this contains a website with a header, content, and footer, and a form for entering your name.</p>\n<p>\n <img src=\"/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/web-storage-demo.png\" alt=\"A Screenshot of a website that has a header, content and footer sections. The header has a welcome text to the left-hand side and a button labelled 'forget' to the right-hand side. The content has an heading followed by a two paragraphs of dummy text. The footer reads 'Copyright nobody. Use the code as you like'.\" width=\"1024\" height=\"550\" loading=\"lazy\">\n</p>\n<p>Let's build up the example, so you can understand how it works.</p>\n<ol>\n <li>\n <p>First, make a local copy of our <a href=\"https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/web-storage/personal-greeting.html\" class=\"external\" target=\"_blank\">personal-greeting.html</a> file in a new directory on your computer.</p>\n </li>\n <li>\n <p>Next, note how our HTML references a JavaScript file called <code>index.js</code>, with a line like <code><script src=\"index.js\" defer></script></code>. We need to create this and write our JavaScript code into it. Create an <code>index.js</code> file in the same directory as your HTML file.</p>\n </li>\n <li>\n <p>We'll start off by creating references to all the HTML features we need to manipulate in this example — we'll create them all as constants, as these references do not need to change in the lifecycle of the app. Add the following lines to your JavaScript file:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// create needed constants\nconst rememberDiv = document.querySelector(\".remember\");\nconst forgetDiv = document.querySelector(\".forget\");\nconst form = document.querySelector(\"form\");\nconst nameInput = document.querySelector(\"#entername\");\nconst submitBtn = document.querySelector(\"#submitname\");\nconst forgetBtn = document.querySelector(\"#forgetname\");\n\nconst h1 = document.querySelector(\"h1\");\nconst personalGreeting = document.querySelector(\".personal-greeting\");\n</code></pre></div>\n </li>\n <li>\n <p>Next up, we need to include a small event listener to stop the form from actually submitting itself when the submit button is pressed, as this is not the behavior we want. Add this snippet below your previous code:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// Stop the form from submitting when a button is pressed\nform.addEventListener(\"submit\", (e) => e.preventDefault());\n</code></pre></div>\n </li>\n <li>\n <p>Now we need to add an event listener, the handler function of which will run when the \"Say hello\" button is clicked. The comments explain in detail what each bit does, but in essence here we are taking the name the user has entered into the text input box and saving it in web storage using <code>setItem()</code>, then running a function called <code>nameDisplayCheck()</code> that will handle updating the actual website text. Add this to the bottom of your code:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// run function when the 'Say hello' button is clicked\nsubmitBtn.addEventListener(\"click\", () => {\n // store the entered name in web storage\n localStorage.setItem(\"name\", nameInput.value);\n // run nameDisplayCheck() to sort out displaying the personalized greetings and updating the form display\n nameDisplayCheck();\n});\n</code></pre></div>\n </li>\n <li>\n <p>At this point we also need an event handler to run a function when the \"Forget\" button is clicked — this is only displayed after the \"Say hello\" button has been clicked (the two form states toggle back and forth). In this function we remove the <code>name</code> item from web storage using <code>removeItem()</code>, then again run <code>nameDisplayCheck()</code> to update the display. Add this to the bottom:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// run function when the 'Forget' button is clicked\nforgetBtn.addEventListener(\"click\", () => {\n // Remove the stored name from web storage\n localStorage.removeItem(\"name\");\n // run nameDisplayCheck() to sort out displaying the generic greeting again and updating the form display\n nameDisplayCheck();\n});\n</code></pre></div>\n </li>\n <li>\n <p>It is now time to define the <code>nameDisplayCheck()</code> function itself. Here we check whether the name item has been stored in web storage by using <code>localStorage.getItem('name')</code> as a conditional test. If the name has been stored, this call will evaluate to <code>true</code>; if not, the call will evaluate to <code>false</code>. If the call evaluates to <code>true</code>, we display a personalized greeting, display the \"forget\" part of the form, and hide the \"Say hello\" part of the form. If the call evaluates to <code>false</code>, we display a generic greeting and do the opposite. Again, put the following code at the bottom:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// define the nameDisplayCheck() function\nfunction nameDisplayCheck() {\n // check whether the 'name' data item is stored in web Storage\n if (localStorage.getItem(\"name\")) {\n // If it is, display personalized greeting\n const name = localStorage.getItem(\"name\");\n h1.textContent = `Welcome, ${name}`;\n personalGreeting.textContent = `Welcome to our website, ${name}! We hope you have fun while you are here.`;\n // hide the 'remember' part of the form and show the 'forget' part\n forgetDiv.style.display = \"block\";\n rememberDiv.style.display = \"none\";\n } else {\n // if not, display generic greeting\n h1.textContent = \"Welcome to our website \";\n personalGreeting.textContent =\n \"Welcome to our website. We hope you have fun while you are here.\";\n // hide the 'forget' part of the form and show the 'remember' part\n forgetDiv.style.display = \"none\";\n rememberDiv.style.display = \"block\";\n }\n}\n</code></pre></div>\n </li>\n <li>\n <p>Last but not least, we need to run the <code>nameDisplayCheck()</code> function when the page is loaded. If we don't do this, then the personalized greeting will not persist across page reloads. Add the following to the bottom of your code:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>nameDisplayCheck();\n</code></pre></div>\n </li>\n</ol>\n<p>Your example is finished — well done! All that remains now is to save your code and test your HTML page in a browser. You can see our <a href=\"https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/personal-greeting.html\" class=\"external\" target=\"_blank\">finished version running live here</a>.</p>\n<div class=\"notecard note\">\n <p><strong>Note:</strong> There is another, slightly more complex example to explore at <a href=\"/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API\">Using the Web Storage API</a>.</p>\n</div>\n<div class=\"notecard note\">\n <p><strong>Note:</strong> In the line <code><script src=\"index.js\" defer></script></code> of the source for our finished version, the <code>defer</code> attribute specifies that the contents of the <a href=\"/en-US/docs/Web/HTML/Element/script\"><code><script></code></a> element will not execute until the page has finished loading.</p>\n</div>"}},{"type":"prose","value":{"id":"storing_complex_data_—_indexeddb","title":"Storing complex data — IndexedDB","isH3":false,"content":"<p>The <a href=\"/en-US/docs/Web/API/IndexedDB_API\">IndexedDB API</a> (sometimes abbreviated IDB) is a complete database system available in the browser in which you can store complex related data, the types of which aren't limited to simple values like strings or numbers. You can store videos, images, and pretty much anything else in an IndexedDB instance.</p>\n<p>\n The IndexedDB API allows you to create a database, then create object stores within that database.\n Object stores are like tables in a relational database, and each object store can contain a number of objects.\n To learn more about the IndexedDB API, see <a href=\"/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB\">Using IndexedDB</a>.\n</p>\n<p>However, this does come at a cost: IndexedDB is much more complex to use than the Web Storage API. In this section, we'll really only scratch the surface of what it is capable of, but we will give you enough to get started.</p>"}},{"type":"prose","value":{"id":"working_through_a_note_storage_example","title":"Working through a note storage example","isH3":true,"content":"<p>Here we'll run you through an example that allows you to store notes in your browser and view and delete them whenever you like, getting you to build it up for yourself and explaining the most fundamental parts of IDB as we go along.</p>\n<p>The app looks something like this:</p>\n<p>\n <img src=\"/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/idb-demo.png\" alt=\"IndexDB notes demo screenshot with 4 sections. The first section is the header. The second section lists all the notes that have been created. It has two notes, each with a delete button. A third section is a form with 2 input fields for 'Note title' and 'Note text' and a button labeled 'Create new note'. The bottom section footer reads 'Copyright nobody. Use the code as you like'.\" width=\"803\" height=\"736\" loading=\"lazy\">\n</p>\n<p>Each note has a title and some body text, each individually editable. The JavaScript code we'll go through below has detailed comments to help you understand what's going on.</p>"}},{"type":"prose","value":{"id":"getting_started","title":"Getting started","isH3":true,"content":"<ol>\n <li>First of all, make local copies of our <a href=\"https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/index.html\" class=\"external\" target=\"_blank\"><code>index.html</code></a>, <a href=\"https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/style.css\" class=\"external\" target=\"_blank\"><code>style.css</code></a>, and <a href=\"https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/index-start.js\" class=\"external\" target=\"_blank\"><code>index-start.js</code></a> files into a new directory on your local machine.</li>\n <li>Have a look at the files. You'll see that the HTML defines a website with a header and footer, as well as a main content area that contains a place to display notes, and a form for entering new notes into the database. The CSS provides some styling to make it clearer what is going on. The JavaScript file contains five declared constants containing references to the <a href=\"/en-US/docs/Web/HTML/Element/ul\"><code><ul></code></a> element the notes will be displayed in, the title and body <a href=\"/en-US/docs/Web/HTML/Element/input\"><code><input></code></a> elements, the <a href=\"/en-US/docs/Web/HTML/Element/form\"><code><form></code></a> itself, and the <a href=\"/en-US/docs/Web/HTML/Element/button\"><code><button></code></a>.</li>\n <li>Rename your JavaScript file to <code>index.js</code>. You are now ready to start adding code to it.</li>\n</ol>"}},{"type":"prose","value":{"id":"database_initial_setup","title":"Database initial setup","isH3":true,"content":"<p>Now let's look at what we have to do in the first place, to actually set up a database.</p>\n<ol>\n <li>\n <p>Below the constant declarations, add the following lines:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// Create an instance of a db object for us to store the open database in\nlet db;\n</code></pre></div>\n <p>Here we are declaring a variable called <code>db</code> — this will later be used to store an object representing our database. We will use this in a few places, so we've declared it globally here to make things easier.</p>\n </li>\n <li>\n <p>Next, add the following:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// Open our database; it is created if it doesn't already exist\n// (see the upgradeneeded handler below)\nconst openRequest = window.indexedDB.open(\"notes_db\", 1);\n</code></pre></div>\n <p>This line creates a request to open version <code>1</code> of a database called <code>notes_db</code>. If this doesn't already exist, it will be created for you by subsequent code. You will see this request pattern used very often throughout IndexedDB. Database operations take time. You don't want to hang the browser while you wait for the results, so database operations are <a href=\"/en-US/docs/Glossary/Asynchronous\">asynchronous</a>, meaning that instead of happening immediately, they will happen at some point in the future, and you get notified when they're done.</p>\n <p>To handle this in IndexedDB, you create a request object (which can be called anything you like — we called it <code>openRequest</code> here, so it is obvious what it is for). You then use event handlers to run code when the request completes, fails, etc., which you'll see in use below.</p>\n <div class=\"notecard note\">\n <p><strong>Note:</strong> The version number is important. If you want to upgrade your database (for example, by changing the table structure), you have to run your code again with an increased version number, different schema specified inside the <code>upgradeneeded</code> handler (see below), etc. We won't cover upgrading databases in this tutorial.</p>\n </div>\n </li>\n <li>\n <p>Now add the following event handlers just below your previous addition:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// error handler signifies that the database didn't open successfully\nopenRequest.addEventListener(\"error\", () =>\n console.error(\"Database failed to open\"),\n);\n\n// success handler signifies that the database opened successfully\nopenRequest.addEventListener(\"success\", () => {\n console.log(\"Database opened successfully\");\n\n // Store the opened database object in the db variable. This is used a lot below\n db = openRequest.result;\n\n // Run the displayData() function to display the notes already in the IDB\n displayData();\n});\n</code></pre></div>\n <p>The <a href=\"/en-US/docs/Web/API/IDBRequest/error_event\" title=\"error\"><code>error</code></a> event handler will run if the system comes back saying that the request failed. This allows you to respond to this problem. In our example, we just print a message to the JavaScript console.</p>\n <p>The <a href=\"/en-US/docs/Web/API/IDBRequest/success_event\" title=\"success\"><code>success</code></a> event handler will run if the request returns successfully, meaning the database was successfully opened. If this is the case, an object representing the opened database becomes available in the <a href=\"/en-US/docs/Web/API/IDBRequest/result\" title=\"openRequest.result\"><code>openRequest.result</code></a> property, allowing us to manipulate the database. We store this in the <code>db</code> variable we created earlier for later use. We also run a function called <code>displayData()</code>, which displays the data in the database inside the <a href=\"/en-US/docs/Web/HTML/Element/ul\"><code><ul></code></a>. We run it now so that the notes already in the database are displayed as soon as the page loads. You'll see <code>displayData()</code> defined later on.</p>\n </li>\n <li>\n <p>Finally for this section, we'll add probably the most important event handler for setting up the database: <a href=\"/en-US/docs/Web/API/IDBOpenDBRequest/upgradeneeded_event\" title=\"upgradeneeded\"><code>upgradeneeded</code></a>. This handler runs if the database has not already been set up, or if the database is opened with a bigger version number than the existing stored database (when performing an upgrade). Add the following code, below your previous handler:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// Set up the database tables if this has not already been done\nopenRequest.addEventListener(\"upgradeneeded\", (e) => {\n // Grab a reference to the opened database\n db = e.target.result;\n\n // Create an objectStore in our database to store notes and an auto-incrementing key\n // An objectStore is similar to a 'table' in a relational database\n const objectStore = db.createObjectStore(\"notes_os\", {\n keyPath: \"id\",\n autoIncrement: true,\n });\n\n // Define what data items the objectStore will contain\n objectStore.createIndex(\"title\", \"title\", { unique: false });\n objectStore.createIndex(\"body\", \"body\", { unique: false });\n\n console.log(\"Database setup complete\");\n});\n</code></pre></div>\n <p>This is where we define the schema (structure) of our database; that is, the set of columns (or fields) it contains. Here we first grab a reference to the existing database from the <code>result</code> property of the event's target (<code>e.target.result</code>), which is the <code>request</code> object. This is equivalent to the line <code>db = openRequest.result;</code> inside the <code>success</code> event handler, but we need to do this separately here because the <code>upgradeneeded</code> event handler (if needed) will run before the <code>success</code> event handler, meaning that the <code>db</code> value wouldn't be available if we didn't do this.</p>\n <p>We then use <a href=\"/en-US/docs/Web/API/IDBDatabase/createObjectStore\"><code>IDBDatabase.createObjectStore()</code></a> to create a new object store inside our opened database called <code>notes_os</code>. This is equivalent to a single table in a conventional database system. We've given it the name notes, and also specified an <code>autoIncrement</code> key field called <code>id</code> — in each new record this will automatically be given an incremented value — the developer doesn't need to set this explicitly. Being the key, the <code>id</code> field will be used to uniquely identify records, such as when deleting or displaying a record.</p>\n <p>We also create two other indexes (fields) using the <a href=\"/en-US/docs/Web/API/IDBObjectStore/createIndex\"><code>IDBObjectStore.createIndex()</code></a> method: <code>title</code> (which will contain a title for each note), and <code>body</code> (which will contain the body text of the note).</p>\n </li>\n</ol>\n<p>So with this database schema set up, when we start adding records to the database, each one will be represented as an object along these lines:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">json</span></div><pre class=\"brush: json notranslate\"><code>{\n \"title\": \"Buy milk\",\n \"body\": \"Need both cows milk and soy.\",\n \"id\": 8\n}\n</code></pre></div>"}},{"type":"prose","value":{"id":"adding_data_to_the_database","title":"Adding data to the database","isH3":true,"content":"<p>Now let's look at how we can add records to the database. This will be done using the form on our page.</p>\n<p>Below your previous event handler, add the following line, which sets up a <code>submit</code> event handler that runs a function called <code>addData()</code> when the form is submitted (when the submit <a href=\"/en-US/docs/Web/HTML/Element/button\"><code><button></code></a> is pressed leading to a successful form submission):</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// Create a submit event handler so that when the form is submitted the addData() function is run\nform.addEventListener(\"submit\", addData);\n</code></pre></div>\n<p>Now let's define the <code>addData()</code> function. Add this below your previous line:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// Define the addData() function\nfunction addData(e) {\n // prevent default - we don't want the form to submit in the conventional way\n e.preventDefault();\n\n // grab the values entered into the form fields and store them in an object ready for being inserted into the DB\n const newItem = { title: titleInput.value, body: bodyInput.value };\n\n // open a read/write db transaction, ready for adding the data\n const transaction = db.transaction([\"notes_os\"], \"readwrite\");\n\n // call an object store that's already been added to the database\n const objectStore = transaction.objectStore(\"notes_os\");\n\n // Make a request to add our newItem object to the object store\n const addRequest = objectStore.add(newItem);\n\n addRequest.addEventListener(\"success\", () => {\n // Clear the form, ready for adding the next entry\n titleInput.value = \"\";\n bodyInput.value = \"\";\n });\n\n // Report on the success of the transaction completing, when everything is done\n transaction.addEventListener(\"complete\", () => {\n console.log(\"Transaction completed: database modification finished.\");\n\n // update the display of data to show the newly added item, by running displayData() again.\n displayData();\n });\n\n transaction.addEventListener(\"error\", () =>\n console.log(\"Transaction not opened due to error\"),\n );\n}\n</code></pre></div>\n<p>This is quite complex; breaking it down, we:</p>\n<ul>\n <li>Run <a href=\"/en-US/docs/Web/API/Event/preventDefault\"><code>Event.preventDefault()</code></a> on the event object to stop the form actually submitting in the conventional manner (this would cause a page refresh and spoil the experience).</li>\n <li>Create an object representing a record to enter into the database, populating it with values from the form inputs. Note that we don't have to explicitly include an <code>id</code> value — as we explained earlier, this is auto-populated.</li>\n <li>Open a <code>readwrite</code> transaction against the <code>notes_os</code> object store using the <a href=\"/en-US/docs/Web/API/IDBDatabase/transaction\"><code>IDBDatabase.transaction()</code></a> method. This transaction object allows us to access the object store so we can do something to it, e.g. add a new record.</li>\n <li>Access the object store using the <a href=\"/en-US/docs/Web/API/IDBTransaction/objectStore\"><code>IDBTransaction.objectStore()</code></a> method, saving the result in the <code>objectStore</code> variable.</li>\n <li>Add the new record to the database using <a href=\"/en-US/docs/Web/API/IDBObjectStore/add\"><code>IDBObjectStore.add()</code></a>. This creates a request object, in the same fashion as we've seen before.</li>\n <li>Add a bunch of event handlers to the <code>request</code> and the <code>transaction</code> objects to run code at critical points in the lifecycle. Once the request has succeeded, we clear the form inputs ready for entering the next note. Once the transaction has completed, we run the <code>displayData()</code> function again to update the display of notes on the page.</li>\n</ul>"}},{"type":"prose","value":{"id":"displaying_the_data","title":"Displaying the data","isH3":true,"content":"<p>We've referenced <code>displayData()</code> twice in our code already, so we'd probably better define it. Add this to your code, below the previous function definition:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// Define the displayData() function\nfunction displayData() {\n // Here we empty the contents of the list element each time the display is updated\n // If you didn't do this, you'd get duplicates listed each time a new note is added\n while (list.firstChild) {\n list.removeChild(list.firstChild);\n }\n\n // Open our object store and then get a cursor - which iterates through all the\n // different data items in the store\n const objectStore = db.transaction(\"notes_os\").objectStore(\"notes_os\");\n objectStore.openCursor().addEventListener(\"success\", (e) => {\n // Get a reference to the cursor\n const cursor = e.target.result;\n\n // If there is still another data item to iterate through, keep running this code\n if (cursor) {\n // Create a list item, h3, and p to put each data item inside when displaying it\n // structure the HTML fragment, and append it inside the list\n const listItem = document.createElement(\"li\");\n const h3 = document.createElement(\"h3\");\n const para = document.createElement(\"p\");\n\n listItem.appendChild(h3);\n listItem.appendChild(para);\n list.appendChild(listItem);\n\n // Put the data from the cursor inside the h3 and para\n h3.textContent = cursor.value.title;\n para.textContent = cursor.value.body;\n\n // Store the ID of the data item inside an attribute on the listItem, so we know\n // which item it corresponds to. This will be useful later when we want to delete items\n listItem.setAttribute(\"data-note-id\", cursor.value.id);\n\n // Create a button and place it inside each listItem\n const deleteBtn = document.createElement(\"button\");\n listItem.appendChild(deleteBtn);\n deleteBtn.textContent = \"Delete\";\n\n // Set an event handler so that when the button is clicked, the deleteItem()\n // function is run\n deleteBtn.addEventListener(\"click\", deleteItem);\n\n // Iterate to the next item in the cursor\n cursor.continue();\n } else {\n // Again, if list item is empty, display a 'No notes stored' message\n if (!list.firstChild) {\n const listItem = document.createElement(\"li\");\n listItem.textContent = \"No notes stored.\";\n list.appendChild(listItem);\n }\n // if there are no more cursor items to iterate through, say so\n console.log(\"Notes all displayed\");\n }\n });\n}\n</code></pre></div>\n<p>Again, let's break this down:</p>\n<ul>\n <li>First, we empty out the <a href=\"/en-US/docs/Web/HTML/Element/ul\"><code><ul></code></a> element's content, before then filling it with the updated content. If you didn't do this, you'd end up with a huge list of duplicated content being added to with each update.</li>\n <li>Next, we get a reference to the <code>notes_os</code> object store using <a href=\"/en-US/docs/Web/API/IDBDatabase/transaction\"><code>IDBDatabase.transaction()</code></a> and <a href=\"/en-US/docs/Web/API/IDBTransaction/objectStore\"><code>IDBTransaction.objectStore()</code></a> like we did in <code>addData()</code>, except here we are chaining them together in one line.</li>\n <li>The next step is to use the <a href=\"/en-US/docs/Web/API/IDBObjectStore/openCursor\"><code>IDBObjectStore.openCursor()</code></a> method to open a request for a cursor — this is a construct that can be used to iterate over the records in an object store. We chain a <code>success</code> event handler onto the end of this line to make the code more concise — when the cursor is successfully returned, the handler is run.</li>\n <li>We get a reference to the cursor itself (an <a href=\"/en-US/docs/Web/API/IDBCursor\"><code>IDBCursor</code></a> object) using <code>const cursor = e.target.result</code>.</li>\n <li>Next, we check to see if the cursor contains a record from the datastore (<code>if (cursor){ }</code>) — if so, we create a DOM fragment, populate it with the data from the record, and insert it into the page (inside the <code><ul></code> element). We also include a delete button that, when clicked, will delete that note by running the <code>deleteItem()</code> function, which we will look at in the next section.</li>\n <li>At the end of the <code>if</code> block, we use the <a href=\"/en-US/docs/Web/API/IDBCursor/continue\"><code>IDBCursor.continue()</code></a> method to advance the cursor to the next record in the datastore, and run the content of the <code>if</code> block again. If there is another record to iterate to, this causes it to be inserted into the page, and then <code>continue()</code> is run again, and so on.</li>\n <li>When there are no more records to iterate over, <code>cursor</code> will return <code>undefined</code>, and therefore the <code>else</code> block will run instead of the <code>if</code> block. This block checks whether any notes were inserted into the <code><ul></code> — if not, it inserts a message to say no note was stored.</li>\n</ul>"}},{"type":"prose","value":{"id":"deleting_a_note","title":"Deleting a note","isH3":true,"content":"<p>As stated above, when a note's delete button is pressed, the note is deleted. This is achieved by the <code>deleteItem()</code> function, which looks like so:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// Define the deleteItem() function\nfunction deleteItem(e) {\n // retrieve the name of the task we want to delete. We need\n // to convert it to a number before trying to use it with IDB; IDB key\n // values are type-sensitive.\n const noteId = Number(e.target.parentNode.getAttribute(\"data-note-id\"));\n\n // open a database transaction and delete the task, finding it using the id we retrieved above\n const transaction = db.transaction([\"notes_os\"], \"readwrite\");\n const objectStore = transaction.objectStore(\"notes_os\");\n const deleteRequest = objectStore.delete(noteId);\n\n // report that the data item has been deleted\n transaction.addEventListener(\"complete\", () => {\n // delete the parent of the button\n // which is the list item, so it is no longer displayed\n e.target.parentNode.parentNode.removeChild(e.target.parentNode);\n console.log(`Note ${noteId} deleted.`);\n\n // Again, if list item is empty, display a 'No notes stored' message\n if (!list.firstChild) {\n const listItem = document.createElement(\"li\");\n listItem.textContent = \"No notes stored.\";\n list.appendChild(listItem);\n }\n });\n}\n</code></pre></div>\n<ul>\n <li>The first part of this could use some explaining — we retrieve the ID of the record to be deleted using <code>Number(e.target.parentNode.getAttribute('data-note-id'))</code> — recall that the ID of the record was saved in a <code>data-note-id</code> attribute on the <code><li></code> when it was first displayed. We do however need to pass the attribute through the global built-in <a href=\"/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number\"><code>Number()</code></a> object as it is of datatype string, and therefore wouldn't be recognized by the database, which expects a number.</li>\n <li>We then get a reference to the object store using the same pattern we've seen previously, and use the <a href=\"/en-US/docs/Web/API/IDBObjectStore/delete\"><code>IDBObjectStore.delete()</code></a> method to delete the record from the database, passing it the ID.</li>\n <li>When the database transaction is complete, we delete the note's <code><li></code> from the DOM, and again do the check to see if the <code><ul></code> is now empty, inserting a note as appropriate.</li>\n</ul>\n<p>So that's it! Your example should now work.</p>\n<p>If you are having trouble with it, feel free to <a href=\"https://mdn.github.io/learning-area/javascript/apis/client-side-storage/indexeddb/notes/\" class=\"external\" target=\"_blank\">check it against our live example</a> (see the <a href=\"https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/index.js\" class=\"external\" target=\"_blank\">source code</a> also).</p>"}},{"type":"prose","value":{"id":"storing_complex_data_via_indexeddb","title":"Storing complex data via IndexedDB","isH3":true,"content":"<p>As we mentioned above, IndexedDB can be used to store more than just text strings. You can store just about anything you want, including complex objects such as video or image blobs. And it isn't much more difficult to achieve than any other type of data.</p>\n<p>To demonstrate how to do it, we've written another example called <a href=\"https://github.com/mdn/learning-area/tree/main/javascript/apis/client-side-storage/indexeddb/video-store\" class=\"external\" target=\"_blank\">IndexedDB video store</a> (see it <a href=\"https://mdn.github.io/learning-area/javascript/apis/client-side-storage/indexeddb/video-store/\" class=\"external\" target=\"_blank\">running live here also</a>). When you first run the example, it downloads all the videos from the network, stores them in an IndexedDB database, and then displays the videos in the UI inside <a href=\"/en-US/docs/Web/HTML/Element/video\"><code><video></code></a> elements. The second time you run it, it finds the videos in the database and gets them from there instead before displaying them — this makes subsequent loads much quicker and less bandwidth-hungry.</p>\n<p>Let's walk through the most interesting parts of the example. We won't look at it all — a lot of it is similar to the previous example, and the code is well-commented.</p>\n<ol>\n <li>\n <p>For this example, we've stored the names of the videos to fetch in an array of objects:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>const videos = [\n { name: \"crystal\" },\n { name: \"elf\" },\n { name: \"frog\" },\n { name: \"monster\" },\n { name: \"pig\" },\n { name: \"rabbit\" },\n];\n</code></pre></div>\n </li>\n <li>\n <p>To start with, once the database is successfully opened we run an <code>init()</code> function. This loops through the different video names, trying to load a record identified by each name from the <code>videos</code> database.</p>\n <p>If each video is found in the database (checked by seeing whether <code>request.result</code> evaluates to <code>true</code> — if the record is not present, it will be <code>undefined</code>), its video files (stored as blobs) and the video name are passed straight to the <code>displayVideo()</code> function to place them in the UI. If not, the video name is passed to the <code>fetchVideoFromNetwork()</code> function to, you guessed it, fetch the video from the network.</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>function init() {\n // Loop through the video names one by one\n for (const video of videos) {\n // Open transaction, get object store, and get() each video by name\n const objectStore = db.transaction(\"videos_os\").objectStore(\"videos_os\");\n const request = objectStore.get(video.name);\n request.addEventListener(\"success\", () => {\n // If the result exists in the database (is not undefined)\n if (request.result) {\n // Grab the videos from IDB and display them using displayVideo()\n console.log(\"taking videos from IDB\");\n displayVideo(\n request.result.mp4,\n request.result.webm,\n request.result.name,\n );\n } else {\n // Fetch the videos from the network\n fetchVideoFromNetwork(video);\n }\n });\n }\n}\n</code></pre></div>\n </li>\n <li>\n <p>The following snippet is taken from inside <code>fetchVideoFromNetwork()</code> — here we fetch MP4 and WebM versions of the video using two separate <a href=\"/en-US/docs/Web/API/Window/fetch\" title=\"fetch()\"><code>fetch()</code></a> requests. We then use the <a href=\"/en-US/docs/Web/API/Response/blob\"><code>Response.blob()</code></a> method to extract each response's body as a blob, giving us an object representation of the videos that can be stored and displayed later on.</p>\n <p>We have a problem here though — these two requests are both asynchronous, but we only want to try to display or store the video when both promises have fulfilled. Fortunately there is a built-in method that handles such a problem — <a href=\"/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all\"><code>Promise.all()</code></a>. This takes one argument — references to all the individual promises you want to check for fulfillment placed in an array — and returns a promise which is fulfilled when all the individual promises are fulfilled.</p>\n <p>Inside the <code>then()</code> handler for this promise, we call the <code>displayVideo()</code> function like we did before to display the videos in the UI, then we also call the <code>storeVideo()</code> function to store those videos inside the database.</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// Fetch the MP4 and WebM versions of the video using the fetch() function,\n// then expose their response bodies as blobs\nconst mp4Blob = fetch(`videos/${video.name}.mp4`).then((response) =>\n response.blob(),\n);\nconst webmBlob = fetch(`videos/${video.name}.webm`).then((response) =>\n response.blob(),\n);\n\n// Only run the next code when both promises have fulfilled\nPromise.all([mp4Blob, webmBlob]).then((values) => {\n // display the video fetched from the network with displayVideo()\n displayVideo(values[0], values[1], video.name);\n // store it in the IDB using storeVideo()\n storeVideo(values[0], values[1], video.name);\n});\n</code></pre></div>\n </li>\n <li>\n <p>Let's look at <code>storeVideo()</code> first. This is very similar to the pattern you saw in the previous example for adding data to the database — we open a <code>readwrite</code> transaction and get a reference to our <code>videos_os</code> object store, create an object representing the record to add to the database, then add it using <a href=\"/en-US/docs/Web/API/IDBObjectStore/add\"><code>IDBObjectStore.add()</code></a>.</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// Define the storeVideo() function\nfunction storeVideo(mp4, webm, name) {\n // Open transaction, get object store; make it a readwrite so we can write to the IDB\n const objectStore = db\n .transaction([\"videos_os\"], \"readwrite\")\n .objectStore(\"videos_os\");\n\n // Add the record to the IDB using add()\n const request = objectStore.add({ mp4, webm, name });\n\n request.addEventListener(\"success\", () =>\n console.log(\"Record addition attempt finished\"),\n );\n request.addEventListener(\"error\", () => console.error(request.error));\n}\n</code></pre></div>\n </li>\n <li>\n <p>Finally, we have <code>displayVideo()</code>, which creates the DOM elements needed to insert the video in the UI and then appends them to the page. The most interesting parts of this are those shown below — to actually display our video blobs in a <code><video></code> element, we need to create object URLs (internal URLs that point to the video blobs stored in memory) using the <a href=\"/en-US/docs/Web/API/URL/createObjectURL_static\" title=\"URL.createObjectURL()\"><code>URL.createObjectURL()</code></a> method. Once that is done, we can set the object URLs to be the values of our <a href=\"/en-US/docs/Web/HTML/Element/source\"><code><source></code></a> element's <code>src</code> attributes, and it works fine.</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// Define the displayVideo() function\nfunction displayVideo(mp4Blob, webmBlob, title) {\n // Create object URLs out of the blobs\n const mp4URL = URL.createObjectURL(mp4Blob);\n const webmURL = URL.createObjectURL(webmBlob);\n\n // Create DOM elements to embed video in the page\n const article = document.createElement(\"article\");\n const h2 = document.createElement(\"h2\");\n h2.textContent = title;\n const video = document.createElement(\"video\");\n video.controls = true;\n const source1 = document.createElement(\"source\");\n source1.src = mp4URL;\n source1.type = \"video/mp4\";\n const source2 = document.createElement(\"source\");\n source2.src = webmURL;\n source2.type = \"video/webm\";\n\n // Embed DOM elements into page\n section.appendChild(article);\n article.appendChild(h2);\n article.appendChild(video);\n video.appendChild(source1);\n video.appendChild(source2);\n}\n</code></pre></div>\n </li>\n</ol>"}},{"type":"prose","value":{"id":"offline_asset_storage","title":"Offline asset storage","isH3":false,"content":"<p>The above example already shows how to create an app that will store large assets in an IndexedDB database, avoiding the need to download them more than once. This is already a great improvement to the user experience, but there is still one thing missing — the main HTML, CSS, and JavaScript files still need to be downloaded each time the site is accessed, meaning that it won't work when there is no network connection.</p>\n<p>\n <img src=\"/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/ff-offline.png\" alt=\"Firefox offline screen with an illustration of a cartoon character to the left-hand side holding a two-pin plug in its right hand and a two-pin socket in its left hand. On the right-hand side there is an Offline Mode message and a button labeled 'Try again'.\" width=\"765\" height=\"307\" loading=\"lazy\">\n</p>\n<p>This is where <a href=\"/en-US/docs/Web/API/Service_Worker_API\">Service workers</a> and the closely-related <a href=\"/en-US/docs/Web/API/Cache\">Cache API</a> come in.</p>\n<p>A service worker is a JavaScript file that is registered against a particular origin (website, or part of a website at a certain domain) when it is accessed by a browser. When registered, it can control pages available at that origin. It does this by sitting between a loaded page and the network and intercepting network requests aimed at that origin.</p>\n<p>When it intercepts a request, it can do anything you wish to it (see <a href=\"/en-US/docs/Web/API/Service_Worker_API#other_use_case_ideas\">use case ideas</a>), but the classic example is saving the network responses offline and then providing those in response to a request instead of the responses from the network. In effect, it allows you to make a website work completely offline.</p>\n<p>The Cache API is another client-side storage mechanism, with a bit of a difference — it is designed to save HTTP responses, and so works very well with service workers.</p>"}},{"type":"prose","value":{"id":"a_service_worker_example","title":"A service worker example","isH3":true,"content":"<p>Let's look at an example, to give you a bit of an idea of what this might look like. We have created another version of the video store example we saw in the previous section — this functions identically, except that it also saves the HTML, CSS, and JavaScript in the Cache API via a service worker, allowing the example to run offline!</p>\n<p>See <a href=\"https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/\" class=\"external\" target=\"_blank\">IndexedDB video store with service worker running live</a>, and also <a href=\"https://github.com/mdn/learning-area/tree/main/javascript/apis/client-side-storage/cache-sw/video-store-offline\" class=\"external\" target=\"_blank\">see the source code</a>.</p>\n<h4 id=\"registering_the_service_worker\">Registering the service worker</h4>\n<p>The first thing to note is that there's an extra bit of code placed in the main JavaScript file (see <a href=\"https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.js\" class=\"external\" target=\"_blank\">index.js</a>). First, we do a feature detection test to see if the <code>serviceWorker</code> member is available in the <a href=\"/en-US/docs/Web/API/Navigator\"><code>Navigator</code></a> object. If this returns true, then we know that at least the basics of service workers are supported. Inside here we use the <a href=\"/en-US/docs/Web/API/ServiceWorkerContainer/register\"><code>ServiceWorkerContainer.register()</code></a> method to register a service worker contained in the <code>sw.js</code> file against the origin it resides at, so it can control pages in the same directory as it, or subdirectories. When its promise fulfills, the service worker is deemed registered.</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// Register service worker to control making site work offline\nif (\"serviceWorker\" in navigator) {\n navigator.serviceWorker\n .register(\n \"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js\",\n )\n .then(() => console.log(\"Service Worker Registered\"));\n}\n</code></pre></div>\n<div class=\"notecard note\">\n <p><strong>Note:</strong> The given path to the <code>sw.js</code> file is relative to the site origin, not the JavaScript file that contains the code. The service worker is at <code>https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js</code>. The origin is <code>https://mdn.github.io</code>, and therefore the given path has to be <code>/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js</code>. If you wanted to host this example on your own server, you'd have to change this accordingly. This is rather confusing, but it has to work this way for security reasons.</p>\n</div>\n<h4 id=\"installing_the_service_worker\">Installing the service worker</h4>\n<p>The next time any page under the service worker's control is accessed (e.g. when the example is reloaded), the service worker is installed against that page, meaning that it will start controlling it. When this occurs, an <code>install</code> event is fired against the service worker; you can write code inside the service worker itself that will respond to the installation.</p>\n<p>Let's look at an example, in the <a href=\"https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js\" class=\"external\" target=\"_blank\">sw.js</a> file (the service worker). You'll see that the install listener is registered against <code>self</code>. This <code>self</code> keyword is a way to refer to the global scope of the service worker from inside the service worker file.</p>\n<p>Inside the <code>install</code> handler, we use the <a href=\"/en-US/docs/Web/API/ExtendableEvent/waitUntil\"><code>ExtendableEvent.waitUntil()</code></a> method, available on the event object, to signal that the browser shouldn't complete installation of the service worker until after the promise inside it has fulfilled successfully.</p>\n<p>Here is where we see the Cache API in action. We use the <a href=\"/en-US/docs/Web/API/CacheStorage/open\"><code>CacheStorage.open()</code></a> method to open a new cache object in which responses can be stored (similar to an IndexedDB object store). This promise fulfills with a <a href=\"/en-US/docs/Web/API/Cache\"><code>Cache</code></a> object representing the <code>video-store</code> cache. We then use the <a href=\"/en-US/docs/Web/API/Cache/addAll\"><code>Cache.addAll()</code></a> method to fetch a series of assets and add their responses to the cache.</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>self.addEventListener(\"install\", (e) => {\n e.waitUntil(\n caches\n .open(\"video-store\")\n .then((cache) =>\n cache.addAll([\n \"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/\",\n \"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.html\",\n \"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.js\",\n \"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/style.css\",\n ]),\n ),\n );\n});\n</code></pre></div>\n<p>That's it for now, installation done.</p>\n<h4 id=\"responding_to_further_requests\">Responding to further requests</h4>\n<p>With the service worker registered and installed against our HTML page, and the relevant assets all added to our cache, we are nearly ready to go. There is only one more thing to do: write some code to respond to further network requests.</p>\n<p>This is what the second bit of code in <code>sw.js</code> does. We add another listener to the service worker global scope, which runs the handler function when the <code>fetch</code> event is raised. This happens whenever the browser makes a request for an asset in the directory the service worker is registered against.</p>\n<p>Inside the handler, we first log the URL of the requested asset. We then provide a custom response to the request, using the <a href=\"/en-US/docs/Web/API/FetchEvent/respondWith\"><code>FetchEvent.respondWith()</code></a> method.</p>\n<p>Inside this block, we use <a href=\"/en-US/docs/Web/API/CacheStorage/match\"><code>CacheStorage.match()</code></a> to check whether a matching request (i.e. matches the URL) can be found in any cache. This promise fulfills with the matching response if a match is found, or <code>undefined</code> if it isn't.</p>\n<p>If a match is found, we return it as the custom response. If not, we <a href=\"/en-US/docs/Web/API/Window/fetch\">fetch()</a> the response from the network and return that instead.</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>self.addEventListener(\"fetch\", (e) => {\n console.log(e.request.url);\n e.respondWith(\n caches.match(e.request).then((response) => response || fetch(e.request)),\n );\n});\n</code></pre></div>\n<p>\n And that is it for our service worker.\n There is a whole load more you can do with them — for a lot more detail, see the <a href=\"https://github.com/mdn/serviceworker-cookbook\" class=\"external\" target=\"_blank\">service worker cookbook</a>.\n Many thanks to Paul Kinlan for his article <a href=\"https://developers.google.com/codelabs/pwa-training/pwa03--going-offline#0\" class=\"external\" target=\"_blank\">Adding a Service Worker and Offline into your Web App</a>, which inspired this example.\n</p>\n<h4 id=\"testing_the_example_offline\">Testing the example offline</h4>\n<p>To test our <a href=\"https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/\" class=\"external\" target=\"_blank\">service worker example</a>, you'll need to load it a couple of times to make sure it is installed. Once this is done, you can:</p>\n<ul>\n <li>Try unplugging your network/turning your Wi-Fi off.</li>\n <li>Select <em>File > Work Offline</em> if you are using Firefox.</li>\n <li>Go to the devtools, then choose <em>Application > Service Workers</em>, then check the <em>Offline</em> checkbox if you are using Chrome.</li>\n</ul>\n<p>If you refresh your example page again, you should still see it load just fine. Everything is stored offline — the page assets in a cache, and the videos in an IndexedDB database.</p>"}},{"type":"prose","value":{"id":"summary","title":"Summary","isH3":false,"content":"<p>That's it for now. We hope you've found our rundown of client-side storage technologies useful.</p>"}},{"type":"prose","value":{"id":"see_also","title":"See also","isH3":false,"content":"<ul>\n <li><a href=\"/en-US/docs/Web/API/Web_Storage_API\">Web storage API</a></li>\n <li><a href=\"/en-US/docs/Web/API/IndexedDB_API\">IndexedDB API</a></li>\n <li><a href=\"/en-US/docs/Web/HTTP/Cookies\">Cookies</a></li>\n <li><a href=\"/en-US/docs/Web/API/Service_Worker_API\">Service worker API</a></li>\n</ul><ul class=\"prev-next\">\n <li><a class=\"button secondary\" href=\"/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs\"><span class=\"button-wrap\"> Previous </span></a></li>\n <li><a class=\"button secondary\" href=\"/en-US/docs/Learn/JavaScript/Client-side_web_APIs\"><span class=\"button-wrap\"> Overview: Client-side web APIs</span></a></li>\n \n</ul>"}}],"toc":[{"text":"Client-side storage?","id":"client-side_storage"},{"text":"Storing simple data — web storage","id":"storing_simple_data_—_web_storage"},{"text":"Storing complex data — IndexedDB","id":"storing_complex_data_—_indexeddb"},{"text":"Offline asset storage","id":"offline_asset_storage"},{"text":"Summary","id":"summary"},{"text":"See also","id":"see_also"}],"summary":"That's it for now. We hope you've found our rundown of client-side storage technologies useful.","popularity":0.0157,"modified":"2024-07-25T21:20:22.000Z","other_translations":[{"locale":"de","title":"Client-side storage","native":"Deutsch"},{"locale":"es","title":"Almacenamiento del lado cliente","native":"Español"},{"locale":"fr","title":"Stockage côté client","native":"Français"},{"locale":"ja","title":"クライアント側ストレージ","native":"日本語"},{"locale":"pt-BR","title":"Client-side storage","native":"Português (do Brasil)"},{"locale":"ru","title":"Client-side storage","native":"Русский"},{"locale":"zh-CN","title":"客户端存储","native":"中文 (简体)"}],"pageType":"learn-module-chapter","source":{"folder":"en-us/learn/javascript/client-side_web_apis/client-side_storage","github_url":"https://github.com/mdn/content/blob/main/files/en-us/learn/javascript/client-side_web_apis/client-side_storage/index.md","last_commit_url":"https://github.com/mdn/content/commit/bc0d0d1ef796435e969f6d65c7e5d3c08f4023aa","filename":"index.md"},"short_title":"Client-side storage","parents":[{"uri":"/en-US/docs/Learn","title":"Guides"},{"uri":"/en-US/docs/Learn/JavaScript","title":"JavaScript — Dynamic client-side scripting"},{"uri":"/en-US/docs/Learn/JavaScript/Client-side_web_APIs","title":"Client-side web APIs"},{"uri":"/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage","title":"Client-side storage"}],"pageTitle":"Client-side storage - Learn web development | MDN","noIndexing":false}}</script></body></html>