Type Local Storage in TypeScript

Learn how TypeScript types the localStorage API. Understand the Storage interface, string-only values, null handling, and patterns for typed get/set wrappers.

6 min read

localStorage is a browser API that stores key-value pairs as strings and persists them across browser sessions. TypeScript types it through the Storage interface, which is the same interface used by sessionStorage. The type system tells you that every key and every value is a string, but it does not know what data you store behind each key. You handle that with your own type annotations.

How TypeScript types localStorage

The global localStorage variable is typed as Storage. This interface gives you methods for reading, writing, and removing items, plus a length property for the total count. The key methods and their TypeScript signatures are straightforward.

typescripttypescript
localStorage.setItem('theme', 'dark');
 
const theme = localStorage.getItem('theme');
//    ^? const theme: string | null
 
if (theme !== null) {
  console.log(theme.toUpperCase()); // "DARK"
}

getItem returns string or null because the key might not exist in storage. This is different from an empty string, which means the key exists but was set to an empty value. The null forces you to handle the missing-key case explicitly, which is the right default for storage that can be cleared at any time.

Storing and retrieving non-string data

Storage only handles strings. To store numbers, booleans, or objects, you convert them yourself. TypeScript cannot verify the conversion at compile time, so you write the serialisation and parsing code with clear type expectations.

typescripttypescript
interface UserPreferences {
  theme: string;
  fontSize: number;
}
 
const prefs: UserPreferences = { theme: 'dark', fontSize: 16 };
localStorage.setItem('prefs', JSON.stringify(prefs));
 
const stored = localStorage.getItem('prefs');
if (stored !== null) {
  const parsed: UserPreferences = JSON.parse(stored);
  console.log(parsed.fontSize); // 16
}

JSON.parse returns any by default. The type annotation on parsed tells TypeScript what shape you expect. This is a compile-time assertion, not a runtime validation. If someone manually edits localStorage and puts invalid JSON under the "prefs" key, JSON.parse will throw.

Always wrap JSON.parse in try-catch when reading from storage, because the data could be corrupted or missing:

typescripttypescript
function loadPreferences(): UserPreferences | null {
  const stored = localStorage.getItem('prefs');
  if (stored === null) return null;
 
  try {
    const parsed: UserPreferences = JSON.parse(stored);
    return parsed;
  } catch {
    localStorage.removeItem('prefs');
    return null;
  }
}

The try-catch catches invalid JSON, and removing the corrupted key prevents the same error on the next read. Returning null from the function signals to the caller that no valid preferences were found.

A typed storage helper

Instead of repeating null checks and try-catch blocks, wrap the Storage API in a small typed helper. This keeps the type safety in one place and makes the rest of your code cleaner.

typescripttypescript
function loadFromStorage<T>(key: string, fallback: T): T {
  const stored = localStorage.getItem(key);
  if (stored === null) return fallback;
 
  try {
    return JSON.parse(stored) as T;
  } catch {
    localStorage.removeItem(key);
    return fallback;
  }
}

The generic type parameter T lets the caller specify the expected type once, and the fallback value provides a safe default when the key is missing or corrupted. The helper handles null, parse errors, and cleanup in one place. Call it with a type argument and a fallback object matching that type:

typescripttypescript
const prefs = loadFromStorage<UserPreferences>('prefs', {
  theme: 'light',
  fontSize: 14,
});

Removing and clearing items

The Storage interface provides removeItem for deleting a single key and clear for removing everything. Both are straightforward and type-safe without any extra work:

typescripttypescript
localStorage.removeItem('theme');
localStorage.clear();

After calling clear, every subsequent getItem call returns null. The length property drops to zero. These methods never throw, so you do not need try-catch around them.

Storage event typing

The browser fires a StorageEvent on other tabs when localStorage changes in the current tab. TypeScript types this event through the StorageEvent interface, which gives you the key, old value, and new value of the change.

typescripttypescript
window.addEventListener('storage', (event) => {
  //    ^? (parameter) event: StorageEvent
  console.log(event.key);     // the key that changed
  console.log(event.oldValue); // previous value (string or null)
  console.log(event.newValue); // new value (string or null)
});

The event does not fire in the tab that made the change, only in other tabs from the same origin. StorageEvent.key and the value properties are all typed as string or null, consistent with the Storage interface.

Common mistakes

Forgetting to handle null from getItem is the most frequent error. TypeScript flags this at compile time because the return type includes null in the union. Avoid the temptation to use a non-null assertion unless you are certain the key exists.

Storing objects without JSON.stringify stores the string "[object Object]" instead of the actual data. TypeScript cannot catch this because setItem accepts any string, and calling toString on an object produces a useless string silently.

Not wrapping JSON.parse in try-catch leads to runtime crashes when storage data is corrupted. localStorage is shared across all code on the same origin, so another script or a browser extension could modify or corrupt your data.

See Type Dataset Values in TypeScript for similar string-only DOM storage patterns. See Type Fetch in the Browser with TypeScript for typing API responses that you might want to cache in localStorage.

Rune AI

Rune AI

Key Insights

  • localStorage is typed as Storage, with all keys and values as strings.
  • getItem returns string or null, so always check for null before using the result.
  • Use JSON.stringify and JSON.parse with type assertions for non-string data.
  • Wrap JSON.parse in try-catch to handle corrupted or missing data.
  • A typed helper function reduces repetition and catches errors in one place.
RunePowered by Rune AI

Frequently Asked Questions

What type does localStorage have in TypeScript?

localStorage is typed as Storage. The Storage interface provides getItem, setItem, removeItem, and clear methods, plus a length property and numeric indexing. All keys and values are strings.

How do I store objects in localStorage with TypeScript?

Use JSON.stringify when setting and JSON.parse when getting. Define an interface for your data shape and use a type assertion on the parsed result. Always wrap in try-catch because JSON.parse throws on invalid input.

Why does localStorage.getItem return string or null?

getItem returns null when the key does not exist in storage. This is different from an empty string, which means the key exists but has no value. Always handle null before using the returned value.

Conclusion

TypeScript types localStorage through the Storage interface, where every key and value is a string. Use JSON serialization for objects, handle null from getItem, and wrap unsafe operations in try-catch. A small typed wrapper function makes repeated storage access safer and cleaner.