actualbudget/actual · error
Parameter "${param.paramName}" can't convert to ${type} (alr
Error message
Parameter "${param.paramName}" can't convert to ${type} (already inferred as ${existingType}) What it means
AQL parameters are typed on first use via inferParam (compiler.ts:222). If the same named parameter is later used where an incompatible type is required (and no automatic cast exists in the casts table, e.g. string->date, date->date-month, integer->float), a plain Error is thrown because the parameter would need contradictory SQL types.
Source
Thrown at packages/loot-core/src/server/aql/compiler.ts:237
throw new CompileError(`Bad ${type} format: ${str}`);
}
function inferParam(param, type) {
const existingType = param.paramType;
if (existingType) {
const casts = {
date: ['string'],
'date-month': ['date'],
'date-year': ['date', 'date-month'],
id: ['string'],
float: ['integer'],
};
if (
existingType !== type &&
(!casts[type] || !casts[type].includes(existingType))
) {
throw new Error(
`Parameter "${param.paramName}" can't convert to ${type} (already inferred as ${existingType})`,
);
}
} else {
param.paramType = type;
}
}
function castInput(state, expr, type) {
if (expr.type === type) {
return expr;
} else if (expr.type === 'param') {
inferParam(expr, type);
return typed(expr.value, type);
} else if (expr.type === 'null') {
if (!expr.literal) {
throw new CompileError("A non-literal null doesn't make sense");
}View on GitHub (pinned to d4334cb6e6)
Solutions
- Use a distinct parameter name per logical type, e.g. :startDate for dates and :minAmount for numbers.
- If reuse is intended, pass values whose types are compatible with the casts table (string can become date/id; integer can become float; date can become date-month/date-year).
- Ensure the first usage of the parameter is the type you intend, since the first context fixes paramType.
Example fix
// before
.filter({ date: { $gte: ':p' } }).filter({ amount: { $gt: ':p' } })
// after
.filter({ date: { $gte: ':startDate' } }).filter({ amount: { $gt: ':minAmount' } }) Defensive patterns
Strategy: validation
Validate before calling
function makeParams(spec) {
// spec: { startDate: 'date', minAmount: 'float' }
const used = new Map();
for (const [name, type] of Object.entries(spec)) {
if (used.has(name) && used.get(name) !== type) {
throw new Error(`Param ${name} used with conflicting types`);
}
used.set(name, type);
}
return spec;
} Type guard
function paramTypeMatches(param, type) {
const casts = { date: ['string'], id: ['string'], float: ['integer'], 'date-month': ['date'], 'date-year': ['date', 'date-month'] };
return param.paramType === type || (casts[type] || []).includes(param.paramType);
} Try / catch
try {
runQuery(q, params);
} catch (e) {
if (e.message.includes("can't convert to")) {
throw new Error('Use a distinct parameter name for each value type');
} else throw e;
} Prevention
- Name parameters after their type/purpose (:startDate, :minAmount)
- Never reuse one placeholder across differently-typed expressions
- Document param types in query-builder helper functions
When it happens
Trigger: Reusing one parameter in contexts demanding different types: q('transactions').filter({ date: { $eq: ':param' } }).filter({ amount: { $gt: ':param' } }) — first inferred as date, then required as float.
Common situations: Reusing a single placeholder across filters of different columns; generating queries in loops where one param name is shared; building generic query helpers that always use the same param key.
Related errors
- Invalid field name, must be a string
- Field "${field}" does not exist in table "${tableName}"
- Invalid path: ${path}
- Path error: ${tableName} table does not exist
- Field not joinable on table ${tableName}: "${field}"
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/06a866ad5559013a.
Report an issue: GitHub.