name: dinerojs description: >- Use this skill when working with monetary values in JavaScript or TypeScript. Covers Dinero.js v2 — immutable, integer-based money objects with add, subtract, multiply, allocate, convert, and formatting functions. Reach for this skill whenever the task involves prices, invoices, carts, tax calculations, currency conversion, payment gateway integration, or any scenario where floating-point math would lose cents. Also use for bigint amounts, cryptocurrency values, and Intl currency formatting recipes.
Dinero.js
Immutable, functional library for monetary values in JS/TS. Integer-based math (no floats).
npm install dinero.js. Node 14+, ES modules, tree-shakeable.
TL;DR
- What: create
Dineroobjects from an integer amount + currency, then transform them with pure functions (add,multiply,convert,toDecimal, …). - Best for: prices, invoices, carts, tax, currency conversion, payment gateway integrations, anywhere float math would lose cents.
- Mental model:
{ amount: integer, currency: { code, base, exponent }, scale }.amountis stored in minor units (500 USD = $5.00).scaleis precision; defaults to currency exponent, auto-grows during calculations to preserve precision. - Anti-use: if you just need to display a static number, use
Intl.NumberFormat. If you need arbitrary-precision decimals with symbolic math, usebig.jsordecimal.jsdirectly.
Setup
// Standard (number amounts, safe up to ~$90T)
import { add, dinero, multiply, subtract, toDecimal } from "dinero.js";
import { EUR, USD } from "dinero.js/currencies";
const price = dinero({ amount: 5000, currency: USD }); // $50.00
const total = add(price, dinero({ amount: 1000, currency: USD })); // $60.00
toDecimal(total); // "60.00"
// bigint variant (for crypto, huge amounts, high-exponent currencies)
import { add, dinero } from "dinero.js/bigint";
import { USD } from "dinero.js/bigint/currencies"; // MUST use bigint currencies
const d = dinero({ amount: 500n, currency: USD });
Entry points
| Import path | Contents |
|---|---|
dinero.js |
Core functions (number amounts) |
dinero.js/currencies |
ISO 4217 currency objects for number |
dinero.js/bigint |
Core functions (bigint amounts) + calculator, createDinero |
dinero.js/bigint/currencies |
ISO 4217 currency objects for bigint |
UMD / CDN
<script
src="https://cdn.jsdelivr.net/npm/dinero.js/dist/umd/index.production.js"
></script>
<script>
const { dinero, add, USD } = window.dinerojs;
</script>
Mental model
Dinero object shape
// toSnapshot(d) always returns:
{ amount: 500, currency: { code: 'USD', base: 10, exponent: 2 }, scale: 2 }
- amount: integer in minor units.
amount / base^scale= human value.500 / 10^2 = 5.00. - currency.exponent: how many minor units per major unit (USD=2, JPY=0, BHD=3).
- scale: precision of this object. Defaults to
currency.exponent. Grows during math (never shrinks except viatrimScale). - currency.base: radix.
10for decimal currencies;5for MGA/MRU;[20, 12]for pre-decimal GBP (array = multiple subdivisions).
Core invariants
- All objects are immutable. Every function returns a new one.
- Amounts are always integers. Never pass floats. For fractional values
(rates, percentages, multipliers), use a
DineroScaledAmount:{ amount: 89, scale: 2 }means 0.89. - Operations normalize scales to the highest of the inputs before calculating; result keeps that scale.
- Same-currency required for
add/subtract/equal/compare/min/max/haveSameAmount/normalizeScale. Throws at runtime; with typed currencies also rejected at compile time.
Currency objects
// Built-in (ISO 4217)
import { USD } from 'dinero.js/currencies';
// USD = { code: 'USD', base: 10, exponent: 2 }
// Custom (decimal)
const FRF = { code: 'FRF', base: 10, exponent: 2 };
// Custom (non-decimal, single subdivision — set exponent: 1)
const MRU = { code: 'MRU', base: 5, exponent: 1 };
// Custom (multiple subdivisions: 1 pound = 20 shillings, 1 shilling = 12 pence)
const GBP_OLD = { code: 'GBP', base: [20, 12], exponent: 1 };
// Typed (enables currency-mismatch compile errors)
import type { DineroCurrency } from 'dinero.js';
const FRF = {
code: 'FRF', base: 10, exponent: 2,
} as const satisfies DineroCurrency<number, 'FRF'>;
Constructor
dinero({ amount, currency, scale? }) — Dinero<TAmount, TCurrency>
Create a Dinero object.
dinero({ amount: 500, currency: USD }); // $5.00
dinero({ amount: 35, currency: USD, scale: 3 }); // $0.035
dinero({ amount: 5000n, currency: USD }); // bigint variant
dinero({ amount: 12000, currency: GBP_OLD }); // 50 pre-decimal pounds
dinero(toSnapshot(existingD)); // restore from snapshot
| Param | Type | Required | Notes |
|---|---|---|---|
amount |
TAmount |
yes | Integer in minor units. |
currency |
DineroCurrency<TAmount> |
yes | Must match amount type (number vs bigint). |
scale |
TAmount |
no | Defaults to currency.exponent. |
Gotchas: passing a float amount (e.g., 19.99) is invalid — convert first
(Math.round(19.99 * 100) → 1999). For bigint, base/exponent in currency must
also be bigint — never mix dinero.js/currencies with dinero.js/bigint.
Mutations
add(augend, addend) — Dinero
Adds two Dineros. Same-currency required.
add(d1, d2);
// Sum many:
[d1, d2, d3].reduce(add);
subtract(minuend, subtrahend) — Dinero
Subtracts. Same-currency required.
subtract(d1, d2);
[d1, d2, d3].reduce(subtract); // d1 - d2 - d3
multiply(multiplicand, multiplier, divide?) — Dinero
Multiply by integer or scaled amount. Third arg is rounding mode for when result isn't integer.
multiply(d, 4); // integer
multiply(d, { amount: 2001, scale: 3 }); // 2.001 (scale grows)
multiply(d, { amount: 21, scale: 1 }, halfEven); // banker's rounding
multiply(d, 0.5); // works ONLY if result is integer, else throws
Gotchas: floats throw if result isn't integer. Use scaled amounts
({ amount, scale }) for fractional multipliers — result's scale auto-grows.
allocate(dineroObject, ratios) — Dinero[]
Split amount across ratios, distributing remainder fairly (no money is lost).
allocate(d, [50, 50]); // percentages
allocate(d, [1, 3]); // ratios
allocate(d, [0, 50, 50]); // zero ratios → amount 0
allocate(d, [ // scaled ratios for fractional (50.5 / 49.5)
{ amount: 505, scale: 1 },
{ amount: 495, scale: 1 },
]);
Gotchas: ratios must be positive, can't pass ONLY zeros. Remainder is distributed left-to-right so first non-zero bucket gets the extra cent. Use for tax splits, revenue shares, dividing a bill.
Conversions (currency & scale)
convert(dineroObject, newCurrency, rates) — Dinero<TAmount, TNewCurrency>
Convert to another currency using a rates map keyed by target code.
const rates = { EUR: { amount: 89, scale: 2 } }; // 1 USD = 0.89 EUR
convert(d_usd, EUR, rates);
// Reusable converter via closure
const createConverter = (rates) => (d, target) => convert(d, target, rates);
Gotchas: currencies must share the same base — converting USD(base=10) ↔
MGA(base=5) throws. Use scaled amounts for fractional rates, never floats.
normalizeScale([d1, d2, ...]) — Dinero[]
Bring all objects to the highest scale in the set.
const [a, b] = normalizeScale([
dinero({ amount: 100, currency: USD, scale: 2 }),
dinero({ amount: 2000, currency: USD, scale: 3 }),
]); // both emerge at scale 3
transformScale(dineroObject, newScale, divide?) — Dinero
Force a new scale. Shrinking loses precision → pass a rounding mode.
transformScale(d, 4); // grow scale, no loss
transformScale(d, 2, halfEven); // shrink, round half-to-even
// Default divide is `down` if omitted.
trimScale(dineroObject) — Dinero
Drop trailing precision down to the currency exponent (no info loss).
trimScale(dinero({ amount: 500000, currency: USD, scale: 5 }));
// → amount 500, scale 2
Rounding modes
All used as the last arg of multiply, allocate, or transformScale.
| Fn | Behavior | 1.5 | 2.5 | -1.5 |
|---|---|---|---|---|
up |
toward +∞ | 2 | 3 | -1 |
down |
toward -∞ (default for transformScale) |
1 | 2 | -2 |
halfUp |
half → +∞ (classic) | 2 | 3 | -1 |
halfDown |
half → -∞ | 1 | 2 | -2 |
halfEven |
half → nearest even (banker's, reduces bias) | 2 | 2 | -2 |
halfOdd |
half → nearest odd | 1 | 3 | -1 |
halfTowardsZero |
half → 0 | 1 | 2 | -1 |
halfAwayFromZero |
half → away from 0 ("commercial") | 2 | 3 | -2 |
import { halfEven, multiply } from "dinero.js";
multiply(d, { amount: 21, scale: 1 }, halfEven);
Comparisons
All are pure predicates or selectors. Same-currency required for everything
except hasSubUnits, isZero/Positive/Negative, haveSameCurrency.
| Fn | Signature | Returns | Notes |
|---|---|---|---|
equal |
(d1, d2) |
boolean |
Same-value after scale normalization. |
compare |
(d1, d2) |
-1 | 0 | 1 |
For .sort(compare). |
greaterThan |
(d1, d2) |
boolean |
|
greaterThanOrEqual |
(d1, d2) |
boolean |
|
lessThan |
(d1, d2) |
boolean |
|
lessThanOrEqual |
(d1, d2) |
boolean |
|
minimum |
([d, ...]) |
Dinero |
Takes an array. |
maximum |
([d, ...]) |
Dinero |
Takes an array. |
haveSameAmount |
([d, ...]) |
boolean |
Same-value across a set. |
haveSameCurrency |
([d, ...]) |
boolean |
Works across different currencies. |
isZero |
(d) |
boolean |
|
isPositive |
(d) |
boolean |
Zero → false. |
isNegative |
(d) |
boolean |
Zero → false. |
hasSubUnits |
(d) |
boolean |
True if amount has fractional major units. |
compare(
dinero({ amount: 800, currency: USD }),
dinero({ amount: 500, currency: USD }),
); // 1
[d1, d2, d3].sort(compare); // low → high
[d1, d2, d3].sort((a, b) => compare(b, a)); // high → low
minimum([d1, d2, d3]); // pass an array
Formatting
toDecimal(dineroObject, transformer?) — string (or TOutput)
Stringifies amount at the object's scale. No currency symbol — you compose that.
toDecimal(dinero({ amount: 1050, currency: USD })); // "10.50"
toDecimal(d, ({ value, currency }) => `${currency.code} ${value}`); // "USD 10.50"
Gotchas: only works for single-based decimal currencies (fails for
base: [20,12]). Scale is fixed by the object — to change decimals in output,
do it in the transformer (Number(value).toFixed(1)).
toUnits(dineroObject, transformer?) — TAmount[] (or TOutput)
Splits amount into per-subdivision units ([major, minor, ...]). Works for any
currency.
toUnits(dinero({ amount: 1050, currency: USD })); // [10, 50]
toUnits(
dinero({
amount: 267,
currency: { code: "GBP", base: [20, 12], exponent: 1 },
}),
);
// [1, 2, 3] — 1 pound, 2 shillings, 3 pence
toUnits(d, ({ value }) => `${value[0]} dollars, ${value[1]} cents`);
toSnapshot(dineroObject) — DineroSnapshot
Plain JS object for transport, storage, or inspection. Round-trip:
dinero(toSnapshot(d)) rebuilds.
toSnapshot(dinero({ amount: 500, currency: USD }));
// { amount: 500, currency: { code: 'USD', base: 10, exponent: 2 }, scale: 2 }
Recipes
Intl currency formatting (symbols, locales)
const intlFormat = (d, locale, opts = {}) =>
toDecimal(
d,
({ value, currency }) =>
Number(value).toLocaleString(locale, {
...opts,
style: "currency",
currency: currency.code,
}),
);
intlFormat(d, "en-US"); // "$10.00"
intlFormat(d, "fr-CA"); // "10,00 $ US"
Calculate a percentage (e.g., 15% tax)
// Option A: allocate
const [tax] = allocate(price, [15, 85]);
// Option B: multiply by scaled amount
const tax = multiply(price, { amount: 15, scale: 2 });
// Reusable
const percentage = (d, share, scale = 0) => {
const rest = 100 ** (scale + 1) - share;
return allocate(d, [share, rest], { scale })[0];
};
VAT with precision preservation
const price = dinero({ amount: 1995, currency: EUR }); // €19.95
const tax = multiply(price, { amount: 55, scale: 3 }); // 5.5%
const total = add(price, tax);
// total.amount = 2104725, scale = 5 → €21.04725 (precision kept)
toDecimal(trimScale(transformScale(total, 2, halfEven))); // "21.05"
Create a Dinero from a float (write your own, not provided)
const dineroFromFloat = ({ amount: float, currency, scale }) => {
const factor = currency.base ** (scale ?? currency.exponent);
return dinero({ amount: Math.round(float * factor), currency, scale });
};
dineroFromFloat({ amount: 19.99, currency: USD }); // $19.99
Look up a currency by code (from API / DB)
import * as currencies from "dinero.js/currencies";
const getCurrency = (code) => {
if (!(code in currencies)) throw new Error(`Unknown currency: ${code}`);
return currencies[code];
};
Currency-bound factory (avoid repeating currency: USD)
const usd = (amount) => dinero({ amount, currency: USD });
usd(500); // $5.00
Pipe transformations
import { pipe } from "ramda";
const result = pipe(
(d) => multiply(d, 2),
(d) => add(d, fee),
(d) => toDecimal(d),
)(price);
Transport over HTTP (number)
// Client → server
fetch('/api/order', { method: 'POST', body: JSON.stringify({ price: dinero(...) }) });
// (JSON.stringify works on a Dinero directly — it serializes the snapshot shape)
// Server → restore
const price = dinero(JSON.parse(body).price);
Transport over HTTP (bigint) — JSON.stringify doesn't handle bigint
const replacer = (k, v) => typeof v === "bigint" ? String(v) : v;
fetch("/api/order", {
method: "POST",
body: JSON.stringify(product, replacer),
});
// On receive, cast strings back to bigint before calling dinero().
Store in SQL (recommended: columns)
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price_amount BIGINT NOT NULL,
price_currency VARCHAR(3) NOT NULL,
price_exponent INTEGER NOT NULL DEFAULT 2
);
// Restore
dinero({
amount: row.price_amount,
currency: {
code: row.price_currency,
base: 10,
exponent: row.price_exponent,
},
});
Tip: if you use a custom scale ≠ currency exponent, persist scale too.
Store in SQL (JSONB / JSON column)
await db.query("INSERT INTO products (price) VALUES ($1)", [
JSON.stringify(toSnapshot(price)),
]);
// Restore: dinero(row.price)
Store in MongoDB (snapshot embedded; Decimal128 for bigint)
await col.insertOne({ price: toSnapshot(price) });
// Restore: dinero(doc.price)
// For bigint amounts, use Decimal128 for the amount field
balance: {
amount: Decimal128.fromString(String(snapshot.amount)),
currency: snapshot.currency,
scale: snapshot.scale,
}
Payment gateway adapters
// Stripe / Adyen / Square (minor units + ISO code)
const toStripe = (d) => {
const { amount, currency } = toSnapshot(d);
return { amount, currency: currency.code.toLowerCase() }; // Stripe wants lowercase
};
const toAdyen = (d) => {
const s = toSnapshot(d);
return { value: s.amount, currency: s.currency.code };
};
const toSquare = (d) => {
const s = toSnapshot(d);
return { amount: BigInt(s.amount), currency: s.currency.code };
};
// PayPal (major units as string)
const toPaypal = (d) => ({
value: toDecimal(d),
currency_code: toSnapshot(d).currency.code,
});
Cryptocurrency (use bigint)
import { add, dinero } from "dinero.js/bigint";
const ETH = { code: "ETH", base: 10n, exponent: 18n };
const wallet = dinero({ amount: 1000000000000000000n, currency: ETH }); // 1 ETH
Tip: don't name files xmr.js/xbt.js — adblockers block them.
Custom amount type (e.g., big.js)
import Big from "big.js";
import { createDinero, DineroCalculator, DineroFormatter } from "dinero.js";
const calculator: DineroCalculator<Big> = {
add: (a, b) => a.plus(b),
subtract: (a, b) => a.minus(b),
multiply: (a, b) => a.times(b),
integerDivide: (a, b) => a.div(b).round(0, Big.roundDown),
modulo: (a, b) => a.mod(b),
compare: (a, b) => a.cmp(b) as any,
increment: (v) => v.plus(new Big(1)),
decrement: (v) => v.minus(new Big(1)),
power: (a, b) => a.pow(Number(b)),
zero: () => new Big(0),
};
const formatter: DineroFormatter<Big> = {
toNumber: (v) => v.toNumber(),
toString: (v) => v.toFixed(), // avoid "1e+22" scientific notation
};
const bigDinero = createDinero({ calculator, formatter });
Format non-decimal currency (ancient / fictional)
const GRD = { code: "GRD", base: 6, exponent: 1 }; // drachma / obol
toUnits(
dinero({ amount: 9, currency: GRD }),
({ value }) =>
value.filter((n) => n > 0)
.map((n, i) => `${n} ${["drachma", "obol"][i]}${n > 1 ? "s" : ""}`)
.join(", "),
); // "1 drachma, 3 obols"
TypeScript: prevent currency mismatches at compile time
import { EUR, USD } from "dinero.js/currencies"; // already typed as const
const usd = dinero({ amount: 500, currency: USD }); // Dinero<number, 'USD'>
const eur = dinero({ amount: 100, currency: EUR }); // Dinero<number, 'EUR'>
add(usd, eur); // ❌ TS error: 'EUR' not assignable to 'USD'
Gotchas
- Don't pass floats to
amount. Always integers in minor units.$19.99→{ amount: 1999 }. multiply(d, 0.5)throws if result isn't integer. Use{ amount: 5, scale: 1 }instead.- bigint + number mixing throws
TypeError. Currencies fromdinero.js/currencieshavenumberbase/exponent;dinero.js/bigint/currencieshasbigint. Never cross-import. - Currency data changes between versions. ISO 4217 amendments ship in minor releases (exponent changes, codes removed). Pin your version or define your own currencies for stability. Validate codes from DB/API at runtime.
convertrequires samebase. USD (10) ↔ MGA (5) throws.- Scale auto-grows but never shrinks unless you call
trimScaleortransformScale. After many multiplications onnumberamounts you can hitNumber.MAX_SAFE_INTEGER(9,007,199,254,740,991). UsetrimScaleperiodically or switch to bigint. - Non-decimal currencies need
exponent: 1, not 0. AndtoDecimalwon't work — onlytoUnitshandles multi-subdivision currencies. JSON.stringifyon bigint Dineros throws. Use a(key, value) => typeof value === 'bigint' ? String(value) : valuereplacer, and cast back on deserialize.PostgreSQL moneytype is lossy. Locale-dependent, fixed 2 decimals, no currency — prefer separate columns.- Currency symbols are not provided.
toDecimalgives"10.50", never"$10.50". UseIntl.NumberFormatin a transformer. - Allocate leaves zero buckets alone.
allocate(d, [0, 50, 50])returns[$0, ..., ...]; the remainder only lands on non-zero ratios. - v2 is functions, not methods.
d.add(x)→add(d, x). No chaining, by design (tree-shaking). Wrap if you want fluent style. - No global defaults / no locale on objects. Each call is explicit; write factories for ergonomics.
- Adblocker file naming: avoid
xmr.js,xbt.js, etc. for crypto — blocked by filter lists.
Migration from v1.x
- import Dinero from 'dinero.js';
- Dinero({ amount: 500, currency: 'USD', precision: 3 });
+ import { dinero } from 'dinero.js';
+ import { USD } from 'dinero.js/currencies';
+ dinero({ amount: 500, currency: USD, scale: 3 }); // precision → scale
| v1 | v2 |
|---|---|
d.add(x) / .subtract / .multiply / .allocate |
standalone add(d,x) / subtract / multiply / allocate |
d.equalsTo(x) / .greaterThan / etc. |
equal(d,x) / greaterThan(d,x) / etc. |
d.isZero() / .isPositive() / .isNegative() |
isZero(d) / isPositive(d) / isNegative(d) |
d.hasSameAmount(x) / .hasSameCurrency(x) |
haveSameAmount([d,x]) / haveSameCurrency([d,x]) |
d.hasSubUnits() |
hasSubUnits(d) |
Dinero.minimum([…]) / .maximum([…]) |
minimum([…]) / maximum([…]) |
d.convert(…) / .convertPrecision(…) |
convert(d,…) / transformScale(d,…) |
Dinero.normalizePrecision([…]) |
normalizeScale([…]) |
d.divide(…) |
use allocate instead |
d.percentage(…) |
build from allocate or multiply |
d.getAmount() / .getCurrency() / .getPrecision() |
toSnapshot(d) destructure |
d.toObject() |
toSnapshot(d) |
d.toUnit() / .toRoundedUnit() |
toUnits(d) / toDecimal(d) |
d.toFormat(fmt) / .setLocale(…) / .getLocale() |
Dropped — compose via toDecimal + Intl |
Floats accepted in convert/multiply/allocate |
Scaled amounts: { amount: 89, scale: 2 } for 0.89 |
@dinero.js/* sub-packages |
All consolidated into dinero.js |
Cheat sheet
// CREATE
dinero({ amount: 500, currency: USD }) // $5.00
dinero({ amount: 35, currency: USD, scale: 3 }) // $0.035
dinero(snapshot) // restore
// MATH (same currency!)
add(a, b) / subtract(a, b)
multiply(d, 4) / multiply(d, { amount: 21, scale: 1 }, halfEven)
allocate(d, [50, 50]) / allocate(d, [1, 3]) / allocate(d, [0, 50, 50])
// CURRENCY / SCALE
convert(d, EUR, { EUR: { amount: 89, scale: 2 } })
normalizeScale([a, b]) transformScale(d, 2, halfEven) trimScale(d)
// COMPARE (boolean)
equal(a,b) greaterThan(a,b) lessThan(a,b) greaterThanOrEqual(a,b) lessThanOrEqual(a,b)
isZero(d) isPositive(d) isNegative(d) hasSubUnits(d)
haveSameAmount([a,b,…]) haveSameCurrency([a,b,…])
// COMPARE (select / sort)
compare(a, b) // -1 | 0 | 1, sortable
minimum([…]) / maximum([…])
// FORMAT
toDecimal(d) // "5.00"
toDecimal(d, ({ value, currency }) => …) // custom
toUnits(d) // [5, 00]
toSnapshot(d) // plain object
// ROUNDING MODES (3rd arg of multiply/allocate/transformScale)
up down halfUp halfDown halfEven halfOdd halfTowardsZero halfAwayFromZero
// ^^^^^^^^ banker's, preferred for money
Appendix — built-in currencies (selected)
All ISO 4217 codes ship in dinero.js/currencies. Exponent ≠ 2 examples to
watch:
| Code | Currency | Base | Exp |
|---|---|---|---|
USD / EUR / GBP / CAD / AUD / CHF / CNY / etc. |
(most world currencies) | 10 | 2 |
JPY |
Japanese yen | 10 | 0 |
KRW |
South Korean won | 10 | 0 |
CLP |
Chilean peso | 10 | 0 |
BIF / DJF / RWF / VUV / XAF / XOF / XPF |
Various zero-exponent | 10 | 0 |
BHD / IQD / JOD / KWD / LYD / OMR / TND |
Gulf/Maghreb dinars | 10 | 3 |
CLF |
Unidad de Fomento | 10 | 4 |
MGA |
Malagasy ariary | 5 | 1 |
MRU |
Mauritanian ouguiya | 5 | 1 |
For the full list, do Object.keys(require('dinero.js/currencies')) or check
docs/api/currencies upstream.