You can format currency to 2 decimals in TypeScript with Intl.NumberFormat: pass style: 'currency' and currency: 'USD', and you get a string like $1,234.50 with the symbol, commas and two decimals:
const usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
usd.format(1234.5); // "$1,234.50"
Every output on this page came from a real run with TypeScript 7.0.2 and Node.js v22.22.2, strict mode on.
Format currency to 2 decimals with Intl.NumberFormat
USD defaults to two decimals, but I set both fraction-digit options when two decimals are a hard rule:
const usd = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
for (const amount of [1234.5, 19.999, 7]) {
console.log(String(amount).padEnd(8), usd.format(amount));
}
Output:
1234.5 $1,234.50
19.999 $20.00
7 $7.00

It rounds for display only. For a rounded value you can do math with, see rounding to 2 decimals in TypeScript.
Reuse one currency formatter instead of creating it every call
Creating the formatter is the slow part. Here’s 100,000 prices formatted both ways:
const opts: Intl.NumberFormatOptions = { style: "currency", currency: "USD" };
const usd = new Intl.NumberFormat("en-US", opts);
const prices = Array.from({ length: 100_000 }, (_, i) => i * 1.25);
function time(label: string, fn: (n: number) => string): void {
const start = performance.now();
for (const p of prices) fn(p);
console.log(label.padEnd(16), (performance.now() - start).toFixed(1), "ms");
}
time("new each call", (n) => new Intl.NumberFormat("en-US", opts).format(n));
time("reused", (n) => usd.format(n));
Output:
new each call 2950.9 ms
reused 47.9 ms
Reuse wins by a wide margin, so create the formatter once at module level and share it across your TypeScript functions.
How do you show negative currency amounts in parentheses?
Add currencySign: 'accounting' to show a loss as ($1,234.50):
const base: Intl.NumberFormatOptions = { style: "currency", currency: "USD" };
const standard = new Intl.NumberFormat("en-US", base);
const accounting = new Intl.NumberFormat("en-US", { ...base, currencySign: "accounting" });
for (const v of [-1234.5, 250]) {
console.log(standard.format(v).padEnd(12), accounting.format(v));
}
Output:
-$1,234.50 ($1,234.50)
$250.00 $250.00

Commas without a symbol are in formatting a number with commas in TypeScript.
Store money as cents, then format to USD
Floats can’t hold 0.10 exactly. Keep integer cents and divide by 100 only when you format:
type Cents = number; // always a whole number of cents
const cart: { name: string; priceCents: Cents; qty: number }[] = [
{ name: "Desk lamp", priceCents: 3999, qty: 2 },
{ name: "USB-C cable", priceCents: 1249, qty: 3 },
];
const usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
const subtotal: Cents = cart.reduce((sum, i) => sum + i.priceCents * i.qty, 0);
const tax: Cents = Math.round(subtotal * 0.0825); // Austin, TX 8.25%
console.log("Subtotal:", usd.format(subtotal / 100));
console.log("Tax: ", usd.format(tax / 100));
console.log("Total: ", usd.format((subtotal + tax) / 100));
console.log("0.10 + 0.10 + 0.10 as floats:", 0.1 + 0.1 + 0.1);
Output:
Subtotal: $117.45
Tax: $9.69
Total: $127.14
0.10 + 0.10 + 0.10 as floats: 0.30000000000000004
The cents totals are exact; the float sum isn’t. It’s the same reduce pattern I use to sum an array in TypeScript.
Build a custom currency layout with formatToParts
Want smaller cents on a price tag? formatToParts() returns labeled pieces, so you never parse the string:
const usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
console.log(usd.formatToParts(-1299.5).map((p) => `${p.type}=${p.value}`).join(" | "));
const tag = usd.formatToParts(1299.5)
.map((p) => (p.type === "fraction" ? `<sup>${p.value}</sup>` : p.value))
.join("");
console.log(tag);
Output:
minusSign=- | currency=$ | integer=1 | group=, | integer=299 | decimal=. | fraction=50
$1,299.<sup>50</sup>
For splitting a plain number, see getting the decimal part of a number in TypeScript.
Format other currencies: EUR, GBP and JPY
The locale sets the separators; the currency code sets the symbol and default decimals:
const show = (locale: string, currency: string) => {
const f = new Intl.NumberFormat(locale, { style: "currency", currency });
console.log(`${locale} ${currency}`, f.format(1234.567), "| digits:", f.resolvedOptions().maximumFractionDigits);
};
show("de-DE", "EUR");
show("ja-JP", "JPY");
try { show("en-US", "US"); } catch (e) { console.log(String(e)); }
Output:
de-DE EUR 1.234,57 € | digits: 2
ja-JP JPY ¥1,235 | digits: 0
RangeError: Invalid currency code : US
Yen uses zero decimals, so don’t force two. A bad ISO 4217 code throws a RangeError that a string type can’t catch.
Why toFixed(2) with a dollar sign is a weak currency format
'$' + n.toFixed(2) looks fine until real data shows up:
const usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
for (const n of [1234.5, -5, 1.005, 8.575]) {
console.log(String(n).padEnd(8), ("$" + n.toFixed(2)).padEnd(10), usd.format(n));
}
Output:
1234.5 $1234.50 $1,234.50
-5 $-5.00 -$5.00
1.005 $1.00 $1.01
8.575 $8.57 $8.58

