{"record":{"id":"a849ab431462be7a","repo":"actualbudget/actual","slug":"invalid-internal-table-filter-only-object-filters","errorCode":null,"errorMessage":"Invalid internal table filter: only object filters are supported","messagePattern":"Invalid internal table filter: only object filters are supported","errorType":"exception","errorClass":"CompileError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/aql/compiler.ts","lineNumber":1094,"sourceCode":"export function compileQuery(\n  queryState: QueryState,\n  schema: Schema,\n  schemaConfig: SchemaConfig = {},\n) {\n  const { withDead, validateRefs = true, tableOptions, rawMode } = queryState;\n\n  const {\n    tableViews = {},\n    tableFilters = () => [],\n    customizeQuery = queryState => queryState,\n  } = schemaConfig;\n\n  const internalTableFilters = name => {\n    const filters = tableFilters(name);\n    // These filters cannot join tables and must be simple strings\n    for (const filter of filters) {\n      if (Array.isArray(filter)) {\n        throw new CompileError(\n          'Invalid internal table filter: only object filters are supported',\n        );\n      }\n      if (Object.keys(filter)[0].indexOf('.') !== -1) {\n        throw new CompileError(\n          'Invalid internal table filter: field names cannot contain paths',\n        );\n      }\n    }\n    return filters;\n  };\n\n  const tableRef = (name: string, isJoin?: boolean) => {\n    const view =\n      typeof tableViews === 'function'\n        ? tableViews(name, { withDead, isJoin, tableOptions })\n        : tableViews[name];\n    return view || name;","sourceCodeStart":1076,"sourceCodeEnd":1112,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/aql/compiler.ts#L1076-L1112","documentation":"When AQL queries internal tables, `tableFilters` supplies implicit filters for that table. These filters must be simple `{field: value}` objects because they are compiled without join support; if a filter is an array (array-form condition), CompileError is thrown since array filters may reference joins that internal table filters cannot handle.","triggerScenarios":"Providing a custom `tableFilters` mapping for an internal table where a filter is written in the array/condition-list form `[['amount', '$gt', 5]]` instead of the object form `{amount: 5}`.","commonSituations":"Extending the AQL schema with views/dead-keys filters and reusing condition arrays from elsewhere, or constructing filters programmatically and mixing the two accepted condition forms.","solutions":["Rewrite the filter as a plain object: `{field: value}` or `{field: {$op: value}}`.","Move complex join-dependent conditions out of tableFilters into the query itself.","Add a preprocessing step that converts array-form conditions to object form before registering table filters."],"exampleFix":"// before\ntableFilters = { tombstone: ['tombstone', '=', 0] }\n// after\ntableFilters = { tombstone: { tombstone: 0 } }","handlingStrategy":"validation","validationCode":"function assertObjectFilters(filters) {\n  for (const f of filters) {\n    if (Array.isArray(f)) throw new Error('Internal table filters must be object form');\n  }\n}","typeGuard":"const isObjectFilter = (f) => typeof f === 'object' && f !== null && !Array.isArray(f);","tryCatchPattern":null,"preventionTips":["Always write tableFilters entries in {field: value} object form.","Keep join-dependent conditions in the main query, not tableFilters.","Add a unit test for any custom tableFilters mapping."],"tags":["aql","schema","internal-table-filters"],"backgroundTag":"invalid-table-filter","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}