Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the jQuery Conference 2009 (Cambridge, MA) with:

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

By Ben Nadel on

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

@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.

Reply to this Comment

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.

Reply to this Comment

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.

Reply to this Comment

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!

Reply to this Comment

@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.

Reply to this Comment

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();

Reply to this Comment

@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.

Reply to this Comment

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.

Reply to this Comment

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).

Reply to this Comment

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..

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
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.