Padding digits instead? Use formatting a number to 2 digits in TypeScript.
A typed formatUSD helper for TypeScript
Here’s the helper I use. Formatters are built once, and NaN throws instead of printing $NaN:
const base: Intl.NumberFormatOptions = { style: "currency", currency: "USD", minimumFractionDigits: 2 };
const USD = new Intl.NumberFormat("en-US", base);
const USD_ACCOUNTING = new Intl.NumberFormat("en-US", { ...base, currencySign: "accounting" });
interface FormatUSDOptions { accounting?: boolean; fromCents?: boolean }
function formatUSD(amount: number, { accounting = false, fromCents = false }: FormatUSDOptions = {}): string {
if (!Number.isFinite(amount)) throw new RangeError(`formatUSD: not a finite amount (${amount})`);
return (accounting ? USD_ACCOUNTING : USD).format(fromCents ? amount / 100 : amount);
}
console.log(formatUSD(49.9), formatUSD(-12, { accounting: true }), formatUSD(259900, { fromCents: true }));
try { formatUSD(Number("1,299.50")); } catch (e) { console.log((e as Error).message); }
Output:
$49.90 ($12.00) $2,599.00
formatUSD: not a finite amount (NaN)
Number('1,299.50') is NaN because of the comma; see converting a string to a double in TypeScript for parsing input. Strict mode also catches bad calls:
declare function formatUSD(amount: number, opts?: { accounting?: boolean; fromCents?: boolean }): string;
const fromForm = "49.99";
formatUSD(fromForm);
formatUSD(12, { acounting: true });
Output:
currency_helper_bad.ts(4,11): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
currency_helper_bad.ts(5,17): error TS2561: Object literal may only specify known properties, but 'acounting' does not exist in type '{ accounting?: boolean | undefined; fromCents?: boolean | undefined; }'. Did you mean to write 'accounting'?

You’ll find these handy too:
- Number to string with leading zeros in TypeScript
- Format a date with a time zone
- Map an array of objects
- TypeScript key-value pairs
Frequently asked questions
How do I format a number as USD with 2 decimals in TypeScript?
Create new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }) and call format(amount).
How do I show negative dollars in parentheses?
Add currencySign: 'accounting'. -1234.5 then formats as ($1,234.50) in en-US.
Why does toFixed(2) turn 1.005 into 1.00?
Binary stores 1.005 as slightly less, so toFixed rounds down. Intl.NumberFormat printed $1.01 in my run.
Why does my formatter throw a RangeError?
The currency code isn’t valid ISO 4217, such as ‘US’ instead of ‘USD’.
Where are all the Intl.NumberFormat options documented?
See the Intl.NumberFormat() constructor on MDN.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every ex