Argument of Type ‘number’ Is Not Assignable to Parameter of Type ‘string’ (TS2345)

The error argument of type ‘number’ is not assignable to parameter of type ‘string’ (TS2345) means a number went into a string parameter. Convert it with String(), or widen the parameter to string | number:

formatZip(String(zipCode));   // was formatZip(zipCode)

I ran all of this on TypeScript 7.0.2 and Node.js v22.22.2 in strict mode, so the error text below is what tsc actually printed on my machine.

In this tutorial, I’ll reproduce the error in both directions, show the assignment version (TS2322, type ‘string’ is not assignable to type ‘number’), and walk through each fix: conversions, NaN checks, unions, overloads, generics, form inputs and JSON data.

What causes TS2345: argument of type ‘number’ is not assignable to parameter of type ‘string’?

TypeScript checks each argument against the parameter’s declared type. A ZIP code stored as a number, a price passed to parseFloat(), and a count saved with localStorage.setItem() all trigger it, because each of those parameters is typed string:

function formatZip(zip: string): string {
  return zip.padStart(5, "0");
}

const zipCode = 2134;               // Boston, MA (02134)
formatZip(zipCode);

const price = 19.99;
parseFloat(price);

localStorage.setItem("cartCount", 3);

tsc output:

arg_error.ts(6,11): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
arg_error.ts(9,12): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
arg_error.ts(11,35): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
Command Prompt showing tsc error TS2345: Argument of type 'number' is not assignable to parameter of type 'string' three times
Three calls, three TS2345 errors. The line and column point at the argument that has the wrong type.

Notice the parseFloat(price) case. The value is already a number, so you don’t need parseFloat at all; if you wanted rounding, use toFixed() as shown in rounding to 2 decimals in TypeScript.

The .ts(2345) suffix you see in VS Code is the same error number. TypeScript’s codes don’t change between editors, so searching for TS2345 always lands on this family of argument errors.

Running this code on an older Node.js version? Read my guide on fixing ReferenceError: fetch is not defined in TypeScript first.

Fix it: convert the number to a string

If the function genuinely needs text, convert at the call site. String(), toString() and a template literal all give the same result for a normal number:

function formatZip(zip: string): string {
  return zip.padStart(5, "0");
}

const zipCode = 2134;
console.log("String():       ", formatZip(String(zipCode)));
console.log("toString():     ", formatZip(zipCode.toString()));
console.log("template:       ", formatZip(`${zipCode}`));

const total = 1249.5;
console.log("toFixed(2):     ", "$" + total.toFixed(2));
console.log("toLocaleString: ", total.toLocaleString("en-US", { style: "currency", currency: "USD" }));

const missing: number | null = null;
console.log("String(null):   ", String(missing));

Output:

String():        02134
toString():      02134
template:        02134
toFixed(2):      $1249.50
toLocaleString:  $1,249.50
String(null):    null

I reach for String() by default because it also handles null and undefined without throwing, while missing.toString() would crash. For leading zeros and other padding tricks, see converting a number to a string with leading zeros.

When the string is for display, format it instead of plain converting. toLocaleString() with currency: "USD" adds the dollar sign and commas; more patterns are in formatting numbers with commas in TypeScript.

Type ‘string’ is not assignable to type ‘number’ (TS2322)

The reverse problem is even more common, because so much data arrives as text. When you assign a string to a number variable, TypeScript reports TS2322; when you pass it as an argument, it’s TS2345 again with the types swapped:

let orderTotal: number = 0;
orderTotal = "149.99";

const input = document.querySelector<HTMLInputElement>("#qty")!;
const qty: number = input.value;

function getItemById(id: number): string {
  return `Item ${id}`;
}
const params = new URLSearchParams("?itemId=42");
getItemById(params.get("itemId"));

Math.round("4.5");

tsc output:

assign_error.ts(2,1): error TS2322: Type 'string' is not assignable to type 'number'.
assign_error.ts(5,7): error TS2322: Type 'string' is not assignable to type 'number'.
assign_error.ts(11,13): error TS2345: Argument of type 'string | null' is not assignable to parameter of type 'number'.
  Type 'null' is not assignable to type 'number'.
