When to use these helpers
Use these helpers when a value is an amount in a currency. A MoneyValue keeps its currency, adds and compares exactly, and refuses to mix two currencies without a conversion. The arithmetic and comparison operators also understand MoneyValues; plain numbers behave exactly as before.Calling style
Call a helper asroundMoney($.unit_price * $.quantity). Not every helper is available everywhere: validation, form, and document-template helpers have distinct context contracts. Editor suggestions are not proof that a helper is supported at runtime.
Complete function reference
allocate
allocate
allocate
Splits an amount by weights so the parts add up exactly to the total rounded to the currency’s decimals (2 decimals for a plain number). Leftover units go to the parts with the largest remainders, earlier parts first on a tie. Weights that are all zero give zero parts; weights that are not a list give an empty list. The parts carry no base.Signature: allocate(value, weights)Example:value | allocate(...).base
base
base
With a MoneyValue, returns its captured base-currency equivalent as a number, or null when none was captured. With no argument, returns the organization’s base currency code inside a calculated field and null elsewhere. Any other value gives null.Signature: base(value?)Example:value | base(...).convert
convert
convert
Converts a MoneyValue to another currency at the organization’s rate (options.type and options.on as for rateOn) and rounds the result to the target currency’s decimals with the organization’s rounding mode. Returns the value unchanged when it is already in that currency, and null when the first argument is not a MoneyValue or to is not text. Stops with MONEY_RATE_MISSING when no rate qualifies. The result records the rate and rate type but has no base.Signature: convert(value, to, options?)Example:value | convert(...).currencyOf
currencyOf
currencyOf
Returns the ISO currency code of a MoneyValue, or null for any other value.Signature: currencyOf(value)Example:value | currencyOf(...).fx
fx
fx
Short alias of rateOn with the same arguments, defaults, and result.Signature: fx(currency, on?, type?, to?)Example:value | fx(...).isMoney
isMoney
isMoney
Checks whether a value is a MoneyValue. A Money field is a MoneyValue only where the organization’s Money-aware operators put one in the calculated-field context; otherwise it is a plain number.Signature: isMoney(value)Example:value | isMoney(...).money
money
money
Creates a MoneyValue from an amount (a number or decimal text) and a three-letter uppercase ISO currency code. Returns null when the amount is not numeric or the code is not three uppercase letters. The optional denomination key is kept on the value.Signature: money(amount, currency, denomination?)Example:value | money(...).rateOn
rateOn
rateOn
Returns the exchange rate from a currency to a target currency (the organization’s base currency when to is omitted) as a number. on is a date text or Date and defaults to the moment of evaluation; type is a rate-type key and defaults to the organization’s default rate type. Returns 1 when the currency is the target and null when no rate qualifies. The lookup is asynchronous; the expression waits for it.Signature: rateOn(currency, on?, type?, to?)Example:value | rateOn(...).roundMoney
roundMoney
roundMoney
Rounds a MoneyValue to its currency’s decimals, or a plain number to 2 decimals or to the decimals of the currency code you pass. A number as the second argument sets the decimals. The mode is half_up, half_even, half_down, up, or down; without it the organization’s rounding mode applies. A rounded MoneyValue keeps its currency and base; a value that is not numeric is returned unchanged.Signature: roundMoney(value, currencyOrDecimals?, mode?)Example:value | roundMoney(...).sumBase
sumBase
sumBase
Totals the captured base-currency equivalents of the MoneyValues in a list, across currencies, and returns a plain number. MoneyValues without a base and plain numbers are skipped; an empty list or a value that is not a list gives 0.Signature: sumBase(list)Example:value | sumBase(...).sumMoney
sumMoney
sumMoney
Totals a list exactly. MoneyValues must share one currency (MONEY_CURRENCY_MISMATCH otherwise) and give a MoneyValue; plain numbers in the list are added to that amount. A list of plain numbers gives a number, a list with nothing numeric gives 0, and a value that is not a list gives null. The total keeps a base only when every MoneyValue has one and the list has no plain numbers.Signature: sumMoney(list)Example:value | sumMoney(...).Runtime behavior and edge cases
Money values and Money fields
A MoneyValue is an amount with a currency. Create one withmoney(amount, currency). In an entity calculated field evaluated on the server, a Money field reads as a MoneyValue when the organization has turned on Money-aware operators; otherwise it stays a plain number, and the helpers treat it as one. A MoneyValue exposes these properties:
.rateDate and .rateType give the date and type of that rate. When a calculated field’s result is a MoneyValue, the field stores its amount.
Operators with MoneyValues
Unary minus negates a MoneyValue. Dividing by zero gives
Infinity, as it does for plain numbers. Comparing a MoneyValue with a value that is not numeric, such as null, is false for == and the ordering operators and true for !=. When neither side is a MoneyValue, every operator behaves exactly as before.
One currency at a time
Adding, subtracting, dividing, or comparing two MoneyValues in different currencies stops the expression withMONEY_CURRENCY_MISMATCH (“Cannot add USD and EUR: convert one of them first.”). sumMoney applies the same rule. Convert one side first:
Exact decimal arithmetic
Money arithmetic works on exact decimals rather than floating-point numbers, so amounts do not drift:money(10, "USD") / 3 keeps up to 18 decimal places, and a Money-to-Money ratio keeps up to 10. Only convert and allocate round for you. Round with round(m) or roundMoney before you show, compare, or store an amount that must match the currency’s precision.
Rounding
round(m) and roundMoney(m) round a MoneyValue to its currency’s decimals; round(number, decimals) on a plain number is unchanged. In a calculated field evaluated on the server, the decimals come from the organization’s currency list and the mode from the organization’s rounding setting. Elsewhere KayanOS uses built-in decimals (3 for KWD, JOD, BHD, OMR, TND, LYD, and IQD; 0 for JPY, KRW, CLP, VND, and ISK; 2 otherwise) and half_up. When a Money field uses a fixed number of decimals that differs from its currency, pass that number: roundMoney($.unit_price * $.quantity, 4). Pass a mode only when a rule requires one, for example roundMoney(m, 2, "half_even").
Exchange rates
rateOn, fx, and convert read the organization’s exchange rates only in an entity calculated field evaluated on the server. The lookup uses:
- Rate type: the type you pass, otherwise the organization’s default rate type.
- Date: the
ondate you pass, otherwise the moment the expression runs. The helpers do not read the record’s document date by themselves; pass it, as inconvert(m, "USD", { on: $.invoice_date }). - Missing-rate policy: the rate type’s policy decides how old a rate may be. Under a same-day policy only a rate for that day qualifies; under a latest-earlier policy the most recent earlier rate within the allowed number of days qualifies. A monthly rate type’s rate covers its whole month, and the type’s fallback rate types are tried in order.
rateOn and fx return null and convert stops with MONEY_RATE_MISSING. Each record evaluation makes at most 200 distinct rate lookups; further lookups find no rate. Outside a calculated field evaluated on the server, such as in rules, document templates, and background recalculation, there are no rates: rateOn returns null and convert stops with MONEY_RATE_MISSING unless the value is already in the target currency.
When .base is null
The base-currency equivalent is captured when a record is saved with an exchange rate. .base and base(m) are null when no rate was captured for that value, and for MoneyValues created inside the expression: money(), convert(), allocate(), and Money plus or minus a plain number. Multiplying or dividing by a number scales the base; adding two MoneyValues keeps a base only when both have one. sumBase skips values without a base, so a total can be understated; test base(m) == null before you rely on it.
Before you publish
- Start with a known value and confirm the expected result.
- Handle null, empty text, and empty lists deliberately.
- Do not place secrets, access tokens, or sensitive personal data in an expression.
- Read expression contexts and availability before copying an expression between features.


