# Costflow Syntax v0.3

Costflow is a productivity tool for plain text accounting. By connecting messaging apps with cloud storage services, it can be much easier to create new directives/entries, especially on mobile devices. You can get more details in this post.

Costflow Syntax is the rule for parsing plain text to the accounting tools formats, such as Beancount, Ledger and hledger, etc.

You can try Costflow Syntax on Costflow Playground while you are reading. All products that support Costflow Syntax are listed on Docs homepage.

# Features

  • Date is optional, the default value is 'today' in your timezone.
  • Currency/Commodity code is optional.
  • Account name replacements. E.g. bofa in your message will be replaced with Assets:US:BofA:Checking.
  • Get real time price for exchanging rate or stock, even cryptocurrency.
  • Simple transaction syntax.
  • Custom indent and line length.
  • Added in v0.2: Insert links.
  • Added in v0.2: Add time info to transactions.
  • Added in v0.2: Simpler date format.
  • Added in v0.3: Formula, make creating transactions with patterns easier, for sure.

All belowing contents are based on Costflow Syntax v0.3 and Beancount is the output format supoorted.

# Commands

The 'command' is the first word/symbol of the texts (exclude date). Some commands are same with 'directives' in Beancount.

Command Description
* Completed transaction. Optional.
! Incomplete transaction.
f Formula. Added in v0.3. For transactions with patterns.
; Comment. It will be saved to your ledger file.
// Comment. It will only be saved to Costflow, not your ledger file.
open Open directive.
close Close directive.
commodity Commodity directive.
option Option directive.
note Note directive.
balance Balance directive.
pad Pad directive.
price Price directive.
$ Get the latest exchange rate between currencies or price of cryptocurrencies or stocks. It will not be saved to your ledger file, just return the result.
event Event directive.
If no command found and
If the first word is one of formula keywords, the command will be f;
If message contains numbers, then the command will be *;
Otherwise, the command will fall back to //

# Config

To make the syntax easier, users will be asked to fill the following fileds to config:

Config Required Type Detail Example
mode Y String Output format, only support beancount in this version。 beancount
currency Y String Default currency code, in ISO 4217 format CNY
timezone Y String Timezone, in IANA format Asia/Hong_Kong
indent N Number Indent length, for transaction commands. 2
lineLength N Number The length before currency code(include), for transaction commands. 80
tag N String Apply tag(s) to every transaction automatically. Start with #, separated with blank space if multiple. #trip #food
link N String Apply link(s) to every transaction automatically. Start with ^, separated with blank space if multiple. ^project-x
insertTime N String Insert time to every transaction, the value is the position to insert. Currently only metadata allowed. metadata
alphavantage N String When you want to use pricing or $ command on Costflow Playground, you should apply your own Alpha Vantage API token. No need for Costflow Hub. TRBZ2Y1D7TN0ZHFO
replacement N Object Use key: value format. Replacing the key with the value of your input. The value is the account name in your legder file. {"bofa": "Assets:US:BofA:Checking"}
formula N Object Use key: value format. Formula will parse the input first and run Costflow Syntax again. The key is the formula keyword, some variables can be used in the value. Check formula docs. {"gcp": "@Google USD visa > cloud"}

# General Rules:

  • The date is optional. If no YYYY-MM-DD format date found, the date will be 'today' in your timezone. Use '2019-07-01' in the following examples. Start from v0.2, we support more date formats:

    • Month name: Jul 25 / July 10 / Aug 2. The month name can be full name or three-letter abbreviations, the first letter should be uppercase.
    • Relative dates:
      • Yesterday: yesterday / ytd
      • The day before yesterday: dby
      • Tomorrow: tomorrow / tmr, useful for Balance command.
      • The day after tomorrow: dat
  • Commodity/Currency code is optional, too, the default value is what you set before. Use 'USD' in following examples.

  • Metadata is not supported in this version.

  • Inline comment is not supported in this version.

  • Commodity/Currency should be a word all in capital letters.

  • The abbreviation of an account name should not be a word all in capital letters or numbers.

  • If the transaction is 2019-07-01 * "McDonald's" "🍔", and the insertTime is metadata, the time info will be applied like:

    2019-07-01 *  "McDonald's" "🍔"
      time: "11:22:33"
      ...
    
    

