Simplify repeitive rules
Now you know how to write a basic import rule to match a recurring transaction. However, we have only imported DoorDash transactions. There are still many open transactions in the import command's output.

Those in the open transactions are transactions extracted from the input CSV files but don't match any rules. The Netflix and Comcast transactions are obviously repeating. We need to add new rules for them. To match both of them, you can add two new rules to your file to make your import rule file look like this:
inputs:
- match: "import-data/connect/chase/*.csv"
imports:
- name: DoorDash Food Delivery
match:
extractor:
equals: "plaid"
desc:
prefix: "DD *DOORDASH"
actions:
- file: main.bean
txn:
flag: "{{ '!' if pending else '*' }}"
payee: "{{ payee | default(omit, true) }}"
narration: "DoorDash food delivery"
postings:
- account: Liabilities:ChaseCreditCard
amount:
number: "{{ -amount }}"
currency: "{{ currency | default('USD', true) }}"
- account: "Expenses:FoodDelivery:DoorDash"
amount:
number: "{{ amount }}"
currency: "{{ currency | default('USD', true) }}"
- name: Netflix
match:
extractor:
equals: "plaid"
desc: "Netflix"
actions:
- file: main.bean
txn:
flag: "{{ '!' if pending else '*' }}"
payee: "{{ payee | default(omit, true) }}"
narration: "Netflix subscription"
postings:
- account: Liabilities:ChaseCreditCard
amount:
number: "{{ -amount }}"
currency: "{{ currency | default('USD', true) }}"
- account: "Expenses:Entertainment:StreamingService"
amount:
number: "{{ amount }}"
currency: "{{ currency | default('USD', true) }}"
- name: Comcast
match:
extractor:
equals: "plaid"
desc: "Comcast"
actions:
- file: main.bean
txn:
flag: "{{ '!' if pending else '*' }}"
payee: "{{ payee | default(omit, true) }}"
narration: "Comcast internet service"
postings:
- account: Liabilities:ChaseCreditCard
amount:
number: "{{ -amount }}"
currency: "{{ currency | default('USD', true) }}"
- account: "Expenses:Internet"
amount:
number: "{{ amount }}"
currency: "{{ currency | default('USD', true) }}"
And then you run the import command, you should see output like this:

Avoid repeating with prepending and appending postings for input files
Although this works perfectly, you have probably already noticed.
There are many repeating parts in our rules.
For example, we always have the same posting for the Chase credit card account Liabilities:ChaseCreditCard
for each posting part.
postings:
- account: Liabilities:ChaseCreditCard
amount:
number: "{{ -amount }}"
currency: "{{ currency | default('USD', true) }}"
Since all these transactions come from a specific credit card, we know which account to use.
Therefore, we provide extra options in the input, allowing you to define prepending and appending posting for all Beancount transactions generated from any rules.
The output file name is also always main.bean
.
Unless we want to specify it as a different output file name for a particular rule, we can add the default_file
field in the config to make it apply as a default value for all generated transactions from this particular input file.
With that in mind, we can update our input config like this:
inputs:
- match: "import-data/connect/chase/*.csv"
config:
default_file: main.bean
prepend_postings:
- account: Liabilities:ChaseCreditCard
amount:
number: "{{ -amount }}"
currency: "{{ currency | default('USD', true) }}"
imports:
- name: DoorDash Food Delivery
match:
extractor:
equals: "plaid"
desc:
prefix: "DD *DOORDASH"
actions:
- txn:
flag: "{{ '!' if pending else '*' }}"
payee: "{{ payee | default(omit, true) }}"
narration: "DoorDash food delivery"
postings:
- account: "Expenses:FoodDelivery:DoorDash"
amount:
number: "{{ amount }}"
currency: "{{ currency | default('USD', true) }}"
- name: Netflix
match:
extractor:
equals: "plaid"
desc: "Netflix"
actions:
- txn:
flag: "{{ '!' if pending else '*' }}"
payee: "{{ payee | default(omit, true) }}"
narration: "Netflix subscription"
postings:
- account: "Expenses:Entertainment:StreamingService"
amount:
number: "{{ amount }}"
currency: "{{ currency | default('USD', true) }}"
- name: Comcast
match:
extractor:
equals: "plaid"
desc: "Comcast"
actions:
- txn:
flag: "{{ '!' if pending else '*' }}"
payee: "{{ payee | default(omit, true) }}"
narration: "Comcast internet service"
postings:
- account: "Expenses:Internet"
amount:
number: "{{ amount }}"
currency: "{{ currency | default('USD', true) }}"
We also removed the balancing posting from all import rules since they are not needed anymore.
Not just prepend_postings,
you can also use append_postings
if you want the postings to append in the end instead of the front.
If you rerun the import command right now, you should get exactly the same result without any changes.
Use match with variables syntax to combine rules
We have already simplified the import rules a bit. However, you can see that there are still many repetitive parts. The rules for DoorDash, Netflix, and Comcast are slightly different from each other, but they are mostly the same. To solve the problem, we provide an advanced type of match syntax that allows you to match different transactions and provide corresponding variables to be used in the template. We call it match with variables. You can read its documents here.
With the new syntax, we can rewrite the rules as just like this:
inputs:
- match: "import-data/connect/chase/*.csv"
config:
default_file: "main.bean"
prepend_postings:
- account: Liabilities:ChaseCreditCard
amount:
number: "{{ -amount }}"
currency: "{{ currency | default('USD', true) }}"
imports:
- name: Routines
common_cond:
extractor:
equals: "plaid"
match:
- cond:
desc:
prefix: "DD *DOORDASH"
vars:
narration: DoorDash food delivery
account: Expenses:FoodDelivery:DoorDash
- cond:
desc: "Netflix"
vars:
narration: Netflix subscription
account: Expenses:Entertainment:StreamingService
- cond:
desc: "Comcast"
vars:
account: Expenses:Internet
actions:
- txn:
flag: "{{ '!' if pending else '*' }}"
payee: "{{ payee | default(omit, true) }}"
narration: "{{ narration | default(desc, true) }}"
postings:
- account: "{{ account }}"
amount:
number: "{{ amount }}"
currency: "{{ currency | default('USD', true) }}"
This is way better!
The common_cond
part defines the common conditions we want to match for all conditions in this rule.
Under match
, each cond
defines how it wants to match the transactions.
The vars
field defines values for variables to be consumed in the action templates.
Starting from here, you can easily extend new conditions to match the new repetitive transactions you have recognized in the Open Transactions section. No more copy-paste is needed.