error TS2322: Type 'string' is not assignable to type 'number'. is the checker's most common error. It appears when a value is stored somewhere whose type was declared: a variable with @type, a property of an object typed with a @typedef, or the value a function returns under @returns. The first type in the message is the value you wrote; the second is the type the annotation promised.
In a JavaScript project the declared type almost always comes from a JSDoc comment, so the fix is either in the value (convert it, correct it) or in the comment (the type was too narrow). The error never changes what runs: it tells you the code and its documentation disagree.
At a glance
| Code | TS2322 |
|---|---|
| Message | Type '{0}' is not assignable to type '{1}'. |
| TypeScript's category | error |
| Listed under | Types do not match |
| Turned on by | No option: every checked file reports it |
| 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} and {1} are filled in by tsc for each error.
What it looks like
/** @typedef {{ sku: string, name: string, price: number }} Product */
/** @type {Product} */
export const tee = {
sku: "TEE-01",
name: "Cotton tee",
price: "19.99",error TS2322: Type 'string' is not assignable to type 'number'.
};
/** @type {number} */
let quantity = 1;
quantity = "2";error TS2322: Type 'string' is not assignable to type 'number'.
export { quantity };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /**
* @param {number[]} prices
* @returns {number}
*/
export function cartTotal(prices) {
const total = prices.reduce((sum, price) => sum + price, 0);
return total.toFixed(2);error TS2322: Type 'string' is not assignable to type 'number'.
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} What causes it
- A
@typeor@typedefsays a variable or property holds one type, and the code puts a value of another type there (a string where a number is declared,nullwherenullis not allowed). - A function's
@returnstag names a type and areturnstatement hands back something else. - With
strictNullCheckson (part ofstrict), a value that may beundefinedornullis stored where the type does not include them. The message then namesundefinedornullas the type that is not assignable.
How to fix it
Fix or convert the value
When the annotation is right, change the value: write the number as a number, or convert text with Number(...) before storing it. A return that formats a number for display (toFixed returns a string) belongs in a separate function whose @returns says string.
/** @typedef {{ sku: string, name: string, price: number }} Product */
/** @type {Product} */
export const tee = {
sku: "TEE-01",
name: "Cotton tee",
price: 19.99,
};
/** @type {number} */
let quantity = 1;
quantity = Number("2");
/**
* @param {number[]} prices
* @returns {number}
*/
export function cartTotal(prices) {
const total = prices.reduce((sum, price) => sum + price, 0);
return Math.round(total * 100) / 100;
}
/**
* @param {number[]} prices
* @returns {string}
*/
export function cartTotalLabel(prices) {
return cartTotal(prices).toFixed(2);
}
export { quantity };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Or widen the declared type
When the value is right and the comment is too strict, change the comment: @type {number | string} for a value that really can be either, @returns {string} when the function's job is to format. A union is honest documentation; it also makes every caller handle both cases.
Read the whole message for objects
For objects and arrays the first line names the outer types and the indented lines underneath say which property differs. In the editor the squiggle sits on the property itself, which is usually the line to change.
Where the error is reported
For an object literal typed with @type, TypeScript reports the property that does not fit, not the whole object. For an assignment it reports the variable on the left. For a return it reports the returned expression. The examples show all three.
Gotchas
- A cast (
/** @type {number} */ (value)) silences the error only when the two types overlap; between unrelated types such asstringandnumberthe cast itself is an error (TS2352). Converting the value is almost always the better fix. - Missing or extra properties have their own messages: TS2741 and TS2739 for missing ones, TS2353 for unknown ones in an object literal.
- When the value is a function argument the same mismatch is reported as TS2345 instead.
Questions
Why does my .js file get TS2322 when JavaScript allows any value anywhere?
Because the file is type-checked (checkJs or // @ts-check) and a JSDoc comment declared a type. Without the comment the variable's type is whatever it was first given, and later assignments of another type are reported the same way.
Sources
- Everyday Types · TypeScript handbook
- JSDoc Reference · TypeScript handbook
- diagnosticMessages.json (TypeScript 5.9.3) · TypeScript on GitHub
Last reviewed 20 October 2026.