Creating custom DSL rules
Write posting rules that turn your events into journal entries.
Overview
The posting engine uses a DSL to map events to journal lines. Built-in packs cover standard cases; custom rules handle your categories, vendors, and exceptions.
Open Accounting → Rules (/accounting/rules). The page has Categorization (counterparty patterns → event type) and Posting Rules (DSL packs that write journal lines).

You can also ask Luca (G then L) to draft a rule and review the proposal.
Rule shape
rule "Rule Name" {
on "event_type"
basis cash|accrual
sources [DEPOSITORY, CREDIT_CARD, ...]
priority 10
where <condition>
let variable = <expression>
post {
debit {
account: account("role.subrole")
amount: <expression>
currency: <expression>
memo: <expression>
}
credit {
account: account("cash.operating")
amount: <expression>
currency: <expression>
}
}
}Example: SaaS subscription payment
rule "SaaS Subscription Payment" {
on "vendor_payment"
basis cash
sources [DEPOSITORY]
priority 15
where contains(payload.category, "software")
let amt = coalesce(payload.total_amount, payload.amount)
let ccy = coalesce(payload.currency, "USD")
post {
debit {
account: account("expense.software")
amount: amt
currency: ccy
memo: coalesce(payload.vendor, "") ++ " — " ++ coalesce(payload.description, "")
}
credit {
account: account("cash.operating")
amount: amt
currency: ccy
}
}
}| Clause | Role |
|---|---|
on "vendor_payment" | Event type this rule handles |
basis cash | Recognize expense when cash moves |
sources [DEPOSITORY] | Bank accounts, not cards |
priority 15 | Higher wins when several rules match |
where … | Extra filter on the payload |
post { … } | Debit expense, credit cash |
Account roles
account("role.subrole") resolves to your chart via account mappings (workspace settings). Common roles include cash.operating, expense.software, expense.general, revenue.services, liability.accounts_payable, and asset.accounts_receivable.
Conditions
| Function | Example |
|---|---|
contains(str, substr) | contains(payload.category, "legal") |
starts_with(str, prefix) | starts_with(payload.vendor, "AWS") |
between(val, lo, hi) | between(payload.amount, 0, 1000) |
is_null(val) | is_null(payload.tax_amount) |
coalesce(a, b) | coalesce(payload.memo, "No memo") |
Combine with and, or, and not.
Give specialized rules a higher priority so they win over a general vendor_payment rule. You can also requires balance("liability.accounts_payable") > 0 when a payment should only post against an open payable.
Test before publish
In Luca:
Test this rule against a sample event with amount 500 and category software
Or POST /api/dsl/evaluate with the rule source and a sample payload. The response shows whether the rule matched and which journal lines it would post.
Publish
- Open Accounting → Rules
- Edit your custom pack (or create one)
- Add the rule — packs are versioned on each edit
- Published rules apply to future events
Your custom pack sits above standard and industry packs. See Industry rule packs and the rules reference.