Intl.NumberFormat API
Format numbers for any locale with JavaScript's built-in internationalization API
Introduction
Consider displaying the number 1234567.89 in your application. Using toString() produces "1234567.89", which is difficult to read and assumes everyone uses periods for decimals and reads numbers left-to-right. Americans expect "1,234,567.89", Germans expect "1.234.567,89", and users in India expect "12,34,567.89" with different grouping rules.
The Intl.NumberFormat API solves this by formatting numbers according to locale-specific conventions. It handles thousands separators, decimal points, digit grouping, currency symbols, percentage signs, measurement units, and numbering systems. This eliminates the need for manual string manipulation or third-party libraries.
This guide explains how to use Intl.NumberFormat to format numbers correctly for users worldwide, starting with basic usage and progressing to advanced features like currency formatting, compact notation, and custom rounding modes.
Format a number with default settings
Create a formatter by calling new Intl.NumberFormat() with a locale string, then call its format() method with a number.
const formatter = new Intl.NumberFormat('en-US');
formatter.format(1234567.89);
// "1,234,567.89"
The formatter adds thousands separators and formats the decimal point according to the locale. Without specifying a locale, the formatter uses the runtime's default locale, typically based on the user's system settings.
const formatter = new Intl.NumberFormat('de-DE');
formatter.format(1234567.89);
// "1.234.567,89"
German conventions use periods for thousands separators and commas for decimal points, the reverse of American conventions. The formatter handles these differences automatically.
Understand locale codes
A locale code identifies a language and optionally a region, written as language-REGION. The language uses a two-letter ISO 639-1 code like en or es. The region uses a two-letter ISO 3166-1 code like US or MX.
new Intl.NumberFormat('en-US').format(1234.56);
// "1,234.56" (American English)
new Intl.NumberFormat('en-GB').format(1234.56);
// "1,234.56" (British English)
new Intl.NumberFormat('es-ES').format(1234.56);
// "1234,56" (European Spanish)
new Intl.NumberFormat('es-MX').format(1234.56);
// "1,234.56" (Mexican Spanish)
Both English variants use the same formatting, but Spanish variants differ. European Spanish omits thousands separators for four-digit numbers and uses commas for decimals, while Mexican Spanish follows American conventions.
Choose locales based on your users' locations or language preferences. Applications typically determine locale from user settings, browser language, or IP geolocation.
Choose a formatting style
The style option determines the formatting category. Pass an options object as the second argument to the constructor.
new Intl.NumberFormat('en-US', {
style: 'decimal'
}).format(1234.56);
// "1,234.56"
The decimal style is the default. The other styles are currency, percent, and unit.
Format currency with symbols and codes
The currency style requires a currency option with an ISO 4217 currency code.
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(1234.56);
// "$1,234.56"
The formatter adds the dollar sign and formats to two decimal places by default, the standard for most currencies. Different locales position the symbol differently.
new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR'
}).format(1234.56);
// "1.234,56 €"
German formatting places the euro symbol after the amount with a space. The currency option determines which currency to display, not which locale's conventions to follow. The locale determines the formatting conventions, while the currency determines the symbol.
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(1234.56);
// "€1,234.56"
American formatting conventions with the euro symbol produce a euro sign before the amount, following American rather than European placement conventions.
Control currency display format
The currencyDisplay option changes how the currency appears in the formatted string.
const amount = 1234.56;
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencyDisplay: 'symbol'
}).format(amount);
// "$1,234.56"
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencyDisplay: 'code'
}).format(amount);
// "USD 1,234.56"
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencyDisplay: 'name'
}).format(amount);
// "1,234.56 US dollars"
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencyDisplay: 'narrowSymbol'
}).format(amount);
// "$1,234.56"
The symbol option is the default and shows the currency symbol like $ or €. The code option shows the three-letter currency code. The name option spells out the currency name. The narrowSymbol option uses a locale's narrow currency symbol, which may be ambiguous but saves space.
The difference between symbol and narrowSymbol becomes apparent with currencies that share symbols.
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'CAD',
currencyDisplay: 'symbol'
}).format(100);
// "CA$100.00"
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'CAD',
currencyDisplay: 'narrowSymbol'
}).format(100);
// "$100.00"
Canadian dollars show as CA$ with the symbol option to distinguish them from US dollars, but show as just $ with narrowSymbol.
Format negative currency amounts with accounting notation
The currencySign option controls how negative amounts appear.
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencySign: 'standard'
}).format(-1234.56);
// "-$1,234.56"
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencySign: 'accounting'
}).format(-1234.56);
// "($1,234.56)"
The standard option is the default and uses a minus sign. The accounting option wraps negative amounts in parentheses, following accounting conventions. This makes negative numbers more visually distinct in financial reports.
Format percentages
The percent style multiplies the number by 100 and adds a percent sign.
new Intl.NumberFormat('en-US', {
style: 'percent'
}).format(0.1234);
// "12%"
new Intl.NumberFormat('en-US', {
style: 'percent'
}).format(0.1256);
// "13%"
The formatter rounds to the nearest integer by default. Control decimal places with digit options.
new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: 2
}).format(0.1234);
// "12.34%"
Pass the decimal form of the percentage, not the multiplied form. The formatter handles the multiplication.
Format measurements with units
The unit style requires a unit option with a unit identifier.
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'kilometer-per-hour'
}).format(100);
// "100 km/h"
new Intl.NumberFormat('en-GB', {
style: 'unit',
unit: 'mile-per-hour'
}).format(100);
// "100 mph"
Available units include length measurements (meter, kilometer, mile), time durations (second, minute, hour), digital storage (byte, kilobyte, megabyte), temperatures (celsius, fahrenheit), and many others. Compound units like kilometer-per-hour combine simple units with hyphens.
The unitDisplay option controls the unit format.
const distance = 1234.5;
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'kilometer',
unitDisplay: 'long'
}).format(distance);
// "1,234.5 kilometers"
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'kilometer',
unitDisplay: 'short'
}).format(distance);
// "1,234.5 km"
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'kilometer',
unitDisplay: 'narrow'
}).format(distance);
// "1,234.5km"
The long option spells out the unit name. The short option uses abbreviations. The narrow option uses the most compact form, which may be ambiguous.
Display large numbers with compact notation
The notation option changes how numbers are expressed. The compact value uses locale-specific short forms for large numbers.
new Intl.NumberFormat('en-US', {
notation: 'compact'
}).format(1234567);
// "1.2M"
new Intl.NumberFormat('en-US', {
notation: 'compact'
}).format(987654321);
// "988M"
Compact notation rounds to one decimal place by default and adds suffixes like K for thousands, M for millions, and B for billions. This format appears in social media follower counts, video view counts, and analytics dashboards.
The compactDisplay option controls the suffix length.
new Intl.NumberFormat('en-US', {
notation: 'compact',
compactDisplay: 'short'
}).format(1234567);
// "1.2M"
new Intl.NumberFormat('en-US', {
notation: 'compact',
compactDisplay: 'long'
}).format(1234567);
// "1.2 million"
The short option is the default and uses symbols. The long option spells out the magnitude word. Different locales use different suffixes.
new Intl.NumberFormat('zh-CN', {
notation: 'compact'
}).format(123456789);
// "1.2亿"
Chinese uses 亿 for hundred millions, reflecting the language's numerical grouping system.
Express very large or small numbers in scientific notation
The scientific notation expresses numbers as a coefficient multiplied by a power of ten.
new Intl.NumberFormat('en-US', {
notation: 'scientific'
}).format(123456789);
// "1.235E8"
new Intl.NumberFormat('en-US', {
notation: 'scientific'
}).format(0.00000123);
// "1.23E-6"
This format works well for very large numbers (astronomical distances, molecular counts) and very small numbers (particle masses, nanoscale measurements). The exponent always appears as a multiple of one.
Use engineering notation for technical applications
The engineering notation is similar to scientific notation but constrains exponents to multiples of three.
new Intl.NumberFormat('en-US', {
notation: 'engineering'
}).format(123456789);
// "123.457E6"
new Intl.NumberFormat('en-US', {
notation: 'engineering'
}).format(1234);
// "1.234E3"
Engineering notation aligns with SI unit prefixes (kilo, mega, giga), making it standard in engineering and physics contexts. The coefficient ranges from 1 to 999.
Control decimal places with fraction digits
The minimumFractionDigits and maximumFractionDigits options control how many digits appear after the decimal point.
new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(1234.5);
// "1,234.50"
new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(1234.567);
// "1,234.57"
The minimum ensures trailing zeros appear when needed. The maximum rounds longer decimals. Currency formatters default to two decimal places. Decimal formatters default to zero minimum and three maximum.
new Intl.NumberFormat('en-US', {
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(1234.567);
// "1,235"
Setting both to zero rounds to the nearest integer.
Control overall precision with significant digits
The minimumSignificantDigits and maximumSignificantDigits options control total precision regardless of decimal point position.
new Intl.NumberFormat('en-US', {
minimumSignificantDigits: 3,
maximumSignificantDigits: 3
}).format(1234.567);
// "1,230"
new Intl.NumberFormat('en-US', {
minimumSignificantDigits: 3,
maximumSignificantDigits: 3
}).format(0.001234);
// "0.00123"
Significant digits count all digits except leading zeros. The first example rounds to three digits, producing 1230. The second example keeps three digits after the leading zeros, producing 0.00123.
Significant digit options override fraction digit options when both are specified.
Choose a rounding strategy with rounding modes
The roundingMode option determines how numbers round when truncation is necessary.
const value = 1.5;
new Intl.NumberFormat('en-US', {
maximumFractionDigits: 0,
roundingMode: 'halfExpand'
}).format(value);
// "2"
new Intl.NumberFormat('en-US', {
maximumFractionDigits: 0,
roundingMode: 'halfTrunc'
}).format(value);
// "1"
The halfExpand mode is the default and rounds 0.5 away from zero, a common rounding strategy taught in schools. The halfTrunc mode rounds 0.5 toward zero.
Other modes include:
ceil: Always round toward positive infinityfloor: Always round toward negative infinityexpand: Always round away from zerotrunc: Always round toward zerohalfCeil: Round 0.5 toward positive infinityhalfFloor: Round 0.5 toward negative infinityhalfEven: Round 0.5 toward the nearest even number
const prices = [1.5, 2.5, 3.5];
prices.map(price =>
new Intl.NumberFormat('en-US', {
maximumFractionDigits: 0,
roundingMode: 'halfEven'
}).format(price)
);
// ["2", "2", "4"]
The halfEven mode, also called banker's rounding, reduces rounding bias in repeated calculations. When rounding 0.5, it chooses the nearest even number. This produces 2 for both 1.5 and 2.5, but 4 for 3.5.
Financial applications use ceil for rounding up charges and floor for rounding down refunds. Statistical applications use halfEven to minimize cumulative rounding errors.
Control thousands separators with grouping options
The useGrouping option controls whether thousands separators appear.
new Intl.NumberFormat('en-US', {
useGrouping: true
}).format(123456);
// "123,456"
new Intl.NumberFormat('en-US', {
useGrouping: false
}).format(123456);
// "123456"
The true value is the default. The false value removes all separators. String values provide finer control.
new Intl.NumberFormat('en-US', {
useGrouping: 'always'
}).format(1234);
// "1,234"
new Intl.NumberFormat('en-US', {
useGrouping: 'min2'
}).format(1234);
// "1234"
The always value uses separators in all cases. The min2 value omits separators for four-digit numbers. The auto value follows locale preferences, which typically match min2 behavior.
Compact notation defaults to min2 because compact numbers rarely need internal separators.
Show signs explicitly with sign display options
The signDisplay option controls when positive and negative signs appear.
new Intl.NumberFormat('en-US', {
signDisplay: 'auto'
}).format(100);
// "100"
new Intl.NumberFormat('en-US', {
signDisplay: 'always'
}).format(100);
// "+100"
The auto value is the default and shows minus signs for negative numbers but not plus signs for positive numbers. The always value shows both.
new Intl.NumberFormat('en-US', {
signDisplay: 'exceptZero'
}).format(0);
// "0"
new Intl.NumberFormat('en-US', {
signDisplay: 'always'
}).format(0);
// "+0"
The exceptZero value omits signs for zero values even with always behavior. This prevents confusing +0 and -0 displays.
new Intl.NumberFormat('en-US', {
signDisplay: 'never'
}).format(-100);
// "100"
The never value removes signs entirely, showing only the absolute value.
Financial applications use always to highlight gains with plus signs. Temperature displays use exceptZero to avoid showing +0° or -0°.
Break formatted output into parts
The formatToParts() method returns an array of objects representing each component of the formatted string.
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).formatToParts(1234.56);
This returns:
[
{ type: 'currency', value: '$' },
{ type: 'integer', value: '1' },
{ type: 'group', value: ',' },
{ type: 'integer', value: '234' },
{ type: 'decimal', value: '.' },
{ type: 'fraction', value: '56' }
]
Each object has a type identifying the component and a value containing the string. The type can be currency, integer, group, decimal, fraction, literal, minusSign, plusSign, percentSign, or others.
This enables custom styling of individual components.
const parts = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).formatToParts(1234.56);
const formatted = parts.map(part => {
if (part.type === 'currency') {
return `<span class="currency">${part.value}</span>`;
}
if (part.type === 'integer') {
return `<span class="integer">${part.value}</span>`;
}
return part.value;
}).join('');
// <span class="currency">$</span><span class="integer">1</span>,<span class="integer">234</span>.56
This produces HTML with styled components. The same approach works for applying different colors to negative amounts, enlarging currency symbols, or animating individual digits.
Format number ranges
The formatRange() method formats two numbers as a range.
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).formatRange(100, 200);
// "$100.00 – $200.00"
The formatter uses a locale-specific range separator (an en dash in English) and includes both currency symbols. If the values format to the same string, the formatter returns a single value with a tilde.
new Intl.NumberFormat('en-US', {
notation: 'compact'
}).formatRange(1200, 1800);
// "~1K"
Both 1200 and 1800 format as 1K in compact notation, so the formatter shows approximately 1K.
The formatRangeToParts() method returns parts for the range.
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).formatRangeToParts(100, 200);
This returns:
[
{ type: 'currency', value: '$', source: 'startRange' },
{ type: 'integer', value: '100', source: 'startRange' },
{ type: 'decimal', value: '.', source: 'startRange' },
{ type: 'fraction', value: '00', source: 'startRange' },
{ type: 'literal', value: ' – ', source: 'shared' },
{ type: 'currency', value: '$', source: 'endRange' },
{ type: 'integer', value: '200', source: 'endRange' },
{ type: 'decimal', value: '.', source: 'endRange' },
{ type: 'fraction', value: '00', source: 'endRange' }
]
The source property identifies whether the part comes from the start value, end value, or range separator.
Price ranges, date ranges, and measurement ranges use this method for proper locale-sensitive formatting.
Inspect resolved options
The resolvedOptions() method returns an object showing the actual options used by the formatter.
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
formatter.resolvedOptions();
This returns:
{
locale: 'en-US',
numberingSystem: 'latn',
style: 'currency',
currency: 'USD',
currencyDisplay: 'symbol',
currencySign: 'standard',
minimumIntegerDigits: 1,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
useGrouping: 'auto',
notation: 'standard',
signDisplay: 'auto',
roundingMode: 'halfExpand'
}
The object includes explicitly set options and default values. The locale may differ from the requested locale if the system resolved to a different but compatible locale. The numberingSystem shows which digit characters the formatter uses.
This method helps debug unexpected formatting by revealing all active settings.
Reuse formatters for better performance
Creating a NumberFormat instance involves locale data loading and option processing. Reuse instances when formatting multiple values with the same settings.
// Inefficient: creates new formatter for each value
prices.map(price =>
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(price)
);
// Efficient: creates formatter once
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
prices.map(price => formatter.format(price));
The second approach is significantly faster when formatting many values. Create formatters outside loops and component render functions.
Modern JavaScript engines cache NumberFormat instances internally, but explicit reuse provides better performance and clearer code.
Check browser support
The Intl.NumberFormat API is supported in all modern browsers. Chrome, Firefox, Safari, and Edge have supported the basic API since 2016. Advanced features like formatRange(), formatRangeToParts(), and extended roundingMode options gained support more recently but are now available in current browser versions.
Check for support using:
if (typeof Intl !== 'undefined' && Intl.NumberFormat) {
// NumberFormat is supported
const formatter = new Intl.NumberFormat('en-US');
}
Check for specific features:
const formatter = new Intl.NumberFormat('en-US');
if (typeof formatter.formatRange === 'function') {
// formatRange is supported
}
Applications requiring support for older browsers can use polyfills like @formatjs/intl-numberformat, but most modern applications can use the native API without fallbacks.
When to use NumberFormat
Use Intl.NumberFormat for:
- Displaying numbers to users in any UI context
- Formatting currency amounts in e-commerce applications
- Showing percentages in analytics dashboards
- Displaying follower counts, view counts, and other social metrics
- Formatting measurements and scientific values
- Building internationalized applications that support multiple locales
Do not use Intl.NumberFormat for:
- Internal calculations or data processing
- Storing values in databases
- Serializing data for APIs
- Parsing user input back to numbers
NumberFormat is a presentation layer tool. Keep raw numeric values in calculations and storage, applying formatting only when displaying values to users.