Here is the config will be used in following examples.

{
    "mode": "beancount",
    "currency": "CNY",
    "timezone": "Asia/Hong_Kong",
    "tag": "#costflow",
    "link": "",
    "indent": 2,
    "lineLength": 60,
    "insertTime": "",
    "replacement": {
        "boc": "Assets:CN:BOC",
        "bofa": "Assets:US:BofA:Checking",
        "cloud": "Expenses:Cloud",
        "cmb": "Liabilities:CreditCard:CMB",
        "eob": "Equity:Opening-Balances",
        "food": "Expenses:Food",
        "phone": "Expenses:Home:Phone",
        "rent": "Expenses:Home:Rent",
        "rx": "Assets:Receivables:X",
        "ry": "Assets:Receivables:Y",
        "subscription": "Expenses:Subscriptions",
        "visa": "Liabilities:CreditCard:Visa"
    },
    "formula": {
        "☕️": "@Leplays ☕️ {{ amount }} Liabilities:CreditCard:Visa > Expenses:Coffee",
        "c2f": "{{ pre }} cmb > food",
        "aws": "@AWS {{ amount }} USD visa > cloud",
        "spotify": "@Spotify 15.98 USD visa > subscription"
    }
}

# Transaction

Transactions are the most common type of directives that occur in a ledger. Costflow Syntax support two methods for transactions.

# First: >

The first one uses greater-than sign, it means the flow direction from its left to its right. The general format is:

