Exploring HTML5's localStorage - Persistent Client-Side Key-Value Pairs

Posted January 25, 2011 at 11:06 AM by Ben Nadel

Tags: Javascript / DHTML

The other day, when building the Things I Give website and web app, I used HTML5's localStorage object for the first time. Like cookies, the localStorage object provides a way to persist string-based, key-value pairs on the client. Unlike cookies, however, these values don't have to be passed back to the server with every request. And, the localStorage has a much larger quota than cookies - about 5 megabytes on supporting browsers.

The localStorage object has four primary method:

  • localStorage.clear()
  • localStorage.getItem( key )
  • localStorage.removeItem( key )
  • localStorage.setItem( key, value )

When dealing with the localStorage object, it is important to remember that, like cookies, you are dealing with string values. Both the key and the value arguments must be strings. And, while the browser might not throw an error if you pass-in non-string values, you'll quickly find that it is simply calling the toString() method implicitly.

So, how persistent is the localStorage? To be honest, I have no idea. Other than calling the .clear() method explicitly, I am not even sure how to clear the localStorage at the browser level. If anyone has any insight on this matter, I'd love to hear it.

That said, let's take a quick look at the localStorage object in action:

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Explorating HTML5's localStorage</title>
  •  
  • <script type="text/javascript">
  •  
  • // Right now, localStorage is empty. We're going to be
  • // testing the features of the storage interface.
  •  
  •  
  • // While the localStorage object does have accessor and
  • // mutator methods, it can also be used as an associative
  • // array. This can be helpful when checking to see if a
  • // value actually exists.
  • console.log(
  • "hella in localStorage ---",
  • ("hella" in localStorage)
  • );
  •  
  • // Try to access the same value using the getItem() method.
  • console.log(
  • "localStorage.getItem( 'hella' ) ---",
  • localStorage.getItem( "hella" )
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Now that we've tried to check for and access an undefined
  • // value, let's go ahead and set a value. First, we'll use
  • // mutator method, setItem().
  • localStorage.setItem( "hella", "sexy" );
  •  
  • // And, we'll use the associate array approach.
  • localStorage[ "wicked" ] = "awesome";
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Now that we've set a value, let's again use two approaches
  • // to determining if a value has been set.
  • console.log(
  • "hella in localStorage ---",
  • ("hella" in localStorage)
  • );
  •  
  • // Try to access the same value using the getItem() method.
  • console.log(
  • "localStorage.getItem( 'wicked' ) ---",
  • localStorage.getItem( "wicked" )
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Now that we see how to put in values and how to receive
  • // them, let's take a look at the type of data that we
  • // actually get to work with. The localStorage can only
  • // store string values; so, what happens when we pass it
  • // something that is not a string.
  •  
  • // Create an object.
  • var myObject = {};
  •  
  • // Store the object.
  • localStorage.setItem( "myObject", myObject );
  •  
  • // Now, get the value and test the data type.
  • console.log(
  • "typeof( myObject ) ---",
  • typeof( localStorage.getItem( "myObject" ) )
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // As a final test, let's delete the values in the
  • // localStorage object. First, we can use the removeItem()
  • // mutator.
  • localStorage.removeItem( "hella" );
  •  
  • // Then, we can also use the "delete" operator.
  • delete localStorage[ "wicked" ];
  •  
  •  
  • // Now, let's check again to see if these items exist.
  • console.log(
  • "hella in localStorage ---",
  • ("hella" in localStorage)
  • );
  •  
  • console.log(
  • "wicked in localStorage ---",
  • ("wicked" in localStorage)
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Clear out any local storage items that have
  • // been populated from our experimenting.
  • localStorage.clear();
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Exploring HTML5's localStorage
  • </h1>
  •  
  • </body>
  • </html>

As you can see, I am using various approaches to testing, setting, and retrieving data from the localStorage object. And, when we run the above code, we get the following console output:

hella in localStorage --- false
localStorage.getItem( 'hella' ) --- null
hella in localStorage --- true
localStorage.getItem( 'wicked' ) --- awesome
typeof( myObject ) --- string
hella in localStorage --- true null
wicked in localStorage --- false null

The first thing I do is start off by testing the existence of the key, "hella." While the localStorage object does present accessor methods, it can also be used like an associative array. So, while I am using the getItem() to check for a null-return, I am also using Javascript's "in" operator to check for key existence.

Once set, you can see that both the "in" operator and the getItem() method check for the existence of and the value for the given key, respectively. Notice also, however, that the return value is always a string. Even after we set a Object instance, its value comes back as the toString() representation of that object. Remember: we are always dealing with string values!

Now look at the last couple of lines. I am using both the "delete" operator and the removeItem() method to delete keys from the localStorage object; but, when we follow that up with subsequent tests, you can see that the "in" operator no longer returns a valid result. In our case, it tells us that the key, "hella" still exists within the localStorage object even though its return value is null (ie. the value doesn't exist).

localStorage is definitely cool; but, it has some limitations. As such, it might be useful to think of localStorage more like a "behavior." Then, we can build other objects that use the localStorage as an encapsulated "storage behavior." In the following demo, I've done just that; I've created a Cache object that uses the localStorage internally, but provides a more flexible interface:

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Explorating HTML5's localStorage</title>
  •  
  • <script type="text/javascript">
  •  
  • // The localStorage option has some limitations both in the
  • // way that it treats values and in the way that it checks
  • // for existence. As such, this Cache object will provide
  • // a better proxy.
  • function Cache( nativeStorage, serializer ){
  •  
  • // Store the native storage reference as the object that
  • // we are going to proxy. This object must uphold the
  • // HTML5 Storage interface.
  • this.storage = nativeStorage;
  •  
  • // Store the serialization behavior. This object must
  • // uphold the JSON interface for serialization and
  • // deserialization.
  • this.serializer = serializer;
  • }
  •  
  •  
  • // Set up the class methods.
  • Cache.prototype = {
  •  
  • // I clear the cache.
  • clear: function(){
  • // Clear the storage container.
  • this.storage.clear();
  •  
  • // Return this object reference for method chaining.
  • return( this );
  • },
  •  
  •  
  • // I get an item from the cache. If the item cannot be
  • // found, I can pass back an optional default value.
  • getItem: function( key, defaultValue ){
  • // Get the cached item.
  • var value = this.storage.getItem( key );
  •  
  • // Check to see if it exists. If it does, then we
  • // need to deserialize it.
  • if (value == null){
  •  
  • // No cached item could be found. Now, we either
  • // have to return the default value, or we have
  • // to return Null. We have to be careful here,
  • // though, because the default value might be a
  • // falsy.
  • return(
  • (typeof( defaultValue ) != "undefined") ?
  • defaultValue :
  • null
  • );
  •  
  • } else {
  •  
  • // The value was found; return it in its
  • // original form.
  • return(
  • this.serializer.parse( value )
  • );
  •  
  • }
  • },
  •  
  •  
  • // I check to see if the given key exists in the storage
  • // container.
  • hasItem: function( key ){
  • // Simply check to see if the key access results in a
  • // null value.
  • return(
  • this.storage.getItem( key ) != null
  • );
  • },
  •  
  •  
  • // I remove the given item from the cache.
  • removeItem: function( key ){
  • // Remove the key from the storage container.
  • this.storage.removeItem( key );
  •  
  • // Return this object reference for method chaining.
  • return( this );
  • },
  •  
  •  
  • // I store the item in the cache. When doing this, I
  • // automatically serialize the value.
  • //
  • // NOTE: Not all value (ex. functions and private
  • // variables) will serialize.
  • setItem: function( key, value ){
  • // Store the serialize value.
  • this.storage.setItem(
  • key,
  • this.serializer.stringify( value )
  • );
  •  
  • // Return this object reference for method chaining.
  • return( this );
  • }
  •  
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Now, let's create an instance of our new cache. When doing
  • // this, we're going to use localStorage as our storage
  • // container and the native JSON object as our serializer.
  • var cache = new Cache( localStorage, JSON );
  •  
  •  
  • // Check to see if a value exists.
  • console.log(
  • "cache.hasItem( 'girl' ) ---",
  • cache.hasItem( "girl" )
  • );
  •  
  • // Create a value to store.
  • var girl = {
  • name: "Tricia",
  • sexy: "Hella"
  • };
  •  
  • // Set the value.
  • console.log(
  • "cache.setItem( 'girl', girl ) ---",
  • cache.setItem( "girl", girl )
  • );
  •  
  • // Check to see if a value exists again
  • console.log(
  • "cache.hasItem( 'girl' ) ---",
  • cache.hasItem( "girl" )
  • );
  •  
  • // Get the cached value and chekc its name.
  • console.log(
  • "cache.getItem( 'girl' ).name ---",
  • cache.getItem( "girl" ).name
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Clear out any local storage items that have
  • // been populated from our experimenting.
  • cache.clear();
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Exploring HTML5's localStorage
  • </h1>
  •  
  • </body>
  • </html>

The Cache object has a smaller interface than the localStorage object, providing no associative-array aspect. But, in addition to a hasItem() method, it also provides for non-string values and a default get-value:

  • Cache( storage, serializer )
  • Cache.clear()
  • Cache.getItem( key [, defaultValue ] )
  • Cache.hasItem( key )
  • Cache.removeItem( key )
  • Cache.setItem( key, value )

When we create the cache object, we need to pass a storage container and a serialization behavior to the constructor. This is because we don't want to limit our cache interactions to those that deal with string values. We want to be able to pass a variety of objects to the cache and have the cache worry about the string-based serialization and deserialization.

In this demo, we are using the localStorage object as our storage container and the native JSON object as our serialization behavior. And, when we run the above code, we get the following console output:

cache.hasItem( 'girl' ) --- false
cache.setItem( 'girl', girl ) --- Object { storage=, serializer=JSON}
cache.hasItem( 'girl' ) --- true
cache.getItem( 'girl' ).name --- Tricia

As you can see, our complex "girl" object was successfully set, serialized, deserialized, retrieved, and then accessed. At the end of this workflow, we were able to get the "name" property of the duplicated object.

The localStorage object looks like it has some very cool potential as far as a client-side persistence mechanism goes. This is really my first look into it, so I am sure there is a lot more to wrap my head around. Hopefully, I'll have more to say on this shortly.




Reader Comments

Jan 25, 2011 at 11:21 AM // reply »
8 Comments

Great article, Ben! Can't wait to experiment with this!


Jan 25, 2011 at 11:29 AM // reply »
11,238 Comments

@Seth,

Thanks my man. I just realized, however, that I didn't demonstrate at all that this persists across page refreshes. Ugg, sooo tired today. Pipes in my apartment building were banging all night :(

Anyway, this stuff seems very interesting. The concept of persistence always seems to make every other concept a good deal more complicated; but, the localStorage objects offers a nice, low-hanging fruit for experimentation.


Jan 25, 2011 at 12:56 PM // reply »
5 Comments

This looks like a great way to improve ajax performance!

I've used the jCache plugin for jQuery ( http://plugins.jquery.com/project/jCache); works great but I don't recall if it maintains state across pages (I don't think it does).

One could extend your Cache object to use either jCache or localstorage depending if localstorage is supported - nice way to start adding html5 features that are backward compatible.


Jan 26, 2011 at 8:52 AM // reply »
3 Comments

Hey Ben

This looks like a solution for my problem but I don't think it is maybe you can shine some light.

I am creating an image on the client side with J Query, I want to then save that image as rtf. now the problem comes in that the image needs to be dynamic based on variable from the page. is there any way that I could use this local storage method to save the image so I can manipulate further after the client has seen the image?

the reason I am thinking it will not work is because the local storage only stores the information as string.


Jan 27, 2011 at 5:10 PM // reply »
26 Comments

Hey, Ben, I just saw someone mention this on Twitter: http://the-m-project.net/ It uses HTML5 techniques (especially localStorage) and the jQuery Mobile framework to create mobile web apps with persistent data. Might be worth checking out.


Jul 14, 2011 at 5:12 PM // reply »
1 Comments

How to remove the localstorage? For Safari, on a Mac,...:

  • rm ~/Library/Safari/LocalStorage/*.localstorage

The files seem to begin with "http_" or "https_", then the website FQDN, then "_0" which I take mean to mean a small integer, followed by ".localstorage". This implies that persistence is forever, per-user. Safari does not (yet), have a checkbox in the "Reset Safari..." dialog box to delete 'em!


Jul 22, 2011 at 9:49 AM // reply »
11,238 Comments

@No,

I am not sure how to delete them. I have only tried to remove things using the Browser's actual Preferences and cache-clearing and what not. I've never actually messed around with the physical files that hold the data. That might be a bit risky.


Aug 25, 2011 at 4:02 AM // reply »
1 Comments

With Safari's "Developer Tools" enabled, individual key/value pairs can be deleted via Web Inspector->Resources->Local Storage->domain.

Key/value pairs can also be deleted via JavaScript and the entire LocalStorage store for a domain can wiped out with a JavaScript command: localStorage.clear();


Oct 12, 2011 at 3:58 PM // reply »
2 Comments

@Herman,

You can convert the image to a Base64 string, and store that in localstorage. You'll be increase the image's memory footprint so you might bump your head on the localstorage limit, but besides that it should work.


Jul 9, 2012 at 1:58 PM // reply »
3 Comments

Weird quirk here. I am loading up your content in IE9 browser. If my page extension is .htm, it works fine. If it is .cfm, it does not work and nothing is written to the console. Both page extensions work fine in Firefox and Chrome.


Jul 9, 2012 at 2:16 PM // reply »
3 Comments

Ok, never mind, got it working. Needed to clear my IE cache after I got my white space issue cleared up.


Aug 12, 2012 at 5:11 PM // reply »
1 Comments

Does "logging into" Google Chrome let you take key-value stores with you across browsers?

The apparent downside to key-value stores is that it assumes one computer = 1 user. In fact multiple people may use one computer, and one person may use multiple computers at home and work.

The biggest downside I see to this incredibly powerful feature is that data is chained to one client computer, whereas with a login cookie and a thin client the data is stored on the server and available on all networked computers (after login).


Aug 28, 2012 at 9:07 AM // reply »
1 Comments

so where can we get a list with all methods for localstorage (and sessionstorage.. my understanding is they both have the same methods..)

thank you..



Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 17, 2013 at 7:42 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
Ben - thanks so much for posting these Angular articles and findings, they've been a huge help towards learning one of the more 'complex' JavaScript frameworks out there (IMO). I have been using Angu ... read »
May 16, 2013 at 5:01 PM
UPDATE: Parsing CSV Data Files In ColdFusion With csvToArray()
Your code was the closest thing I've found to obtaining some direction for converting ISO fields to values that CF can translate properly. Thank you for posting! ... read »
May 15, 2013 at 10:37 PM
Very Simple Pusher And ColdFusion Powered Chat
hi id making plz easy ... read »
May 15, 2013 at 6:07 PM
Making SOAP Web Service Requests With ColdFusion And CFHTTP
Ben, you once again saved my bacon at work. Thank you, thank you, thank you! ... read »
May 15, 2013 at 4:15 PM
What If All User Interface (UI) Data Came In Reports?
@Josh, Thanks! @Ben, I definitely recommend the David West book "Object Thinking" I've been quoting from. It goes deeply into the philosophy and history of OO programming. His breadth ... read »
May 15, 2013 at 11:36 AM
Ask Ben: Print Part Of A Web Page With jQuery
I found this helpfull when you need to keep (refresh) the original parent page after closing the iframe child print dialog (Hoping you're not using a form at this time so it won't submit again): On ... read »
May 14, 2013 at 7:13 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, If there's any books you'd recommend on the subject of domain modelling, I'd love to hear it. I just downloaded the free PDF of "Domain Driven Design Quickly". Figured I'd give it ... read »
May 14, 2013 at 6:57 PM
The UX Of Prototyping: Low-Fidelity Is The New High-Fidelity
@Phillip, I'm not sure I follow what you mean? Are you saying that you looked at the list of widgets provided by the jQuery UI and let that be your style guide? ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools