numbro
A JavaScript library for formatting and manipulating numbers as currency, percentages, bytes, ordinals, and time, with built-in locale support.
Repository Health
Technical Analysis
Numbro is a mature JavaScript library, forked from the original Numeral.js, for turning raw numbers into human-friendly display strings and back again. A single numbro(value) wrapper exposes chainable formatting methods — .format(), .formatCurrency(), .formatTime(), and byte-unit helpers — alongside arithmetic helpers (add, subtract, multiply, divide, difference) that operate on the wrapped value.
Under the hood, numbro delegates to bignumber.js for its arithmetic, which avoids the floating-point rounding artifacts that plain JavaScript number math introduces when formatting currency or percentages. Formatting is driven by a compact format-string syntax (mirroring Numeral.js’s 0,0.00 style tokens) that is parsed once and reused across format, unformat, and validate calls.
A standout feature is its language-pack system: 61 bundled locale files under languages/ register currency symbols, delimiters, abbreviations, and ordinal rules per BCP 47 tag, and numbro.registerLanguage() / numbro.setLanguage() let consuming apps swap locales at runtime or load only the ones they need via loadLanguagesInNode(). This makes numbro a common choice for dashboards and financial UIs that need locale-correct number, currency, and byte-size display without pulling in a full i18n framework.
What You Get
- A single
numbro(value)factory returning a chainable instance with.format(),.formatCurrency(),.formatTime(),.binaryByteUnits(),.decimalByteUnits(), and.byteUnits()output methods - Arithmetic helpers (
add,subtract,multiply,divide,difference,set) that operate on the wrapped value via BigNumber.js for rounding-safe math - A compact, reusable format-string syntax (e.g.
"$0,0.00","0 b","0o") shared acrossformat,unformat, andvalidate - 61 bundled language packs covering currency symbols, thousands/decimal delimiters, abbreviations, and ordinal suffixes, switchable at runtime with
setLanguage() - A
numbro.validate()helper to check whether a string parses cleanly against a given format before committing it back to a number - TypeScript typings (
numbro.d.ts) shipped alongside the CommonJS, ES module, and minified UMD builds produced by Rollup
Common Use Cases
- Formatting currency amounts and percentages consistently across a multi-currency dashboard or invoicing UI
- Converting raw byte counts (file sizes, transfer totals, storage usage) into human-readable binary or decimal units
- Displaying locale-correct number formatting (delimiters, currency symbols, abbreviations) for an internationalized product
- Parsing user-typed numeric input (e.g.
"1,234.50"or"$1.2m") back into a plain number for calculations or storage
Under The Hood
Architecture
The library centers on a single Numbro class defined in src/numbro.js, which wraps a raw value and delegates almost all behavior to sibling modules constructed as factories that close over the numbro singleton (formatter = require("./formatting")(numbro), manipulate = require("./manipulating")(numbro)) so those modules can recursively invoke top-level numbro static methods without a circular import. Formatting concerns are split cleanly by responsibility: formatting.js builds output strings by output type (currency, percent, byte, time, ordinal, number), parsing.js turns format-string tokens into option objects, unformatting.js reverses a formatted string back to a number, validating.js checks strings against formats, and globalState.js centralizes the mutable current-language/current-defaults/current-currency state that the other modules read from. This gives the codebase a clear one-module-per-concern layering with no cross-cutting god object beyond the thin Numbro façade itself, though the module-level mutable global state (current language, defaults) is a coupling point anything touching formatting has to be aware of.
Tech Stack
numbro is plain ES6 JavaScript (CommonJS require/module.exports, no TypeScript source) with a single runtime dependency, bignumber.js (^8 || ^9), used throughout manipulating.js and formatting.js for precision-safe decimal math. The build pipeline is Rollup (@rollup/plugin-commonjs, @rollup/plugin-node-resolve, @rollup/plugin-terser) producing a CommonJS bundle, an ES-module build (dist/es/numbro.js, used as the browser field), and a minified UMD file, with hand-maintained TypeScript typings (numbro.d.ts) shipped alongside rather than generated from source. There’s no server, database, or framework surface at all — it’s a pure formatting/computation library meant to be imported into any JS runtime.
Code Quality
Tests run under Jasmine (npx jasmine), with a large tests/languages/ suite (62 files) that exercises every bundled locale plus a tests/src/ suite (9 files) covering the core formatting/parsing/manipulating modules, and nyc enforces 100% line, branch, statement, and function coverage via coverage:check — a notably strict bar. Separate GitHub Actions workflows run lint, test, and coverage independently, and ESLint runs with custom rules from a project-local eslint_rules/ directory in addition to eslint-plugin-jasmine. Naming is consistent camelCase throughout, error handling is explicit (invalid formats return an "ERROR: invalid format" string rather than throwing or silently coercing), and there is no TypeScript on the source itself — types are maintained by hand in a separate .d.ts file, which is a drift risk the tests don’t catch.
API Design
The public surface is small and consistent: every consumer touches numbro(value) once and then chains .format()/.formatCurrency()/.formatTime()/byte-unit methods or arithmetic methods off the returned instance, and the same format-string syntax works for both format() and the reverse unformat()/validate() operations, which keeps the mental model minimal. The locale system is a genuine strength for developer experience — registering or switching a language is a single setLanguage() call rather than a separate i18n dependency — though the reliance on shared mutable global state for the current language/defaults means format calls are not purely referentially transparent (two calls to .format() with the same instance can differ if global defaults changed in between), a tradeoff worth knowing about before using it in concurrent or multi-tenant formatting contexts.
Used by 4 apps in this directory
HyperDX
Developer Tools · Analytics · Monitoring
Open source observability platform that unifies logs, traces, metrics, and session replays on ClickHouse — now the core of ClickStack.
ILLA Builder
Developer Tools · Low Code Platforms · No Code Platforms
Open-source low-code platform for building internal tools with drag-and-drop UI, reactive data bindings, and real-time collaboration.
Notifuse
Marketing
Open-source, self-hosted alternative to Mailchimp, Brevo, and Klaviyo — send newsletters and transactional emails without per-email pricing or vendor lock-in.
Uptrace
Monitoring · Devops
Unified open-source APM that collects OpenTelemetry traces, metrics, and logs into a single self-hosted platform backed by ClickHouse.