[YYYY-MM-DD] [Flag] ["Payee"] ["Narration"] [#tag] [^link] AMOUNT [COMMODITY] FROM_ACCOUNT > [AMOUNT] [COMMODITY] TO_ACCOUNT

The elements in square brackets are optional. Rules details:

  • The date is optional. If no YYYY-MM-DD format date found, the date will be 'today' in your timezone;

  • The flag is used to indicate the status of a transaction, * means completed transaction, ! means incomplete transaction. * can be omitted;

  • Payess and Narration are easy to use, but the syntax is a little bit complex:

    • Compatible with Beancount double quotes format: if there is only one double-quotes string, it is a narration; If there are two double-quotes strings, the first one is a payee and the second one is narration. Not support the legacy pipe symbol format;
    • Double-quotes can be omitted in these cases:
      • Input narration directly without double-quotes, but the narration cannot contain numbers. E.g., 'Xbox 360 Game' is not a valid narration;
      • Payee can be used in @Payee format. In this rule, payee cannot contain blank spaces, but payee can be connected with - or _. E.g., @KFC and @Burger_King are all valid payees;
      • If you only want to record payee, just input @Payee, no need to concern narration;
    • Tag: strings start with # are treated as tags;
    • Link: strings start with ^ are treated as links;
    • In the first transaction syntax, the order of AMOUNT / COMMODITY / ACCOUNT should be:
      1. AMOUNT. Because > means the flow direction, so the default amounts in left side are negative, the minus sign can be omitted;
      2. COMMODITY. Optional, the default value is what you set before;
      3. ACCOUNT. Accounts can be the full name in your ledger file, or they can be the abbreviations you set before.
    • The right side rules are the same with the left side, except the default amounts are positive;
    • Costs and prices are same with Beancount;
    • If there are multiple accounts in either side, use + to connect, Be aware, only one > allowed in a message;
    • The amounts in the right side can be omitted, they are equal to sum(left side amounts)/(right side accounts number)*. Useful when splitting expenses with friends.

    Examples, finally!

    // #1 Traditional format with >
    2017-01-05 "RiverBank Properties" "Paying the rent" 2400 Assets:US:BofA:Checking > 2400  Expenses:Home:Rent
    // #1 Output
    2017-01-05 * "RiverBank Properties" "Paying the rent"
      Assets:US:BofA:Checking                        -2400.00 USD
      Expenses:Home:Rent                             +2400.00 USD
    
    
    // #2 No date, no commodity symbol, use @Payee
    @Verizon 59.61 Assets:US:BofA:Checking > Expenses:Home:Phone
    // #2 Output
    2019-07-01 * "Verizon" ""
      Assets:US:BofA:Checking                          -59.61 USD
      Expenses:Home:Phone                              +59.61 USD
    
    
    // #3 Use text replacements
    @Verizon 59.61 bofa > phone
    // #3 Output is exactly same with #2
    
    
    // #4 Multiple accounts flow to one
    Rent 750 cmb + 750 boc > rent
    // #4 Output
    2019-07-01 * "Rent"
      Liabilities:CreditCard:CMB                     -750.00 USD
      Assets:CN:BOC                                  -750.00 USD
      Expenses:Home:Rent                            +1500.00 USD
    
    
    // #5 One account flows to multiple accounts. The scene in this example is you have dinner with your friends, you pay the bill for your friends(x and y), then your friends will pay you back.
    Dinner 180 CNY bofa > rx + ry + food
    // #5 Output
    2019-07-01 * "Dinner"
      Assets:Receivables:X                            +60.00 CNY
      Assets:Receivables:Y                            +60.00 CNY
      Expenses:Food                                   +60.00 CNY
      Liabilities:CreditCard:CMB                     -180.00 CNY
    
    
    // #6 Transaction with cost
    Transfer to account in US 5000 CNY @@ 726.81 USD boc > 726.81 bofa
    // #6 Output
    2019-07-01 * "Transfer to account in US"
      Assets:CN:BOC                                 -5000.00 CNY @@ 726.81 USD
      Assets:US:BofA:Checking                        +726.81 USD
    

# Second: |

The second method uses the pipe sign (U+007C) instead of >, it means "start a new line", the timing is the same with Beancount. The syntax detail:

[YYYY-MM-DD] [Flag] ["Payee"] ["Narration"] [#tag] [^link] | ACCOUNT_A [COMMODITY] AMOUNT | ACCOUNT_B [COMMODITY] AMOUNT

As we can see, the usage of Date, Flag, Payee, Narration, Tag, Link are all same with the first one. The differences:

  • Should insert a | before each account;
  • The order of ACCOUNT / COMMODITY / AMOUNT is different from the first one but same with Beancount;
  • The second transaction syntax has no flow direction, so the minus sign and amounts cannot be omitted;

We can rewrite the examples with |:

// #1 Traditional format with |
2017-01-05 "RiverBank Properties" "Paying the rent" | Assets:US:BofA:Checking -2400 | Expenses:Home:Rent 2400
// #1 Output
2017-01-05 * "RiverBank Properties" "Paying the rent"
  Assets:US:BofA:Checking                        -2400.00 USD
  Expenses:Home:Rent                             +2400.00 USD


// #2 No date, no commodity symbol, use @Payee
@Verizon | Assets:US:BofA:Checking -59.61 | Expenses:Home:Phone 59.61
// #2 Output
2019-07-01 * "Verizon" ""
  Assets:US:BofA:Checking                          -59.61 USD
  Expenses:Home:Phone                              +59.61 USD


// #3 Use text replacements
@Verizon | bofa -59.61 | phone 59.61
// #3 Output is exactly same with #2


// #4 Multiple accounts flow to one
Rent | cmb -750 | boc -750 | rent 1500
// #4 Output
2019-07-01 * "Rent"
  Liabilities:CreditCard:CMB                     -750.00 USD
  Assets:CN:BOC                                  -750.00 USD
  Expenses:Home:Rent                            +1500.00 USD


// #5 One account flows to multiple accounts. The scean in this example is you have dinner with your friends, you pay the bill for your friends(x and y), then your friends will pay you back.
Dinner | bofa 180 CNY | rx -60 | ry -60 | food -60
// #5 Output
2019-07-01 * "Dinner"
  Assets:Receivables:X                            +60.00 CNY
  Assets:Receivables:Y                            +60.00 CNY
  Expenses:Food                                   +60.00 CNY
  Liabilities:CreditCard:CMB                     -180.00 CNY


// #6 Transaction with cost
Transfer to account in US | boc -5000 CNY @@ 726.81 USD  | bofa +726.81
// #6 Output
2019-07-01 * "Transfer to account in US"
  Assets:CN:BOC                                 -5000.00 CNY @@ 726.81 USD
  Assets:US:BofA:Checking                        +726.81 USD


# Formula

Added in v0.3. Formula is for making creating transactions with patterns easier, such as Netflix and Spotify subscriptions, Uber and Lyft bills, even salaries.

Like replacement, a config is needed before start using formula. The formula config is key: value format. The key is the formula name (can't be a command name), the value is the content to parse with Costflow Syntax. Some variables are allowed in the value. The variable should be wrapped in double curly braces . The variables allowed in current version:

  • amount: the first number in the input. You can use some basic math operations with the amount, e.g. amount * 0.8;
  • pre: the original input excludes the formula name;

More variables will be supported in future versions.

The way formula works is: convert the formula based on its config, then parse the value with Costflow Syntax all over again. So the value should follow Costflow Syntax too.

For example, we wrote a formula config for AWS transaction:

{
  "aws": "@AWS {{ amount }} USD visa > cloud"
}

If we run f aws 60 or just aws 60 in Costflow Hub / CLI / Playground, the formula will run like this:

Formula flowchart

Formula is created for transactions, but it can be used for other directives. Here are some example formula config:

{
  "formula": {
    "aws": "@AWS {{ amount }} USD visa > aws",
    "cashback": "Airline Cash Back {{ amount }} visa > {{ amount * 0.95 }} expenseFlight + {{ amount * 0.05 }} incomeCashback",
    "netflix": "@Netflix 15.99 USD visa > netflix",
    "split": "Dinner {{ amount }} visa > food + rx + ry",
    "spotify": "@Spotify 14.99 USD visa > spotify",
    "uber": "@Uber {{ amount }} USD visa > uber",
    "rent": "Rent {{ amount }} USD visa > rent",
    "v2f": "{{ pre }} visa > food",
    "☕️": "@Leplays ☕️ {{ amount }}  Liabilities:CreditCard:Visa > Expenses:Coffee"
  }
}

# Comment

; and // are easy to use as comments, just type anything after them.

Examples:

; I paid and left the taxi, forgot to take change, it was cold.
// to do: cancel Netflix subscription

The difference is that comments with ; will be saved to Costflow first, then appended to the ledger file, comments with // will only be saved to Costflow. // is useful for reminding yourself to do some financial actions.

# Open

Same with Beancount. Do not support metadata in this version.

[YYYY-MM-DD] open ACCOUNT

Examples:

// Input
open Assets:US:BofA
// Output
2019-07-01 open Assets:US:BofA

# Close

Same with Beancount.

// Input
close Assets:US:BofA
// Output
2019-07-01 close Assets:US:BofA

# Commodity

Same with Beancount. Do not support metadata in this version.

[YYYY-MM-DD] commodity SYMBOL

Examples:

// Input
commodity BTC
// Output
2019-07-01 commodity BTC

# Option

Syntax:

option [NAME] VALUE

Explanation:

NAME and VALUE usually are double-quotes strings, but in the following cases double-quotes can be omitted:

  • If NAME is "title", just input option VALUE, VALUE does not need double-quotes;
  • If VALUE is one of currency codes in ISO 4217, the NAME can be omitted (will be parsed to operating_currency), VALUE does not need double-quotes;

Examples:

// Input
option Example Costflow file
// Output
option "title" "Example Costflow file"

// Input
option CNY
// Output
option "operating_currency" "CNY"

// Input
option "conversion_currency" "NOTHING"
// Output
option "conversion_currency" "NOTHING"

# Note

Syntax:

[YYYY-MM-DD] note ACCOUNT DESCRIPTION

Explanation:

  • ACCOUNT can be the full name in your ledger file, or they can be the abbreviations you set before;
  • DESCRIPTION can be any strings, no need double-quotes.

Examples

// Input
note bofa Called about fraudulent card.
// Output
2019-07-01 note Assets:US:BofA:Checking "Called about fraudulent card."

# Balance

Syntax:

[YYYY-MM-DD] balance ACCOUNT AMOUNT [COMMODITY]

Explanation:

  • ACCOUNT can be the full name in your ledger file, or they can be the abbreviations you set before;
  • COMMODITY is optional, the default value is what you set before;

Examples:

// Input
balance bofa 360
// Output
2019-07-01 balance Assets:US:BofA:Checking 360 USD

// Input with simpler date format. Because Beancount applies the time of all non-transaction directives at the beginning of its date, we usually use the next day for this directive. https://docs.google.com/document/d/1wAMVrKIA2qtRGmoVDSUBJGmYZSygUaR0uOMW1GV3YE0/edit#heading=h.l0pvgeniwvq8
tmr balance bofa 360
// Output
2019-07-02 balance Assets:US:BofA:Checking 360 USD

# Pad

Syntax:

[YYYY-MM-DD] pad ACCOUNT ACCOUNT_PAD

Explanation:

  • ACCOUNT and ACCOUNT_PAD can be the full name in your ledger file, or they can be the abbreviations you set before;
  • Use pad carefully. Check Beancount docs.

Examples:

// Input
pad bofa eob
// Output
2019-07-01 pad Assets:US:BofA:Checking Equity:Opening-Balances

# Price

Syntax:

[YYYY-MM-DD] price COMMODITY_A [PRICE] [COMMODITY_B]

Explanation:

  • COMMODITY_B is optional, the default value is what you set before;
  • Set the PRICE manually: just use a number as PRICE like Beancount;
  • If there is no PRICE found, Costflow will automatically get the latest exchange rates of currencies, or the latest price of cryptocurrencies and stock (by Alpha Vantage](https://www.alphavantage.co/). The priority:
    1. Firstly, Costflow will check whether COMMODITY_A is one of ISO 4217 currency codes. If it is, get the exchange rate;
    2. Secondly, Costflow will check whether COMMODITY_A is one of cryptocurrency codes that Alpha Vantage support (support list). If it is, get the cryptocurrency price;
    3. Finally, Costflow will treat COMMODITY_A as a stock code and get its price. Only stocks listed on NASDAQ and NYSE are supported in this version. Note, COMMODITY_B should not be set, if COMMODITY_A is a stock code, COMMODITY_B is inherited from the stock market.

Examples:

// #1 Input
2017-01-17 price USD 1.08 CAD
// #1 Output
2017-01-17 price USD 1.08 CAD

// #2 Input, get the exchange rate automatically. The word "to" is only for understanding, can be omitted.
price CAD to USD
// #2 Output
2019-07-01 price 

// #3 Input, get Bitcoin price
price BTC
// #3 Output
2019-07-01 price BTC 11946.64 USD

// #4 Input, get Apple stock price
price AAPL
// #4 Output
2019-07-01 price AAPL 199.8 USD

# $

$ is for getting the latest exchange rate of currencies or the latest price of cryptocurrencies and stocks. The result will be returned directly, will not be saved to the ledger file. The syntax:

$ [AMOUNT] COMMODITY_A [to] COMMODITY_B

Explanation:

  • COMMODITY_B is optional, the default value is what you set before;
  • AMOUNT is optional, the default amount is 1;
  • The word "to" is only for understanding, can be omitted;
  • The priority is the same with Price:
    1. Firstly, Costflow will check whether COMMODITY_A is one of ISO 4217 currency codes. If it is, get the exchange rate;
    2. Secondly, Costflow will check whether COMMODITY_A is one of cryptocurrency codes that Alpha Vantage support (support list). If it is, get the cryptocurrency price;
    3. Finally, Costflow will treat COMMODITY_A as a stock code and get its price. Only stocks listed on NASDAQ and NYSE are supported in this version. Note, COMMODITY_B should not be set, if COMMODITY_A is a stock code, COMMODITY_B is inherited from the stock market.

Examples:

// Input, get the exchange rate automatically. The word "to" is only for understanding, can be omitted.
$ CAD to USD
// Return
1 CAD = 0.7637 USD

// Input, get 10 Bitcoin price
$ 10 BTC
// Return
10 BTC = 119466.4 USD

// Input, get Apple stock price
$ AAPL
// Return
AAPL 199.8 USD (-0.030%)

# Event

Syntax:

[YYYY-MM-DD] event NAME VALUE

Explanation:

  • NAME and VALUE can use double-quotes like Beancount;
  • If no double-quotes found, the first word (exclude date) will be saved as NAME, the rest part will be saved as VALUE;

Examples:

// Input, traditional Beancount format
2017-01-02 event "location" "Paris, France"
// Output
2017-01-02 event "location" "Paris, France"

// Input, without double-quotes
event location Paris, France
// Output
2017-01-02 event "location" "Paris, France"

# Links