Unveiling JavaScript: Exploring Multiple Options to Format Numbers as Currency

JavaScript has several built-in and third-party ways to format a number as currency. Here are the ones worth knowing, and when to reach for each.


1. toLocaleString: built into JavaScript, formats a number according to the given locale:

const number = 1234567.89;
const formattedCurrency = number.toLocaleString('en-US', { style: 'currency', currency: 'USD' });

console.log(formattedCurrency); // Output: $1,234,567.89

2. Intl.NumberFormat: part of the ECMAScript Internationalization API, gives more control than toLocaleString and is the modern standard for this:

const number = 1234567.89;
const formatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
const formattedCurrency = formatter.format(number);

console.log(formattedCurrency); // Output: $1,234,567.89

3. Manual formatting: if you need full control and don’t want to depend on locale data:

const number = 1234567.89;
const formattedCurrency = `$${number.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,')}`;

console.log(formattedCurrency); // Output: $1,234,567.89

4. External libraries: numeral.js or accounting.js, if you need formatting features beyond what the built-ins offer:

// numeral.js
const number = 1234567.89;
const formattedCurrency = numeral(number).format('$0,0.00');

// accounting.js
const formattedCurrency2 = accounting.formatMoney(number, '$', 2);

Where this actually matters: not every currency formats like USD. Japanese Yen has no decimal places; some European formats use a comma as the decimal separator and a period for thousands. Hardcoding $ and comma-formatting (the manual approach) breaks silently for these — Intl.NumberFormat handles it correctly because it’s locale-aware, not just symbol-aware:

new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY' }).format(1234567.89);
// ¥1,234,568 -- no decimals, correctly rounded

new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(1234567.89);
// 1.234.567,89 € -- comma as decimal separator, period for thousands, symbol after the number

The manual regex approach from option 3 gets none of this right without rewriting the logic per currency — which is exactly the maintenance cost of avoiding the built-in API.


The real trap: doing math on money as a JS number

Everything above is display formatting — turning a number into a string for humans to read. It’s a separate, more consequential problem if you’re doing arithmetic on money values using plain JavaScript numbers, because floating-point can’t represent most decimal fractions exactly:

console.log(0.1 + 0.2);        // 0.30000000000000004, not 0.3
console.log(19.99 * 3);        // 59.96999999999999, not 59.97

For display, that’s invisible — Intl.NumberFormat rounds to 2 decimals and the tiny error disappears. But add up enough transactions, or compare a calculated total against a stored one with ===, and that sub-cent drift becomes a real bug: totals that are off by a cent, or an equality check that fails when it visually shouldn’t. The standard fix is to never store or calculate money as a fractional number — work in integer cents instead (1999 instead of 19.99), do all arithmetic on the integer, and only convert to a decimal-formatted string at the very last step, for display:

const priceInCents = 1999;          // $19.99, stored as an integer
const quantity = 3;
const totalCents = priceInCents * quantity;   // 5997 -- exact, no floating-point drift

const formatted = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' })
    .format(totalCents / 100);      // convert only at the display step
// "$59.97"

For anything beyond simple arithmetic — proper rounding rules, multi-currency conversion math — a dedicated library like dinero.js or currency.js handles this more rigorously than hand-rolled integer math, but the underlying principle is the same one either way: format for display, never compute on the formatted (or raw floating-point) value.


In this post: The real trap: doing math on money · Which one should you actually use?


Intl.NumberFormat gives more explicit control than toLocaleString and is the modern standard for this.

Which one should you actually use?
  • You only need USD formatted for a modern browser — toLocaleString works with zero dependencies.
  • You need consistent formatting across multiple currencies or locales — Intl.NumberFormat is more explicit and reliable than toLocaleString‘s locale handling.
  • You can’t rely on the Internationalization API being available (older environments) — manual formatting with toFixed and a regex is your fallback, but you lose proper locale/currency-symbol handling.
  • You’re already pulling in numeral.js or accounting.js elsewhere in the codebase — stick with it for consistency rather than mixing formatting approaches.

Hope that saves you some time — drop a comment if anything’s unclear.


App Catalog Authentication Automation Backup Compliance Content Type CSS Flows Google Javascript Limitations List Metadata MFA Microsoft Node NodeJs O365 OneDrive Permissions PnP PnPJS Policy PowerApps Power Automate PowerAutomate PowerPlatform PowerShell React ReactJs Rest API Rest Endpoint Security Send an HTTP Request to SharePoint SharePoint SharePoint List SharePoint Modern SharePoint Online SPFX SPO Sync Tags Teams Termstore Versioning

Leave a Comment

Your email address will not be published. Required fields are marked *