TypeScript error

TS2531: A lookup that may return null, used without a check

  • Error TS2531
  • Reported with strictNullChecks
  • Reported with strict
  • Tested with TypeScript 5.9

error TS2531: Object is possibly 'null'. is reported when code reads a property or calls a method on an expression whose type includes null. In browser code the expression is usually a DOM lookup: document.querySelector("#cart-count") returns Element | null, because the element might not be on the page.

The message names no variable because the value has none: it is the direct result of a call. Store the same result in a variable and the checker reports TS18047, 'badge' is possibly 'null'., instead. Both only appear with strictNullChecks, which strict turns on.

At a glance

CodeTS2531
MessageObject is possibly 'null'.
TypeScript's categoryerror
Listed underNull, undefined and unknown
Turned on by strictNullChecks, strict
In .js files Reported when the file is checked: checkJs in jsconfig.json, or // @ts-check at the top of the file

The message is TypeScript's own text from its diagnostic messages file (Apache-2.0).

What it looks like

cart.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: cart.js(4,3): error TS2531: Object is possibly 'null'. cart.js(9,10): error TS2531: Object is possibly 'null'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. cart.js(4,3): error TS2531: Object is possibly 'null'. cart.js(9,10): error TS2531: Object is possibly 'null'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. cart.js(4,3): error TS2531: Object is possibly 'null'. cart.js(9,10): error TS2531: Object is possibly 'null'. checked 21 October 2026
/** @param {number} count */
export function showCount(count) {
  document.querySelector("#cart-count").textContent = String(count);error TS2531: Object is possibly 'null'.
}

export function savedCartSize() {
  return localStorage.getItem("cart").length;error TS2531: Object is possibly 'null'.
}
Using the result of querySelector and localStorage.getItem directly.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

What causes it

  • A function that returns null when it finds nothing (querySelector, getElementById, localStorage.getItem, match on a string) is used as if it always succeeds.
  • strictNullChecks is on. Without it null is allowed everywhere and the same code fails only at run time, with TypeError: Cannot set properties of null.

How to fix it

Check the value, or read it with ?.

Store the result, test it, and use it inside the check: after if (badge) the checker knows badge is an element. For reads, optional chaining and a default do it in one line: localStorage.getItem("cart")?.length ?? 0. Optional chaining cannot be used on the left of an assignment (a?.b = 1 is TS2779), so writes need the if.

cart.js TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
/** @param {number} count */
export function showCount(count) {
  const badge = document.querySelector("#cart-count");
  if (badge) badge.textContent = String(count);
}

export function savedCartSize() {
  return localStorage.getItem("cart")?.length ?? 0;
}

const main = /** @type {HTMLElement} */ (document.querySelector("main"));
main.dataset.ready = "true";
Check for null, read with ?., or cast when the markup guarantees it.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

Or cast when the markup guarantees the element

If the element is always in the page's HTML, a cast states that: /** @type {HTMLElement} */ (document.querySelector("main")). It also gives a more specific type than Element. If the guarantee ever breaks, the code fails at run time, so prefer the check for anything optional.

Gotchas

  • The ! operator from TypeScript (querySelector("main")!) is a syntax error in a .js file, TS8013; the cast above is the JSDoc spelling of the same idea.
  • querySelector with a tag name returns the specific element type (querySelector("form") is HTMLFormElement | null); with a class or id selector it returns Element | null, which lacks properties such as dataset until you cast or check with instanceof HTMLElement.

Sources

Last reviewed 20 October 2026.