Skip to content

Metadata

This document specifies the metadata model for plain text accounting systems.

Definition

Metadata is a collection of key-value pairs attached to directives or postings, providing additional structured information beyond the core data model.

Metadata = Dict[String, Value]

Purpose

Metadata extends the data model without changing core semantics:

Use Case Example
Source tracking filename, lineno
Import correlation bank-ref, import-id
Custom categorization project, department
External references receipt, invoice-id
Processing hints check, cleared-date

Syntax

Directive Metadata

Metadata appears on lines following the directive header, indented:

2024-01-15 * "Purchase"
  order-id: "ABC123"
  category: "office"
  Assets:Checking  -100 USD
  Expenses:Office

Posting Metadata

Metadata on postings is further indented:

2024-01-15 * "Purchase"
  Assets:Checking  -100 USD
    cleared: TRUE
  Expenses:Office   100 USD
    project: "Q1-2024"

Multiple Metadata Lines

2024-01-15 open Assets:Checking
  institution: "First National Bank"
  account-number: "****1234"
  routing: "123456789"
  nickname: "Primary Checking"

Keys

Key Format

Keys are identifiers with format-specific rules:

Format Allowed Characters Example
Beancount a-z, 0-9, -, _ order-id
Ledger Any non-whitespace Order ID
hledger Any non-whitespace order_id

Reserved Keys

Some keys have special meaning:

Key Purpose
filename Source file path
lineno Source line number
__*__ Internal/system use

Key Conventions

lowercase-with-dashes     ; Preferred
snake_case                ; Alternative
camelCase                 ; Discouraged

Values

Value Types

Type Syntax Example
String Quoted "Hello World"
Number Decimal 123.45
Boolean TRUE/FALSE TRUE
Date ISO 8601 2024-01-15
Currency Amount 100 USD
Account Account name Assets:Checking
Tag Tag literal #project-x
Link Link literal ^invoice-001

String Values

name: "John Doe"
description: "Multi-word string"
path: "/path/to/file.pdf"

Numeric Values

quantity: 42
rate: 1.5
percentage: 0.15

Boolean Values

cleared: TRUE
pending: FALSE
taxable: TRUE

Date Values

due-date: 2024-02-15
filed: 2024-01-20

Amount Values

limit: 1000 USD
fee: 9.95 USD

Account Values

source: Assets:Checking
destination: Expenses:Food

Attachment Locations

On Transactions

2024-01-15 * "Grocery Store"
  receipt: "receipts/2024-01-15-grocery.pdf"
  Assets:Checking  -50 USD
  Expenses:Food

On Open Directives

2024-01-01 open Assets:Checking
  institution: "Bank of America"
  account-number: "****1234"

On Commodity Directives

2024-01-01 commodity USD
  name: "US Dollar"
  precision: 2

On Postings

2024-01-15 * "Multi-project expense"
  Expenses:Travel  500 USD
    project: "alpha"
    billable: TRUE
  Expenses:Travel  300 USD
    project: "beta"
    billable: FALSE
  Assets:Checking

Metadata Stacks

Push/Pop Mechanism

Metadata can be pushed onto a stack and automatically applied:

pushmeta project: "Q1-2024"

2024-01-15 * "Expense 1"    ; Gets project: "Q1-2024"
  ...

2024-01-20 * "Expense 2"    ; Gets project: "Q1-2024"
  ...

popmeta project

Stack Scoping

Stacks are typically scoped to the current file:

; main.beancount
pushmeta department: "Engineering"
include "projects.beancount"
; projects.beancount does NOT inherit department stack
popmeta department

System Metadata

Automatic Metadata

Parsers typically inject:

directive.meta['filename'] = "ledger.beancount"
directive.meta['lineno'] = 42

Usage in Error Messages

ERROR: Account not opened
  --> ledger.beancount:42:3

Querying Metadata

BQL Examples

-- Filter by metadata
SELECT * FROM transactions WHERE META('project') = 'alpha'

-- Include metadata in output
SELECT date, META('project'), SUM(amount)
FROM postings
GROUP BY 1, 2

Programmatic Access

for entry in entries:
    if 'project' in entry.meta:
        print(f"{entry.date}: {entry.meta['project']}")

Inheritance

Directive to Posting

Postings inherit transaction metadata:

2024-01-15 * "Purchase"
  project: "alpha"
  Expenses:Office  100 USD    ; Has project: "alpha"
  Assets:Checking

Override at Posting Level

2024-01-15 * "Purchase"
  project: "alpha"
  Expenses:Office:A  50 USD
    project: "beta"           ; Override: "beta"
  Expenses:Office:B  50 USD   ; Inherited: "alpha"
  Assets:Checking

Metadata Semantics

No Side Effects

Metadata is informational only:

2024-01-15 * "Purchase"
  magic-option: "do-something"  ; Does nothing automatically
  ...

Plugin Interpretation

Plugins may interpret metadata:

def plugin(entries, options):
    for entry in entries:
        if entry.meta.get('auto-categorize'):
            # Plugin-specific behavior
            pass

Common Metadata Patterns

Import Correlation

2024-01-15 * "AMZN MKTP"
  import-id: "chase-2024-01-15-001"
  bank-ref: "TXN123456789"
  ...

Document Attachment

2024-01-15 * "Office Supplies"
  receipt: "receipts/2024/01/office-supplies.pdf"
  ...

Project Tracking

2024-01-15 * "Client Meeting"
  project: "acme-corp"
  billable: TRUE
  hours: 2.5
  ...

Tax Classification

2024-01-15 * "Home Office Equipment"
  tax-category: "business-expense"
  depreciation-years: 5
  ...

Validation

Unknown Metadata Key

Typically a warning, not an error:

WARNING: Unknown metadata key 'typo-key'
  --> ledger.beancount:42:3
   |
42 |   typo-key: "value"
   |   ^^^^^^^^

Invalid Value Type

ERROR: Invalid metadata value type
  --> ledger.beancount:42:13
   |
42 |   quantity: not-a-number
   |             ^^^^^^^^^^^^
   |
   = expected: number

Implementation Model

@dataclass
class MetadataEntry:
    key: str
    value: Any
    line: int  # Source line for error reporting


class Metadata(dict):
    """Dictionary subclass for metadata with source tracking."""

    def __init__(self):
        super().__init__()
        self._sources: Dict[str, int] = {}

    def set(self, key: str, value: Any, line: int = 0):
        self[key] = value
        self._sources[key] = line

    def get_source_line(self, key: str) -> Optional[int]:
        return self._sources.get(key)


# Attached to directives
@dataclass
class Transaction:
    date: date
    flag: str
    payee: Optional[str]
    narration: str
    metadata: Metadata  # Key-value pairs
    postings: List[Posting]

Serialization

Text Format

2024-01-15 * "Purchase"
  key1: "string value"
  key2: 123
  key3: TRUE
  key4: 2024-01-15
  Assets:Checking  -100 USD
  Expenses:Office

JSON Format

{
  "date": "2024-01-15",
  "flag": "*",
  "narration": "Purchase",
  "metadata": {
    "key1": "string value",
    "key2": 123,
    "key3": true,
    "key4": "2024-01-15"
  },
  "postings": [...]
}

Cross-Format Notes

Feature Beancount Ledger hledger
Syntax key: value ; key: value ; key: value
Position Own line Comment Comment
Types Multiple String only String only
Stacks pushmeta/popmeta Not standard Not standard
Reserved filename, lineno None None