Both types accept every value. The difference is what you may do with it afterwards. With any, TypeScript allows everything, so a misspelled property or a wrong method call passes silently. With unknown, TypeScript allows nothing until you narrow the value with typeof, instanceof, in or a type guard.
Use unknown for values whose shape you have not checked yet: parsed JSON, postMessage data, caught errors. TypeScript added it in version 3.0 as the safe counterpart to any.
At a glance
| Use | JSDoc in a .js file | TypeScript in a .ts file |
|---|---|---|
| Anything, checked before use | /** @type {unknown} */ | : unknown |
| Anything, never checked | /** @type {any} */ | : any |
| Closure spellings of any | /** @type {*} */ or /** @type {?} */ | : any |
Examples
/**
* @param {string} text
* @returns {{ theme: string }}
*/
export function readSettings(text) {
/** @type {unknown} */
const data = JSON.parse(text);
if (typeof data === "object" && data !== null && "theme" in data && typeof data.theme === "string") {
return { theme: data.theme };
}
return { theme: "light" };
}
readSettings('{"theme":"dark"}');export function readSettings(text: string): { theme: string } {
const data: unknown = JSON.parse(text);
if (typeof data === "object" && data !== null && "theme" in data && typeof data.theme === "string") {
return { theme: data.theme };
}
return { theme: "light" };
}
readSettings('{"theme":"dark"}');jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /** @param {string} text */
export function themeOf(text) {
/** @type {unknown} */
const data = JSON.parse(text);
return data.theme;error TS18046: 'data' is of type 'unknown'.
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /** @param {string} text */
export function themeOf(text) {
/** @type {any} */
const data = JSON.parse(text);
// No error: with any, the misspelled property and the wrong method both pass.
return data.thmee.toUpperCas();
}
/** @type {*} */
const alsoAny = 42;
alsoAny.push("x");jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} {*} and {?}
The Closure Compiler spellings {*} and {?} both mean any to TypeScript. They turn up in older JSDoc; {*} in particular is often meant as "anything" in the sense of unknown. Rewrite them as unknown when you want the check.
Where any comes from without asking
In a .js file, several things are any unless you type them: untyped parameters (reported as TS7006 under strict), JSON.parse results, empty array literals and new Map() without type arguments. Give the JSON.parse result the type unknown right away, as the first example does, and narrow it before use.
Gotchas
anyspreads: a property read from ananyvalue isanytoo, and so is the result of calling it.- Under
strict, acatchvariable is alreadyunknown. unknownin a union swallows the other members:string | unknownis justunknown.
Questions
Should I use any or unknown in JSDoc?
unknown, unless you are deliberately switching checking off for a value. unknown keeps the checks and makes the narrowing explicit.
Sources
- TypeScript 3.0: new unknown top type · TypeScript release notes
- Everyday Types: any · TypeScript handbook
- JSDoc Reference: types · TypeScript handbook
Last reviewed 20 October 2026.