actualbudget/actual · error
Invalid id, must be string:
Error message
Invalid id, must be string:
What it means
Values cast to the `id` type must be a string (or explicitly null). Ids in Actual are opaque strings; passing a number, object, or other non-string type indicates a bug and is rejected with 'Invalid id, must be string'.
Source
Thrown at packages/loot-core/src/server/aql/schema-helpers.ts:42
if (value instanceof Date) {
return toDateRepr(dayFromDate(value));
} else if (
value.match(/^\d{4}-\d{2}-\d{2}$/) == null ||
value < '1995-01-01'
) {
throw new Error('Invalid date: ' + value);
}
return toDateRepr(value);
case 'date-month':
return toDateRepr(value.slice(0, 7));
case 'date-year':
return toDateRepr(value.slice(0, 4));
case 'boolean':
return value ? 1 : 0;
case 'id':
if (typeof value !== 'string' && value !== null) {
throw new Error('Invalid id, must be string: ' + value);
}
return value;
case 'integer':
if (typeof value === 'number' && Number.isInteger(value)) {
return value;
} else {
throw new Error("Can't convert to integer: " + JSON.stringify(value));
}
case 'json':
return JSON.stringify(value);
default:
}
return value;
}
export function convertOutputType(value, type) {
if (value === null) {
if (type === 'boolean') {View on GitHub (pinned to d4334cb6e6)
Solutions
- Convert the value to a string before querying: `String(id)` — but only if it is a legitimate id.
- Generate proper Actual ids with the id utilities (e.g. `.randomUUID()` based ids) instead of external numeric keys.
- If the id may legitimately be absent, pass null explicitly rather than 0 or a number.
- Trace where the non-string id originates (importer, API response) and fix the mapping layer.
Example fix
// before
q('transactions').filter({ account: row.accountId }); // number 42
// after
q('transactions').filter({ account: row.accountId != null ? String(row.accountId) : null }); Defensive patterns
Strategy: type-guard
Validate before calling
const isValidId = (v) => typeof v === 'string' && v.length > 0;
if (!isValidId(id)) throw new Error('caller must supply a string id'); Type guard
const isStringId = (v) => typeof v === 'string';
Try / catch
try {
return await runQuery(q.filter({ account: id }));
} catch (e) {
if (e.message.startsWith('Invalid id, must be string:')) {
logger.error('Non-string id passed to query', { id });
return null;
}
throw e;
} Prevention
- Never pass numeric keys from external systems directly as ids
- Generate ids with Actual's id utilities
- Pass explicit null (not 0/undefined) when an id is intentionally absent
When it happens
Trigger: Passing a numeric id (e.g. from a CSV import or external system), an auto-increment integer, or an object/undefined that is not null into an `id`-typed query field such as account, payee, or transaction id.
Common situations: Importing data from other finance tools that use integer keys, comparing ids parsed from JSON where numbers leaked in, or using a library/entity record where the id field was never converted to Actual's string id format.
Related errors
- Can't convert to integer:
- Invalid config file: key "${key}" must be a boolean, got ${t
- Invalid field name, must be a string
- Parameter "${param.paramName}" can't convert to ${type} (alr
- Query value cannot be undefined
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/33a9443df55d8f09.
Report an issue: GitHub.