Skip to main content

ActualQL Overview

Introduction

ActualQL is a new query language introduced in 0.0.129. This allows you to query data any way you like. For example, previously we provided a filterTransactions method that let you search transactions, but its behavior was baked into the backend. You couldn't change how transactions were sorted, search certain fields specifically, or sum the total amount.

ActualQL provides a lightweight syntax for querying data. It looks like this:

q('transactions')
.filter({
'category.name': 'Food',
date: '2021-02-20',
})
.select(['id', 'date', 'amount']);

The above query would return the id, date, and amount of all transactions with the Food category on 2021-02-20.

Currently the query language is mostly undocumented, but more docs will come soon. Most of Actual uses ActualQL, so you have access to the same functionality that Actual has.

Until we have better docs, here are few things you can do with ActualQL.

Running a query

You construct a query with q and run it with runQuery. The result is an object with a data prop. An example:

let { q, runQuery } = require('@actual-app/api');

let { data } = await runQuery(q('transactions').select('*'));

data will be an array of all the transactions in the system.

Specify split transaction behavior

Split transactions complicate things: when you sum transaction amounts, do you sum up all the subtransactions or do you just use the top-level transaction? When selecting transactions, which ones do you want?

The transactions table provides two different interfaces for transaction data to help with this. You can configure the type of data to return using options and passing a splits option:

q('transactions').select('*').options({ splits: 'inline' });

There are two different options for splits: inline or grouped*. inline is the default, and will not return the "parent" transaction of a split transaction. It will only show you subtransactions of split transaction, and the result is a flat array of transactions. This lets you sum all the amounts of transactions and by default it will ignore the "parent" transaction.

grouped always returns the full split transaction (parent and subtransaction), no matter which part of it matched a filter. The returned data is also grouped so that transactions have a subtransactions property that lists them.

These two options gives you full control over how you want to handle split transactions.

* There is a third option as well, all, which returns both transactions and subtransactions in a flat list. You only need this if doing something advanced.

Searching transactions

Calling filter applies conditions to the query; only data that matches the given filters will be returned.

The keys of a filter object are the names of the fields (see Transaction) and the values are the condition. By default, it will perform an "is equal" but you can also provide various operators. An example:

q('transactions')
.filter({
'category.name': 'Food',
date: { $gte: '2021-01-01' },
})
.select('*');

The $gte operator on date returns transactions on or after 2021-01-01. Available operators are $eq, $lt, $lte, $gt, $gte, and $like.

If you pass an array to a field, it will combine the conditions with $and:

q('transactions')
.filter({
date: [{ $gte: '2021-01-01' }, { $lte: '2021-12-31' }],
})
.select('*');

This is the same query but restricts it to transactions between 2021-01-01 and 2021-12-31. This could have been written as:

q('transactions')
.filter({
$and: [{ date: { $gte: '2021-01-01' } }, { date: { $lte: '2021-12-31' } }],
})
.select('*');

$and and $or takes an array and combines multiple conditions. For example, you could get transactions on multiple dates like this:

q('transactions')
.filter({
$or: [{ date: '2021-01-01' }, { date: '2021-01-02' }],
})
.select('*');

The above will return transactions on 2021-01-01 or 2021-01-02.