actualbudget/actual · error · CompileError

Bad ${type} format: ${str}

Error message

Bad ${type} format: ${str}

What it means

When a literal string is cast to a date-like type, castInput runs it through parseDate/parseMonth/parseYear; on failure badDateFormat (compiler.ts:218) throws this CompileError. AQL stores dates as integers, so literal date strings must strictly match YYYY-MM-DD, YYYY-MM, or YYYY.

Source

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

function parseMonth(str) {
  const m = str.match(/^(\d{4}-\d{2})$/);
  if (m) {
    return typed(dateToInt(m[1]), 'date', { literal: true });
  }
  return null;
}

function parseYear(str) {
  const m = str.match(/^(\d{4})$/);
  if (m) {
    return typed(dateToInt(m[1]), 'date', { literal: true });
  }
  return null;
}

function badDateFormat(str, type) {
  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(

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Reformat the literal to strict ISO: YYYY-MM-DD for date, YYYY-MM for month, YYYY for year.
  2. Normalize user input before querying, e.g. day/month zero-padded and no time component.
  3. If the value is dynamic and can't be a literal, convert it to a date-machinable string in your code first; the compiler only auto-parses literal strings.

Example fix

// before
q('transactions').filter({ date: '01/15/2024' })
// after
q('transactions').filter({ date: '2024-01-15' })
Defensive patterns

Strategy: validation

Validate before calling

function isIsoDate(s) { return /^\d{4}-\d{2}-\d{2}$/.test(s); }
function isIsoMonth(s) { return /^\d{4}-\d{2}$/.test(s); }
function normalizeDateInput(s) {
  const d = new Date(s);
  if (isNaN(d)) throw new Error('Unparseable date: ' + s);
  return d.toISOString().slice(0, 10);
}

Type guard

function isMachineDate(v) {
  return typeof v === 'string' && /^(\d{4}|\d{4}-\d{2}|\d{4}-\d{2}-\d{2})$/.test(v);
}

Try / catch

try {
  runQuery(q.filter({ date: userInput }));
} catch (e) {
  if (e.message.startsWith('Bad date format')) {
    q = q.filter({ date: normalizeDateInput(userInput) });
  } else throw e;
}

Prevention

When it happens

Trigger: q('transactions').filter({ date: '01/15/2024' }) or { 'date': '2024-1-5' } — any literal string not matching the strict patterns; also 'date-month' casts like { $month: 'foo' } with a non-parseable value.

Common situations: Locale-formatted dates (MM/DD/YYYY, DD.MM.YYYY); dates with time components ('2024-01-15T00:00:00Z'); user-supplied dates pasted from UI inputs with loose formats; missing zero-padding like '2024-1-5'.

Related errors


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