actualbudget/actual · error · CompileError
Field not joinable on table ${tableName}: "${field}"
Error message
Field not joinable on table ${tableName}: "${field}" What it means
makePath (compiler.ts:88) only allows path segments that are joinable — i.e. schema fields with a `ref` pointing at another table. If an intermediate path segment names a field that either doesn't exist or exists but is a plain column (no ref), this CompileError is thrown because the compiler cannot build a JOIN for it.
Source
Thrown at packages/loot-core/src/server/aql/compiler.ts:89
function makePath(state, path) {
const { schema, paths } = state;
const parts = path.split('.');
if (parts.length < 2) {
throw new CompileError('Invalid path: ' + path);
}
const initialTable = parts[0];
const tableName = parts.slice(1).reduce((tableName, field) => {
const table = schema[tableName];
if (table == null) {
throw new CompileError(`Path error: ${tableName} table does not exist`);
}
if (!table[field] || table[field].ref == null) {
throw new CompileError(
`Field not joinable on table ${tableName}: "${field}"`,
);
}
return table[field].ref;
}, initialTable);
let joinTable;
const parentParts = parts.slice(0, -1);
if (parentParts.length === 1) {
joinTable = parentParts[0];
} else {
const parentPath = parentParts.join('.');
const parentDesc = paths.get(parentPath);
if (!parentDesc) {
throw new CompileError('Path does not exist: ' + parentPath);
}
joinTable = parentDesc.tableId;View on GitHub (pinned to d4334cb6e6)
Solutions
- Replace the intermediate segment with a field that has a `ref` in the schema (a real relationship, e.g. 'payee', 'account', 'category').
- If you only need the column value, stop the path at that field: use 'amount' rather than 'amount.something'.
- Check schema.ts to confirm which fields on the table define `ref` before composing multi-segment paths.
Example fix
// before
q('transactions').select('amount.name')
// after
q('transactions').select('payee.name') Defensive patterns
Strategy: validation
Validate before calling
import { schema } from './aql/schema';
function isJoinable(table, field) {
return Boolean(schema[table] && schema[table][field] && schema[table][field].ref != null);
} Type guard
function hasRef(fieldDesc) {
return fieldDesc != null && typeof fieldDesc === 'object' && 'ref' in fieldDesc && fieldDesc.ref != null;
} Try / catch
try {
runQuery(q);
} catch (e) {
if (e.message.includes('Field not joinable')) {
throw new Error('Intermediate path segments must be relationships (ref fields)');
} else throw e;
} Prevention
- Only traverse fields with `ref` in the schema
- Stop paths at scalar fields; never extend them
- Keep a map of joinable relationships per table for query generation
When it happens
Trigger: q('transactions').select('amount.name') — 'amount' is a plain number column with no ref; or q('transactions').filter({ 'notes.value': ... }) where 'notes' has no ref in the schema.
Common situations: Attempting to traverse through scalar columns as if they were relationships; inventing relationship names not present in the schema; old queries written against schema versions where the field was joinable and later changed.
Related errors
- Invalid path: ${path}
- Path error: ${tableName} table does not exist
- Path does not exist:
- Field "${field}" does not exist in table "${tableName}"
- Invalid field name, must be a string
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/4ca7b687a34b7ba4.
Report an issue: GitHub.