assign_error.ts(13,12): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
Command Prompt showing tsc errors TS2322 Type 'string' is not assignable to type 'number' and TS2345 for string arguments
Assignments give TS2322; calls give TS2345. params.get() returns string | null, so both parts must be handled.

Here’s the rule of thumb I use: TS2322 is about a variable, property or return value, and TS2345 is about a function argument. The fix is the same, though. Convert the value or correct the declared type.

The getItemById(params.get("itemId")) line is exactly the SharePoint Framework (SPFx) case. An ID read from a query string or a jQuery val() call is text, but list methods like getById() expect a number.

How do I convert a string to a number in TypeScript?

You have four tools, and they disagree on messy input. This table runs each one on the same strings so you can pick the right one:

const samples = ["42", "19.99", "  7  ", "12px", "", "abc", "0x1A"];

console.log("value     Number  parseInt  parseFloat  unary+");
for (const s of samples) {
  const label = JSON.stringify(s).padEnd(9);
  const cols = [Number(s), parseInt(s, 10), parseFloat(s), +s].map((n) => String(n).padEnd(8));
  console.log(label, cols.join("  "));
}

Output:

value     Number  parseInt  parseFloat  unary+
"42"      42        42        42        42      
"19.99"   19.99     19        19.99     19.99   
"  7  "   7         7         7         7       
"12px"    NaN       12        12        NaN     
""        0         NaN       NaN       0       
"abc"     NaN       NaN       NaN       NaN     
"0x1A"    26        0         0         26
Command Prompt output comparing Number, parseInt, parseFloat and unary plus on strings like 12px, empty string and abc in TypeScript
Number() turns an empty string into 0 and rejects “12px”; parseInt keeps the leading 12.

Use Number() or unary + when the whole string must be numeric, and parseInt(s, 10) or parseFloat when you want the leading number from text like “12px”. The full how-to lives in convert a string to a number in TypeScript.

Always pass the radix 10 to parseInt. For decimal prices, converting a string to a double covers the precision side.

Check for NaN before you pass the number on

Conversion removes the compile error, but it can’t make bad input valid. A small helper that returns number | null forces every caller to deal with the failure case:

function toNumber(text: string | null): number | null {
  if (text === null || text.trim() === "") return null;
  const n = Number(text);
  return Number.isNaN(n) ? null : n;
}

function getItemById(id: number): string {
  return `Item ${id}`;
}

const params = new URLSearchParams("?itemId=42&page=abc");
for (const key of ["itemId", "page", "sort"]) {
  const id = toNumber(params.get(key));
  console.log(key.padEnd(7), id === null ? "not a number, skipped" : getItemById(id));
}

console.log("isNaN('abc'):        ", isNaN("abc" as unknown as number));
console.log("Number.isNaN('abc'): ", Number.isNaN("abc"));

Output:

itemId  Item 42
page    not a number, skipped
sort    not a number, skipped
isNaN('abc'):         true
Number.isNaN('abc'):  false

Prefer Number.isNaN() over the global isNaN(). The global one coerces first, so it says true for the string “abc”, while Number.isNaN only returns true for the actual NaN value. More validation patterns are in checking if a string is a number in TypeScript.

I also treat an empty or whitespace-only string as missing, because Number("") is 0, and a silent 0 is worse than an error.

Accept both types with a union, overloads or generics

Sometimes the function should take either type. Don’t silence the error with as any; describe the real contract instead:

// 1) Accept both and narrow with typeof
function formatZip(zip: string | number): string {
  const text = typeof zip === "number" ? String(zip) : zip;
  return text.padStart(5, "0");
}
console.log(formatZip(2134), formatZip("60601"));

// 2) Overloads: number in -> number out, string in -> string out
function double(value: number): number;
function double(value: string): string;
function double(value: number | string): number | string {
  return typeof value === "number" ? value * 2 : value.repeat(2);
}
const a = double(21);      // a: number
const b = double("ab");    // b: string
console.log(a, b);

