toString() gives computer-style text. toLocaleString() formats a value the way people in a particular country expect to read it: thousands separators, currency symbols, percentages, and dates in the local order. A locale is a language and country code, like "en-US" (English, United States) or "de-DE" (German, Germany).
With no arguments, it uses the reader's own browser settings. For predictable results, name the locale:
const num = 1234567.89;
console.log(num.toLocaleString("en-US"));
console.log(num.toLocaleString("de-DE"));
console.log(num.toLocaleString("no-NO"));
console.log(num.toLocaleString("en-IN")); // India groups digits differently
Germany swaps the comma and the dot. Norway uses spaces. India groups by thousands once, then by hundreds (lakhs and crores).
num.toLocaleString("no-NO") === "1 234 567,89" typed on a keyboard is false. Format for display only; never compare formatted text.
Pass an options object with style: "currency" and a three-letter currency code:
const price = 1299.95;
console.log(price.toLocaleString("en-US", { style: "currency", currency: "USD" }));
console.log(price.toLocaleString("de-DE", { style: "currency", currency: "EUR" }));
console.log(price.toLocaleString("no-NO", { style: "currency", currency: "NOK" }));
console.log(price.toLocaleString("en-US", { style: "currency", currency: "EUR" }));
console.log(price.toLocaleString("ja-JP", { style: "currency", currency: "JPY" }));
The locale decides the layout and the currency decides the symbol: euros in "en-US" still put the symbol first. Yen have no cents, so the last one rounds to a whole number.
style: "currency" on its own is TypeError: Currency code is required with currency style. JavaScript will not guess which money you mean.
style: "percent" multiplies by 100 and adds the % sign, so pass a fraction:
console.log((0.875).toLocaleString("en-US", { style: "percent" }));
console.log((0.875).toLocaleString("en-US", { style: "percent", minimumFractionDigits: 1 }));
console.log((87.5).toLocaleString("en-US", { style: "percent" })); // already a percent!
By default a percentage is rounded to a whole number, which is why 0.875 shows as 88%.
console.log((3.14159).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }));
console.log((5).toLocaleString("en-US", { minimumFractionDigits: 2 }));
console.log((7).toLocaleString("en-US", { minimumIntegerDigits: 2 }));
| minimumFractionDigits | Pad with zeros to at least this many decimals |
| maximumFractionDigits | Round to at most this many decimals |
| minimumIntegerDigits | Pad the whole-number part with leading zeros |
| useGrouping: false | Leave out thousands separators |
toLocaleString, with an e. Typing num.toLocalString() is TypeError: num.toLocalString is not a function.
Like toFixed, the result is text. Do all your math first and format last. Formatted text will not convert back cleanly:
const text = (1234.5).toLocaleString("en-US");
console.log(text, typeof text);
console.log(Number(text)); // the comma breaks it
console.log(Number(text.replaceAll(",", "")));
Dates are where toLocaleString shines. Each locale puts the day, month, and time in its own order:
const d = new Date(2026, 0, 5, 14, 30);
console.log(d.toLocaleString("en-US"));
console.log(d.toLocaleString("en-GB"));
console.log(d.toLocaleDateString("en-US")); // date part only
console.log(d.toLocaleTimeString("en-US")); // time part only
In the UK, 05/01/2026 is the 5th of January. In the US it would mean May 1st. Formatting with the reader's locale avoids that confusion.
const d = new Date(2026, 0, 5);
console.log(d.toLocaleString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" }));
console.log(d.toLocaleString("en-US", { month: "long" }));
console.log(d.toLocaleString("fr-FR", { month: "long" }));
console.log(d.toLocaleString("es-ES", { weekday: "long" }));
This is the easy way to get month and day names in any language, instead of the lookup arrays from JS Date Get.
A date is one exact moment, but the clock reading depends on where you are. The timeZone option chooses where:
const newYear = new Date("2026-01-01"); // a date-only string means midnight UTC
console.log(newYear.toLocaleString("en-US", { timeZone: "UTC" }));
console.log(newYear.toLocaleString("en-US", { timeZone: "America/New_York" }));
new Date("2026-01-01"). That is midnight in London, which is still December 31st, 2025 anywhere in the Americas. A student in Baltimore sees a date from the wrong year. Use new Date(2026, 0, 1) (your local midnight) or pass a timeZone.
On an array, toLocaleString formats each item, then joins them with commas, which can get confusing in locales that also use commas inside numbers:
console.log([1234.5, 99].toLocaleString("en-US"));
console.log([1234.5, 99].toLocaleString("de-DE"));
console.log((123456789012345678901234567890n).toLocaleString("en-US"));
W3Schools shows a clever use: turning a raw byte count into something a person can read.
function fileSize(bytes) {
const opts = { maximumFractionDigits: 1 };
if (bytes < 1024) return bytes + " bytes";
if (bytes < 1024 * 1024) return (bytes / 1024).toLocaleString("en-US", opts) + " KB";
return (bytes / 1024 / 1024).toLocaleString("en-US", opts) + " MB";
}
console.log(fileSize(500));
console.log(fileSize(1536));
console.log(fileSize(1536000));
console.log((1234567).toLocaleString("en-US", { notation: "compact" }));
console.log((42).toLocaleString("en-US", { style: "unit", unit: "kilometer-per-hour" }));
console.log((1234567).toLocaleString("en-US", { useGrouping: false }));
What does this log?
console.log((0.5).toLocaleString("en-US", { style: "percent" }));
50%. The percent style multiplies by 100, so 0.5 means half.Which gives "$1,299.95"?
(1299.95).toLocaleString("en-US")(1299.95).toLocaleString("en-US", { currency: "USD" })(1299.95).toLocaleString("en-US", { style: "currency", currency: "USD" })(1299.95).toFixed(2)Why is Number((1234.5).toLocaleString("en-US")) equal to NaN?
"1,234.5", and Number() does not accept commas. Formatted strings are for people to read, not for math: keep the original number for calculations."en-US", "de-DE", and "fr-FR" and compare the separators"de-DE"), and yenfileSize example with its Try it Yourself button and use it on five numbers of your choice, from bytes up to hundreds of MB