Skip to content
Latchkey

Jest "SecurityError: localStorage is not available"

jsdom refuses localStorage/sessionStorage access when the document origin is "opaque" - the default about:blank. The spec forbids storage on opaque origins, so jsdom throws a SecurityError.

What this error means

Any code touching localStorage under jsdom throws SecurityError: localStorage is not available for opaque origins. It is deterministic and tied to the jsdom URL, not to the test logic.

Jest output
SecurityError: localStorage is not available for opaque origins

    at Window.get localStorage (node_modules/jsdom/lib/jsdom/browser/Window.js)

Diagnose it: flake, environment, or genuine failure?

Before debugging the assertion, establish whether the test is deterministic. A test that fails only in CI is usually order-dependent, time-dependent, or racing something, and fixing the assertion will not help.

Terminal
# does it fail in isolation?
npx vitest run path/to/file.test.ts

# is it order dependent? run the suite in a random order twice
npx vitest run --sequence.shuffle

# is it a race? run the same file repeatedly
for i in $(seq 1 20); do npx vitest run path/to/file.test.ts || break; done

Common causes

Default opaque jsdom origin

With no configured URL, jsdom runs at about:blank, an opaque origin. Per the HTML storage spec, localStorage on an opaque origin must throw.

No storage mock provided

If you do not give jsdom a real http(s) origin, you must instead stub localStorage yourself; otherwise every access errors.

How to fix it

Give jsdom a concrete origin

Set a real URL so the origin is no longer opaque and storage works.

jest.config.js
// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  testEnvironmentOptions: { url: 'http://localhost/' },
};

Mock storage in setup

jest.setup.js
// jest.setup.js
const store = {};
global.localStorage = {
  getItem: (k) => store[k] ?? null,
  setItem: (k, v) => { store[k] = String(v); },
  removeItem: (k) => { delete store[k]; },
  clear: () => { for (const k in store) delete store[k]; },
};

CI-only causes worth ruling out

  • Runners have fewer cores than a laptop, so timing-sensitive tests that pass locally fail under contention.
  • No TTY and a different locale or timezone. Snapshot tests containing formatted dates or numbers are the usual casualty; pin TZ and LANG in the job.
  • Parallel workers sharing a database, a port, or a temp directory. Give each worker its own namespace.
  • Default timeouts calibrated on a fast machine. A cold runner is slower on first execution, especially before any cache warms.

How to prevent it

  • Set a non-opaque url in testEnvironmentOptions.
  • Centralize a storage mock in a shared setup file.
  • Reset storage between tests to avoid cross-test leakage.

Frequently asked questions

What causes Jest "SecurityError: localStorage is not available"?
There are 2 common causes: default opaque jsdom origin and no storage mock provided. With no configured URL, jsdom runs at about:blank, an opaque origin.
How do I fix Jest "SecurityError: localStorage is not available"?
There are 2 fixes depending on which cause you have: give jsdom a concrete origin and mock storage in setup. Work through them in order, since the first is the most common.
What does Jest "SecurityError: localStorage is not available" actually mean?
Any code touching localStorage under jsdom throws SecurityError: localStorage is not available for opaque origins.
How do I stop Jest "SecurityError: localStorage is not available" happening again?
Set a non-opaque url in testEnvironmentOptions. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card