// 3) Generics: the type comes from the argument
function firstItem<T>(items: T[]): T {
  return items[0];
}
const firstPrice = firstItem([19.99, 5.25]);       // number
const firstCity = firstItem(["Austin", "Denver"]);  // string
console.log(firstPrice.toFixed(2), firstCity.toUpperCase());

Output:

02134 60601
42 abab
19.99 AUSTIN

The union with typeof narrowing is the simplest. Overloads give callers a precise return type, and generics let the argument decide the type.

If you’re unsure what a value is at runtime, checking the type of a variable shows the narrowing options, and the is keyword covers custom type guards.

Generics also work with arrow functions; the syntax is in generic arrow functions in TypeScript.

Form inputs, JSON and API data: where strings sneak in

HTMLInputElement.value is always a string, even for type="number" inputs. Use Number(input.value) or input.valueAsNumber. JSON is trickier, because JSON.parse() returns any and hides the problem completely:

interface ApiProduct {
  name: string;
  price: string;      // the API sends "19.99", not 19.99
}

const body = '[{"name":"Mug","price":"19.99"},{"name":"Filters","price":"5.25"}]';

const loose = JSON.parse(body);              // any: no errors, wrong math
console.log("any total:  ", loose[0].price + loose[1].price);

const products: ApiProduct[] = JSON.parse(body);
const total = products.reduce((sum, p) => sum + Number(p.price), 0);
console.log("typed total:", "$" + total.toFixed(2));

Output:

any total:   19.995.25
typed total: $25.24
Node.js output where JSON.parse any data concatenates string prices into 19.995.25 while the typed version totals $25.24
With any, the prices glue together as text. Typed honestly as string, they’re converted and summed correctly.

Once the interface says price: string, the compiler catches the mistake before it ships, as TS2769 wrapping the familiar string-to-number message:

interface ApiProduct {
  name: string;
  price: string;
}
const products: ApiProduct[] = [{ name: "Mug", price: "19.99" }];
const total: number = products.reduce((sum, p) => sum + p.price, 0);

tsc output:

json_error.ts(6,7): error TS2322: Type 'ApiProduct' is not assignable to type 'number'.
json_error.ts(6,51): error TS2769: No overload matches this call.
  The last overload gave the following error.
    Type 'string' is not assignable to type 'number'.

Type API responses with what the server really sends, not what you wish it sent, and convert in one place. Generating those interfaces is covered in converting JSON to a TypeScript interface.

Number vs string type errors: quick fixes

SituationErrorFix
Number passed to a string parameterTS2345String(n) or a template literal
String assigned to a number variableTS2322Number(s), then check Number.isNaN
input.value used as a numberTS2322Number(input.value) or valueAsNumber
params.get() passed to a number parameterTS2345Handle null, then convert
Function should take bothTS2345string | number, overloads or generics
JSON field is a stringTS2322Type it as string, convert once

Other TypeScript errors and data guides I’d read next:

Frequently asked questions

What does TS2345 mean in TypeScript?

TS2345 means an argument’s type doesn’t match the parameter’s declared type, for example a number passed where a string is expected. Convert the argument or change the parameter type.

What’s the difference between TS2345 and TS2322?

TS2345 is reported for function arguments. TS2322 is reported for assignments, such as storing a string in a variable typed number. Both are fixed the same way.

How do I fix argument of type ‘string’ is not assignable to parameter of type ‘number’?

Convert with Number(value) or parseInt(value, 10), check the result with Number.isNaN(), and then pass it to the function.

Should I use as any to get rid of the error?

No. It hides real bugs, like string prices that concatenate instead of add. Use a conversion, a union type or a type guard so the compiler keeps checking.

Why is input.value a string when the input type is number?

The DOM always exposes value as a string. Use valueAsNumber or Number(input.value) to get a number.

Where are union types and narrowing documented?

See Narrowing in the TypeScript Handbook.