actualbudget/actual · error · CompileError

Can't convert ${expr.type} to ${type}

Error message

Can't convert ${expr.type} to ${type}

What it means

Generic cast failure at the end of castInput: after all specific cast branches (date casts, id-from-string, integer-to-float, `any` passthrough) none applied, so the expression's type cannot be converted to the requested type. This catches combinations with no defined cast, e.g. boolean to float or array to id.

Source

Thrown at packages/loot-core/src/server/aql/compiler.ts:339

        `CAST(SUBSTR(${expr2.value}, 1, 4) AS integer)`,
        'date-year',
      );
    }
  } else if (type === 'id') {
    if (expr.type === 'string') {
      return typed(expr.value, 'id', { literal: expr.literal });
    }
  } else if (type === 'float') {
    if (expr.type === 'integer') {
      return typed(expr.value, 'float', { literal: expr.literal });
    }
  }

  if (expr.type === 'any') {
    return typed(expr.value, type, { literal: expr.literal });
  }

  throw new CompileError(`Can't convert ${expr.type} to ${type}`);
}

// TODO: remove state from these functions
function val(state, expr, type?: string) {
  let castedExpr = expr;

  // Cast the type if necessary
  if (type) {
    castedExpr = castInput(state, expr, type);
  }

  if (castedExpr.literal) {
    if (castedExpr.type === 'id') {
      return `'${castedExpr.value}'`;
    } else if (castedExpr.type === 'string') {
      // Escape quotes
      const value = castedExpr.value.replace(/'/g, "''");
      return `'${value}'`;

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Pass the value already in the target type (e.g. number instead of string) for numeric casts
  2. Use a string for id/`date` targets — those are the castable string paths
  3. Unwrap single-element arrays and pass the scalar directly
  4. Adjust the function schema/type declaration if you control it so the argument type matches

Example fix

// before
q.filter({ amount: { $gt: '100' } })
// after
q.filter({ amount: { $gt: 100 } })
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof value === 'string' && ['float','integer','boolean'].includes(targetType)) {
  throw new Error(`Strings don't auto-cast to ${targetType}; convert first`);
}

Type guard

function isCastable(value, targetType) {
  const t = Array.isArray(value) ? 'array' : value === null ? 'null' : typeof value === 'object' ? (value instanceof Date ? 'date' : 'object') : typeof value;
  const ok = {
    id: ['string'], float: ['integer'], integer: [], boolean: [],
    date: ['string'], 'date-month': ['string'], 'date-year': ['string'],
  };
  return t === targetType || (ok[targetType] || []).includes(t);
}

Try / catch

try {
  runQuery(query);
} catch (e) {
  if (/Can't convert .* to /.test(e.message)) {
    throw new Error('Type mismatch in query: convert the value to the target type before querying', { cause: e });
  }
  throw e;
}

Prevention

When it happens

Trigger: Requesting a cast between unrelated types: e.g. string to float (strings don't auto-cast to numeric), boolean to date/id, array to any scalar type, date to id. Triggered via val(), compileFunction(), compileOp() when the function schema requires a type the argument can't supply.

Common situations: Passing numeric values as strings ('100' where 100 is needed); passing an array where a scalar is required ($in misuse with nested arrays); custom functions declaring parameter types the caller can't satisfy.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/f5826663be4b3a4a. Report an issue: GitHub.