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
| Code | TS2531 |
|---|---|
| Message | Object is possibly 'null'. |
| TypeScript's category | error |
| Listed under | Null, 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
/** @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'.
}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
nullwhen it finds nothing (querySelector,getElementById,localStorage.getItem,matchon a string) is used as if it always succeeds. strictNullChecksis on. Without itnullis allowed everywhere and the same code fails only at run time, withTypeError: 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.
/** @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";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.jsfile, TS8013; the cast above is the JSDoc spelling of the same idea. querySelectorwith a tag name returns the specific element type (querySelector("form")isHTMLFormElement | null); with a class or id selector it returnsElement | null, which lacks properties such asdatasetuntil you cast or check withinstanceof HTMLElement.
Sources
- Everyday Types: the null and undefined section · TypeScript handbook
- Narrowing: Truthiness narrowing · TypeScript handbook
- Document: querySelector() method · MDN Web Docs
- diagnosticMessages.json (TypeScript 5.9.3) · TypeScript on GitHub
Last reviewed 20 October 2026.