How to use iOS Safari localStorage and sessionStorage in Private Mode

In short, you can’t, but you can set a cookie via JavaScript 😉 Safari on iOS supports localStorage, but in Private Mode it simply throws an error when you try to save anything to it, which is not great. Also it breaks the behaviour of your app on iPhones and iPads. Long story short: You can’t use it, but you’ll not know until it’s too late.

So the error message Safari throws when you attempt to save anything to localStorage is the following:

Error: QUOTA_EXCEEDED_ERR: DOM Exception 22

Sessionstorage on Safari in private mode will not help us either, because the data saved to it will not persist through a page switch. Common use cases for saving in localStorage could be user preferences that affect your JavaScript UI when you’re using Vue.js, React, Angular or when you implement an age gate somewhere on your site.

I believe I only ran into this because I used localStorage directly instead of using some kind of wrapper around it, which you easily can do with Persist.js which supports the following stores:

  • flash: Flash 8 persistent storage.
  • gears: Google Gears-based persistent storage.
  • localstorage: HTML5 draft storage.
  • globalstorage: HTML5 draft storage (old spec).
  • ie: Internet Explorer userdata behaviors.
  • cookie: Cookie-based persistent storage.

That sounds pretty great!

If you for any reason want to roll your own cool thing or you literally only need to save a string or two in case of private Safari on iOS and keep the more complex stores to sane browsers (I’m not kidding, Firefox, Chrome and even edge get this) you can just write a fallback.

A common practise is to save a JavaScript object to localStorage and to read it on page load like so:

Let’s imagine we want to save the awesome status:

localStorage.jonathan = JSON.stringify({awesome:true});

Now on the next page load, we would love to know the value of awesome. One could be tempted to simply do this:

var savedValue = JSON.parse(localStorage.jonathan);

Don’t! It will just give you the following error message if

  1. the user has never even seen your site (first page load ever)
  2. the user uses Safari in private mode
  3. the user uses a devices that has 0kb free space (can’t write without space)
  4. the user has cleared their cache
Uncaught SyntaxError: Unexpected token u in JSON at position 0
    at JSON.parse (<anonymous>)
    at <anonymous>:1:6

Instead, if you don’t want this to crash your app, always try/catch JSON.parse, always:

var savedValue = {};
try {
    savedValue = JSON.parse(localStorage.jonathan); // get from localStorage if it exists
} catch (e){
    console.log('Something bad happened while trying to parse the localStorage!');
    console.log(e); // if you want to read the error
    savedValue.awesome = false; // assign default value
}

If you’re in a position where you can afford to do silly things for the sake of science, team building or you just want to annoy your sloppy coder colleagues, open a I didn’t try/catch JSON.parse jar instead of a swear jar. You’ll have pizza money until the end of your days.

Let’s take care of Safari once and for all. localStorage doesn’t work and neither does sessionstorage, but you still can set a cookie. More precisely you can set a session cookie, not one that persist until after the browser tab is closed, but that’ll do for a lot of cases, I mean that’s as far as we’ll go without GPU fingerprinting or other shady things that might be illegal in some countries.

Since I’m a lazy person and I don’t enjoy writing a ton of stuff for weird old implementations of things, I recommend you have a look at Cookies.js for all your cookie saving and reading needs. Also interesting is the Mozilla page and their simple cookie framework.

The framework is really simple to use and I suggest you use it after a test if localStorage saving works fails:

Cookies.set('awesome', 'true'); // note that this is a string, not a boolean

That’s it! Let me know if you’ve experienced other fun issues with Safari 😉

Leave a Reply

Your email address will not be published. Required fields are marked *