error TS18047: 'form' is possibly 'null'. is reported when code uses a variable, parameter or property whose type includes null as if it could not be null. It is the named form of TS2531: same check, with the variable's name in the message.
The usual source in JavaScript is a DOM lookup stored in a variable at the top of a file. Checking it once, right after the lookup, narrows the type for every line after it.
At a glance
| Code | TS18047 |
|---|---|
| Message | '{0}' 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); {0} is filled in by tsc for each error.
What it looks like
const form = document.querySelector("form");
form.addEventListener("submit", (event) => {error TS18047: 'form' is possibly 'null'.
event.preventDefault();
});jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} What causes it
- The variable holds the result of something that can return
null(querySelector,getItem,RegExp.exec, a function whose@returnsincludesnull). strictNullChecks(part ofstrict) is on.
How to fix it
Check it once, early
if (!form) throw new Error(...) (or return inside a function) right after the lookup narrows form to HTMLFormElement for the rest of the scope. A clear message beats Cannot read properties of null when the markup changes.
const form = document.querySelector("form");
if (!form) throw new Error("signup.js: no <form> on this page");
form.addEventListener("submit", (event) => {
event.preventDefault();
});
const footer = document.querySelector("footer");
footer?.classList.add("ready");jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Or use ?. where missing is fine
footer?.classList.add("ready") does nothing when the element is absent. Use it where doing nothing is the right result.
Gotchas
- For a
const, the check carries into callbacks such as thesubmithandler; for alet, only when nothing assigns the variable after the callback is created (TypeScript 5.4 and later). Aletthat is reassigned later is possiblynullagain inside the callback, because the checker cannot know when the callback runs. - For
undefinedthe message is TS18048, and for a type that includes both, TS18049.
Sources
- Narrowing: Control flow analysis · TypeScript handbook
- TypeScript 5.4 release notes (narrowing preserved in closures) · TypeScript
- diagnosticMessages.json (TypeScript 5.9.3) · TypeScript on GitHub
Last reviewed 20 October 2026.