actualbudget/actual · error
Value is not a number (${typeof localValue}): ${localValue}
Error message
Value is not a number (${typeof localValue}): ${localValue} What it means
After the string-coercion branch, format re-checks that the value is a number before calling integerToCurrency. Values that are neither null/empty-string nor string nor number (e.g. booleans, objects, arrays, undefined after non-empty check) fall through to this check and throw 'Value is not a number (<typeof>): <value>'.
Source
Thrown at packages/desktop-client/src/hooks/useFormat.ts:91
// This case is generally flawed, but we need to support it for
// backwards compatibility for now.
// For example, it is not clear how the string might look like
// The Budget sends 12300, if the user inputs 123.00, but
// there might be other components that send 123 with the same user input.
// Ideally the string case will be removed in the future. We should always
// use the IntegerAmount.
// The parseInt with the replace is a workaround for the case and looks like
// the "least wrong" solution.
const integerString = localValue.replace(/[^\d-]/g, '');
const parsed = parseInt(integerString, 10);
if (isNaN(parsed)) {
throw new Error(`Invalid numeric value: ${localValue}`);
}
localValue = parsed;
}
if (typeof localValue !== 'number') {
throw new Error(
'Value is not a number (' + typeof localValue + '): ' + localValue,
);
}
return {
numericValue: localValue,
formattedString: integerToCurrency(
localValue,
formatter,
decimalPlaces,
),
};
}
default:
throw new Error('Unknown format type: ' + type);
}
}
View on GitHub (pinned to d4334cb6e6)
Solutions
- Extract the numeric field from the object before formatting (format(obj.amount, 'financial'))
- Coerce explicitly with Number(value) and validate isFinite before the call
- Fix the type at the source so the value is an IntegerAmount (number)
- Add a runtime guard at the call site that falls back to 0 for non-numeric values
Example fix
// before
format({ amount: 12300 }, 'financial'); // object -> throws
// after
format(12300, 'financial'); // pass IntegerAmount number Defensive patterns
Strategy: type-guard
Validate before calling
if (value != null && typeof value !== 'string' && typeof value !== 'number') {
throw new TypeError(`financial format needs number|string|null, got ${typeof value}`);
} Type guard
function isFormatableFinancialValue(v: unknown): v is number | string | null | undefined | '' {
return v == null || v === '' || typeof v === 'string' || typeof v === 'number';
} Try / catch
let out: string;
try {
out = format(value, 'financial');
} catch (e) {
if (e instanceof Error && e.message.startsWith('Value is not a number')) {
out = format(0, 'financial');
} else throw e;
} Prevention
- Unwrap aggregated query objects ({amount: n}) before formatting
- Type component props as IntegerAmount | null so booleans/objects can't flow in
- Validate data shapes at the API/AQL boundary with zod or manual guards
- Fall back to 0 for null/undefined amounts instead of passing raw values
When it happens
Trigger: Passing true/false, an object like {amount: 123}, an array, NaN is a number so it passes this check but not undefined or Symbol to a 'financial' format type; also values where null/'' normalization was skipped because the value was undefined (undefined !== null? actually null==null caught, but undefined === '' false, undefined == null true — so mainly objects/booleans).
Common situations: A query aggregation returning {value: n} objects instead of raw numbers; boolean flags accidentally bound into amount fields; refactors changing IntegerAmount to a wrapped object.
Related errors
- Value is not a number (${typeof value}): ${value}
- Invalid config file: key "${key}" must be a string, got ${ty
- Invalid --name: must be a non-empty string.
- No update fields provided. Use --name or --offbudget.
- Invalid cutoff date: expected a valid date (e.g. YYYY-MM-DD)
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/5964bf6aa8f31206.
Report an issue: GitHub.