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
- Reformat the literal to strict ISO: YYYY-MM-DD for date, YYYY-MM for month, YYYY for year.
- Normalize user input before querying, e.g. day/month zero-padded and no time component.
- 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
- Always store/compare dates in Actual's machine format (YYYY-MM-DD)
- Zero-pad month and day; strip time components
- Convert locale-formatted user input before querying
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
- Casting string fields to dates is not supported
- Invalid date:
- Field "${field}" does not exist in table "${tableName}"
- Invalid path: ${path}
- Path error: ${tableName} table does not exist
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/cf2dd7bc143ab3e4.
Report an issue: GitHub.