NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Money for Laravel

Formatting & parsing

format() renders through the bound MoneyFormatter: ICU when ext-intl is loaded, otherwise a deterministic formatter. The locale comes from the argument, then money.formatting.locale, then the app locale:

use RoundlyConsulting\Money\Formatting\FormatOptions;

$usd = Money::ofMajor('1234.50', 'USD');

$usd->format();                                // app locale (or money.formatting.locale)
$usd->format('en_US');                         // "$1,234.50"
$usd->format('en_US', FormatOptions::code());  // "USD 1,234.50"
$usd->format('en_US', FormatOptions::none());  // "1,234.50"
$usd->format('en_US', new FormatOptions(grouping: false));   // "$1234.50"
$usd->format('en_US', new FormatOptions(fractionDigits: 0, rounding: RoundingMode::HalfEven)); // "$1,234"
Money::ofMajor('10', 'USD')->format('en_US', FormatOptions::whole());  // "$10"

Money::ofMajor('1234.50', 'EUR')->format('de_CH', FormatOptions::code()); // "EUR 1’234.50"
Money::ofMajor('1234.50', 'EUR')->format('sk', FormatOptions::code());    // "1 234,50 EUR"
Money::ofMinor(1234, 'JPY')->format('en_US');   // "¥1,234" — exponent 0
Money::ofMinor(1234, 'BHD')->format('en_US');   // "BHD 1.234" — exponent 3
Money::ofMinor('1234567890123456789', 'USD')->format('en_US'); // "$12,345,678,901,234,567.89"

FormatOptions

  • display — CurrencyDisplay::Symbol, Code or None; shortcuts FormatOptions::code() and FormatOptions::none().
  • fractionDigits — null means the currency exponent; fewer digits round with rounding (null = money.rounding).
  • trimTrailingZeros — FormatOptions::whole() drops the zero fraction of whole amounts.
  • grouping — false turns off thousands separators.

ICU formats doubles, exact to 15 significant digits. Beyond that the package lays the exact decimal digits into ICU’s template, so separators, grouping (Indian 3/2 included) and the locale’s numbering system still match. Custom currencies unknown to ICU render with their own symbol, such as “pts 1,200”.

Deterministic fallback

Set MONEY_FORMATTER=decimal (or run auto without ext-intl) for identical output on every host, driven by the fallback pattern and separators. Setting intl explicitly without the extension fails loud with FormatterUnavailable.

// config/money.php — used when MONEY_FORMATTER=decimal, or with auto and no ext-intl
'formatting' => [
    'driver' => env('MONEY_FORMATTER', 'auto'),
    'fallback' => [
        'pattern' => '{sign}{amount} {code}',   // placeholders: {sign} {amount} {code} {symbol}
        'decimal_separator' => '.',
        'thousands_separator' => ',',
    ],
],

Money::ofMajor('1234.50', 'USD')->format();   // "1,234.50 USD" — identical on every host

Parsing input

Money::parse('1 234,50 EUR', locale: 'sk');     // 1234.50 EUR
Money::parse('(1,234.50)', 'USD', 'en');         // -1234.50 USD — accounting negative
Money::parse("CHF 1’234.50", locale: 'de_CH');   // 1234.50 CHF
Money::parse('12,34,567.50', 'INR', 'hi_IN');    // 1234567.50 INR — Indian grouping
Money::parse('$5', 'CAD', 'en');                 // 5.00 CAD — a shared symbol reads as the argument
Money::parse('$5');                              // 5.00 USD in a USD shop — InvalidAmount in a EUR one

Money::parse('1.23.4', 'EUR', 'en');             // throws InvalidAmount — refused, never guessed
Money::parse('10.005', 'USD', 'en');             // throws RoundingNecessary
  • An optional currency token at the start or the end: a registered code or a symbol. A symbol several countries write ($, £, ¥, kr) is read as the currency argument, or as money.default_currency when that currency writes it; otherwise it is refused (InvalidAmount), never guessed — Money::parse('$5') is 5.00 USD in a USD shop and an error in a EUR one.
  • A detected currency that differs from the argument throws CurrencyMismatch; no token and no argument fall back to money.default_currency.
  • Signs: a leading or trailing minus or plus, or accounting parentheses. Two signs or unbalanced parentheses are refused.
  • At most one decimal separator; grouping only in the integer part and only in well-formed groups — ambiguous input is refused, never guessed.
  • Excess precision throws RoundingNecessary; input is capped at 128 characters.

Blade

<p>{{ __('Total') }}: @money($order->total)</p>
<p>@money($order->total, 'sk')</p>   {{-- null renders an empty string --}}

@money($price) or @money($price, 'sk') prints the escaped output of the bound formatter; null renders an empty string. Toggle it with macros.blade.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.