Configuration
The package works with zero configuration. The published config/money.php in full:
return [
'default_currency' => env('MONEY_DEFAULT_CURRENCY', 'EUR'),
'currencies' => [
'iso' => env('MONEY_ISO_CURRENCIES', true),
'custom' => [],
'allowed' => null,
],
'schema' => [
'currency_length' => env('MONEY_CURRENCY_LENGTH', 3),
'precision' => env('MONEY_PRECISION', 38),
],
'rounding' => env('MONEY_ROUNDING', 'half_away_from_zero'),
'formatting' => [
'driver' => env('MONEY_FORMATTER', 'auto'),
'locale' => env('MONEY_LOCALE'),
'display' => 'symbol',
'fallback' => [
'pattern' => '{sign}{amount} {code}',
'decimal_separator' => '.',
'thousands_separator' => ',',
],
],
'macros' => [
'collection' => true,
'request' => true,
'blade' => true,
'validation' => true,
],
'exchange' => [
'default' => env('MONEY_EXCHANGE_DRIVER', 'ecb'),
'chain' => ['database', 'ecb'],
'pivot' => 'EUR',
'rounding' => env('MONEY_EXCHANGE_ROUNDING', 'half_even'),
'timezone' => env('MONEY_EXCHANGE_TIMEZONE', 'Europe/Berlin'), // "today" for undated lookups
'max_age_days' => env('MONEY_EXCHANGE_MAX_AGE_DAYS', 7),
'cache' => [
'enabled' => env('MONEY_EXCHANGE_CACHE', true),
'store' => env('MONEY_EXCHANGE_CACHE_STORE'),
'ttl' => env('MONEY_EXCHANGE_CACHE_TTL', 3600),
'prefix' => 'money:exchange',
],
'providers' => [
'config' => [
'rates' => [], // ['EUR' => ['USD' => '1.0854']]
],
'database' => [
'table' => env('MONEY_EXCHANGE_TABLE', 'money_exchange_rates'),
'model' => env('MONEY_EXCHANGE_MODEL', RoundlyConsulting\Money\Models\CurrencyRate::class),
],
'ecb' => [
'daily_url' => 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml',
'recent_url' => 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml',
'history_url' => 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.xml',
'timeout' => env('MONEY_ECB_TIMEOUT', 10),
'retries' => 2,
'max_bytes' => 33554432,
'cache_ttl' => 3600,
],
],
'refresh' => [
'schedule' => env('MONEY_EXCHANGE_SCHEDULE', false),
'cron' => '30 16 * * 1-5',
'timezone' => 'Europe/Berlin',
'source' => 'ecb',
],
],
];Every key
| Key | Default | Env | Purpose |
|---|---|---|---|
default_currency | EUR | MONEY_DEFAULT_CURRENCY | Currency for Request::money() without one, the parser without a currency token (or with a shared symbol such as $ that this currency writes), and money_amount without a parameter. |
currencies.iso | true | MONEY_ISO_CURRENCIES | Seed the registry with the bundled ISO 4217 list (165 currencies); false = custom only. |
currencies.custom | [] | — | Extra currencies: code => exponent (0..18), name, symbol. |
currencies.allowed | null | — | Allow-list for currencies chosen by input (null = the whole registry). Must be an array of codes. Never restricts stored data. |
schema.currency_length | 3 | MONEY_CURRENCY_LENGTH | Varchar length of currency columns (3..10) and the longest custom code. Fixed once migrated. |
schema.precision | 38 | MONEY_PRECISION | P of the decimal(P, 0) amount columns (19..65); the cast and MoneyAmount enforce it. Fixed once migrated. |
rounding | half_away_from_zero | MONEY_ROUNDING | Service-level default rounding (formatter digit reduction, avgMoney). Value objects never read config. |
formatting.driver | auto | MONEY_FORMATTER | auto (intl when loaded, else decimal), intl (fails loud without the extension) or decimal. |
formatting.locale | null | MONEY_LOCALE | null follows app()->getLocale(). |
formatting.display | symbol | — | Default currency display: symbol, code or none. |
formatting.fallback.pattern | {sign}{amount} {code} | — | Deterministic formatter pattern; placeholders {sign} {amount} {code} {symbol}. |
formatting.fallback.decimal_separator | . | — | Decimal separator — also the parser’s separator without intl. Blank = not set → '.'. |
formatting.fallback.thousands_separator | , | — | Thousands separator of the formatter and parser without intl. Any string. The one setting where blank is a value: ' ' groups with a space and '' means no grouping symbol; only null takes ','. |
macros.collection | true | — | Register sumMoney / minMoney / maxMoney / avgMoney. |
macros.request | true | — | Register Request::money(). |
macros.blade | true | — | Register @money. |
macros.validation | true | — | Register the currency_code and money_amount string rules. |
exchange.default | ecb | MONEY_EXCHANGE_DRIVER | Default driver: config, database, ecb, chain or your own. |
exchange.chain | ['database', 'ecb'] | — | Drivers the chain driver tries, in order — a list of driver names, never chain itself. |
exchange.pivot | EUR | — | Triangulation currency for config and database. Not set (null or blank) = no pivot. |
exchange.rounding | half_even | MONEY_EXCHANGE_ROUNDING | Default rounding of conversions. |
exchange.timezone | Europe/Berlin | MONEY_EXCHANGE_TIMEZONE | Timezone of “today” for undated lookups and undated manual rates, and of the returned rate dates. A requested date is always its own calendar day (Y-m-d in its own timezone). |
exchange.max_age_days | 7 | MONEY_EXCHANGE_MAX_AGE_DAYS | A newest rate older than this is stale and refused. At least 0 (0 accepts only the day’s own rate). |
exchange.cache.enabled | true | MONEY_EXCHANGE_CACHE | Cache database, ecb and custom driver lookups. |
exchange.cache.store | null | MONEY_EXCHANGE_CACHE_STORE | Cache store (null = default). |
exchange.cache.ttl | 3600 | MONEY_EXCHANGE_CACHE_TTL | Cache lifetime in seconds, at least 1 (switch caching off with cache.enabled). |
exchange.cache.prefix | money:exchange | — | Cache key prefix. |
exchange.providers.config.rates | [] | — | Static rates as decimal strings: ['EUR' => ['USD' => '1.0854']]. |
exchange.providers.database.table | money_exchange_rates | MONEY_EXCHANGE_TABLE | Rates table. |
exchange.providers.database.model | CurrencyRate::class | MONEY_EXCHANGE_MODEL | Swappable rate model — CurrencyRate or a subclass; anything else throws InvalidMoneyConfiguration. |
exchange.providers.ecb.daily_url / recent_url / history_url | ecb.europa.eu | — | Daily, 90-day and full-history feeds. |
exchange.providers.ecb.timeout | 10 | MONEY_ECB_TIMEOUT | HTTP timeout in seconds, at least 1. |
exchange.providers.ecb.retries | 2 | — | Retries after the first attempt, at least 0. |
exchange.providers.ecb.max_bytes | 33554432 | — | Response size cap (32 MiB), at least 1. |
exchange.providers.ecb.cache_ttl | 3600 | — | Parsed-feed cache in seconds, at least 1. |
exchange.refresh.schedule | false | MONEY_EXCHANGE_SCHEDULE | Register the rate refresh schedule. |
exchange.refresh.cron | 30 16 * * 1-5 | — | When the schedule runs (weekdays at 16:30). |
exchange.refresh.timezone | Europe/Berlin | — | Timezone of the cron expression. |
exchange.refresh.source | ecb | — | Source the schedule refreshes from. |
Environment
The common knobs are env-driven, so you rarely need to publish the config at all:
MONEY_DEFAULT_CURRENCY=EUR
MONEY_ROUNDING=half_away_from_zero
MONEY_FORMATTER=auto
MONEY_EXCHANGE_DRIVER=chain
MONEY_EXCHANGE_SCHEDULE=true
MONEY_EXCHANGE_MAX_AGE_DAYS=7Macro toggles
Each developer-experience surface — collection macros, Request::money(), @money and the string validation rules — can be switched off under macros. An existing host macro with the same name always wins. The Blueprint macros have no toggle: published migrations depend on them.
On/off switches
The on/off switches — currencies.iso, macros.*, exchange.cache.enabled and exchange.refresh.schedule — accept true/false, 1/0, on/off or yes/no, from .env or the published file; anything else throws the package toolkit’s InvalidConfigurationException naming the key, so a typo never silently reads as the default.
Strict settings
Every other setting is read strictly too, at the point of use. A key that is not set — absent, null or blank ('' or whitespace, such as a MONEY_ROUNDING= line in .env) — takes its default; a present value of the wrong shape throws InvalidMoneyConfiguration naming the key, and is never cast or swapped for the default:
- Integers take an int or a canonical integer string (“30”, “-5”) within the stated range — “five”, “5.5” or “1e3” throw.
- Names — currency, driver, timezone, table, cache store and prefix, URLs, locale, pattern — must be strings.
- The rounding modes, formatting.driver and formatting.display must name one of their values.
- currencies.allowed, currencies.custom, exchange.chain and exchange.providers.config.rates must be arrays — a string allow-list such as 'EUR' throws rather than allowing every currency.
- exchange.providers.database.model must be CurrencyRate or a subclass of it — anything else throws instead of falling back to the packaged model.
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 cryptoBy 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.