Write your own report
Write a User report as a .rhai script in the report directory, check that report.list shows it, then run it with report.run and export it with report.export.
GnuCash comes with reports of its own, and red-ale has translated most of them. Others, such as GnuCash's business reports
and the sample reports you may have written for it, are left for you to write. A User report is a Rhai script, a file
ending in .rhai in the report directory. red-ale loads it each time a report method runs, lists it beside the
built-in reports, and runs it in a sandbox: it can read the open book but cannot change it, read other files, reach the
network or ask the clock.
You can write one yourself, or ask an AI agent to. Either way the steps are the same, and the script is an ordinary text file. There is no method that takes a script's text, so the file must be put in the report directory by something that can write files on the machine where red-ale runs: you, your editor, or an agent with file access.
Steps
- Choose the report directory and tell red-ale where it is, with config.set:
{"changes": {"reports.directory": "/home/me/red-ale-reports"}}. config.get with the pathreports.directoryreads it back. Scripts may be in sub-directories; at most 1,000 files of at most 1 MiB are read. - Write the script. Save the one below as
asset-balances.rhaiin that directory. A script defines three functions:templates()names the reports it provides,options()declares the options a user can set, andrender()builds the report from the book. - Check that it loaded. Call report.list. The new report is in
reportswith"kind": "user"and its script's path. If the script has a mistake, it is not listed; insteadproblemsnames the file, line and column and says what is wrong (report-load). - Run it with report.run, by name. You get the report result as data, with exact figures. A fault at run time is a report-script error with the line and column. Edit the file and run it again: there is nothing to restart.
- Export it with report.export for a page you can read or print.
- Share the options. An option you declare shows up in report.options and can be set in
options, like those of the built-in reports. Save a favourite set withreport.saveConfiguration(Run and export a report).
A minimal script
This report lists every asset account that can hold transactions, with its balance now:
// Asset Balances: every asset account that can hold transactions, with its balance now.
fn templates() {
[#{ guid: "5a1e0c9d2b7a4e3f8a5b1c2d3e4f5a6b", name: "Asset Balances", menuPath: ["_Custom"] }]
}
fn options(params, o, book) {
o.string("General", "Report Title", "a", "Title for this report.", "Asset Balances");
}
fn render(params, book, opts, ctx) {
let rows = book.accounts()
.filter(|a| a.type == "ASSET" && !a.placeholder)
.map(|a| #{ cells: [#{ text: a.fullName }, #{ number: a.balance_as_of(ctx.now), places: 2 }] });
#{
title: opts.get("General", "Report Title"),
sections: [#{ blocks: [#{
kind: "table",
columns: [#{ label: "Account", role: "label" }, #{ label: "Balance", role: "amount" }],
rows: rows
}] }]
}
}
- The
guidis any 32 lowercase hex digits you make up, and it must be unique among the reports. Keep it when you edit the script, so saved configurations still find it. book.accounts()returns the book's accounts.a.balance_as_of(ctx.now)is an exact rational, and thenumbercell shows it to two places. Money is never a floating-point number in a script.render()returns a map of the report result: atitleandsectionsofblocks(tables, text, charts).
Running it on a book whose ASSET account Root:Asset:Bank holds 2,186.00 gives a table whose rows look like this (shown, not run, since
an Example cannot put a file in the directory):
{"role": "data", "cells": [{"text": "Root:Asset:Bank"}, {"number": {"num": "2186", "denom": "1"}, "places": 2, "display": "2,186.00"}]}
The engine's whole interface, with the reads a script can make, the option types, the arithmetic and the limits, is on the Report scripts concept page. The built-in reports are written the same way and are worked examples.
Example
A directory that does not exist yet is reported under problems. Setting the directory and listing the reports works on any book. A script in the directory would appear in this list:
"kind": "builtin" marks the reports that come with red-ale, and a User report has "kind": "user".
Example: with a copy of the reports/test-data example book open as book.gnucash, at 2024-03-15T12:00:00Z.
JSON-RPC
Request:
{"jsonrpc":"2.0","id":1,"method":"config.set","params":{"changes":{"reports.directory":"reports"}}}
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"values": {
"reports.directory": "reports"
},
"clearedKeys": [],
"configFile": null
}
}
Request:
{"jsonrpc":"2.0","id":2,"method":"report.list","params":{}}
Response:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"reports": [
{
"name": "Account Summary",
"kind": "builtin",
"…": "…"
},
"…"
],
"problems": "…"
}
}
Request:
{"jsonrpc":"2.0","id":3,"method":"report.run","params":{"report":"Account Summary"}}
Response:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"title": "…",
"report": {
"name": "Account Summary",
"…": "…"
},
"sections": "…",
"…": "…"
}
}
Request:
{"jsonrpc":"2.0","id":4,"method":"report.export","params":{"report":"Account Summary"}}
Response:
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"title": "…",
"html": "…"
}
}
Command line
red-ale exec --book book.gnucash --script - <<'EOF'
config.set {"changes":{"reports.directory":"reports"}}
report.list {}
report.run {"report":"Account Summary"}
report.export {"report":"Account Summary"}
EOF
It prints:
[
{
"values": {
"reports.directory": "reports"
},
"clearedKeys": [],
"configFile": null
},
{
"reports": [
{
"name": "Account Summary",
"kind": "builtin",
"…": "…"
},
"…"
],
"problems": "…"
},
{
"title": "…",
"report": {
"name": "Account Summary",
"…": "…"
},
"sections": "…",
"…": "…"
},
{
"title": "…",
"html": "…"
}
]
MCP tool call
Tool call:
{"name":"config.set","arguments":{"changes":{"reports.directory":"reports"}}}
Result:
{
"structuredContent": {
"values": {
"reports.directory": "reports"
},
"clearedKeys": [],
"configFile": null
}
}
Tool call:
{"name":"report.list","arguments":{}}
Result:
{
"structuredContent": {
"reports": [
{
"name": "Account Summary",
"kind": "builtin",
"…": "…"
},
"…"
],
"problems": "…"
}
}
Tool call:
{"name":"report.run","arguments":{"report":"Account Summary"}}
Result:
{
"structuredContent": {
"title": "…",
"report": {
"name": "Account Summary",
"…": "…"
},
"sections": "…",
"…": "…"
}
}
Tool call:
{"name":"report.export","arguments":{"report":"Account Summary"}}
Result:
{
"structuredContent": {
"title": "…",
"html": "…"
}
}