[GH-ISSUE #7137] AQL filter silently drops all but first operator when combining multiple operators on one field #52114

Closed
opened 2026-04-30 20:17:18 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @shmup on GitHub (Mar 6, 2026).
Original GitHub issue: https://github.com/actualbudget/actual/issues/7137

What happened?

When using the API's query builder to filter by a date range with multiple operators on a single field, only the first operator is applied. The rest are silently ignored.

For example:

const api = require('@actual-app/api');
const result = await api.runQuery(
  api.q('transactions')
    .filter({ date: { $gte: '2025-03-01', $lt: '2025-04-01' } })
    .select(['id', 'date', 'amount', 'category'])
);

Expected: only transactions in March 2025 (76 transactions)
Actual: all transactions from March 2025 onward (1398 transactions), the $lt was silently dropped

The root cause is in packages/loot-core/src/server/aql/compiler.ts. compileOp destructures only the first key from the operator object:

const [op] = Object.keys(opExpr);

So { $gte: ..., $lt: ... } only compiles $gte.

The array workaround works: { date: [{ $gte: ... }, { $lt: ... }] }, because that path maps each element to a separate compileOp call. But the object form is the more natural way to express a range and should work too.

Reproduction Steps

  1. Connect to an Actual Budget instance via @actual-app/api
  2. Run a query using runQuery with multiple operators on one field:
    api.runQuery(api.q('transactions').filter({ date: { $gte: '2025-01-01', $lt: '2025-02-01' } }).select(['id', 'date']))
  3. Observe that results include transactions outside the expected range, the upper bound ($lt) is ignored
  4. Compare with the array form which works correctly:
    .filter({ date: [{ $gte: '2025-01-01' }, { $lt: '2025-02-01' }] })

Environment

  • Hosting: Self-hosted (Docker)
  • Browser: N/A (API only, not browser)
  • OS: Linux (Fedora)
Originally created by @shmup on GitHub (Mar 6, 2026). Original GitHub issue: https://github.com/actualbudget/actual/issues/7137 ## What happened? When using the API's query builder to filter by a date range with multiple operators on a single field, only the first operator is applied. The rest are silently ignored. For example: ```js const api = require('@actual-app/api'); const result = await api.runQuery( api.q('transactions') .filter({ date: { $gte: '2025-03-01', $lt: '2025-04-01' } }) .select(['id', 'date', 'amount', 'category']) ); ``` Expected: only transactions in March 2025 (76 transactions) Actual: all transactions from March 2025 onward (1398 transactions), the `$lt` was silently dropped The root cause is in `packages/loot-core/src/server/aql/compiler.ts`. `compileOp` destructures only the first key from the operator object: ```js const [op] = Object.keys(opExpr); ``` So `{ $gte: ..., $lt: ... }` only compiles `$gte`. The array workaround works: `{ date: [{ $gte: ... }, { $lt: ... }] }`, because that path maps each element to a separate `compileOp` call. But the object form is the more natural way to express a range and should work too. ## Reproduction Steps 1. Connect to an Actual Budget instance via `@actual-app/api` 2. Run a query using `runQuery` with multiple operators on one field: `api.runQuery(api.q('transactions').filter({ date: { $gte: '2025-01-01', $lt: '2025-02-01' } }).select(['id', 'date']))` 3. Observe that results include transactions outside the expected range, the upper bound (`$lt`) is ignored 4. Compare with the array form which works correctly: `.filter({ date: [{ $gte: '2025-01-01' }, { $lt: '2025-02-01' }] })` ## Environment - **Hosting:** Self-hosted (Docker) - **Browser:** N/A (API only, not browser) - **OS:** Linux (Fedora)
Author
Owner

@youngcw commented on GitHub (Mar 6, 2026):

Your syntax is wrong, thats why it isn't working. Use this.

const api = require('@actual-app/api');
const result = await api.runQuery(
  api.q('transactions')
    .filter({ date: [{ $gte: '2025-03-01'}, {$lt: '2025-04-01' }], })
    .select(['id', 'date', 'amount', 'category'])
);

You can see examples of this on https://actualbudget.org/docs/api/actual-ql/

<!-- gh-comment-id:4012984939 --> @youngcw commented on GitHub (Mar 6, 2026): Your syntax is wrong, thats why it isn't working. Use this. ```js const api = require('@actual-app/api'); const result = await api.runQuery( api.q('transactions') .filter({ date: [{ $gte: '2025-03-01'}, {$lt: '2025-04-01' }], }) .select(['id', 'date', 'amount', 'category']) ); ``` You can see examples of this on https://actualbudget.org/docs/api/actual-ql/
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/actual#52114