Specification
$imports: [ 'types/money', 'types/date', 'types/payment-terms' ]
$flags: [ 'CASE_INSENSITIVE', 'MULTILINE', 'FLEXIBLE_WHITESPACE' ]
Header:
Number:
$types: { InvoiceNumber: '[A-Z]{2,5}-${Integer}' }
$fields: { number: InvoiceNumber }
$patterns: 'Invoice (No\.?|#):\s*#{number}'
Section:
Date:
$fields: { date: Date }
$patterns: 'Invoice Date:\s*#{date}'
BillTo:
$types: { ClientName: '.*?' }
$fields: { billTo: ClientName }
$patterns: 'Bill To:\s*#{billTo}$'
Subtotal:
$fields: { subtotal: Money }
$patterns: 'Subtotal:\s*#{subtotal}'
Tax:
$fields: { tax: Money }
$patterns: 'Tax:\s*#{tax}'
Amount:
$fields: { total: Money }
$patterns: 'Total( Amount)?:\s*#{total}'
Terms:
$fields: { terms: PaymentTerms }
$patterns: 'Payment Terms:\s*#{terms}'
Invoice: '${Header}( ${Section})+'
Imported types
types/number.yml
Number:
Integer: '\d+(,\d{3})*'
Decimal: '${Integer}\.${Integer}'types/currency.yml
Currency:
USD: '(USD|US\$|\$)'
EUR: '(EUR|€)'
GBP: '(GBP|£)'types/money.yml
$imports: [ 'number', 'currency' ]
Money:
$fields:
currency: Currency
amount: Number
$patterns:
- '#{currency}\s*#{amount}'
- '#{amount}\s*#{currency}'types/date.yml
$imports: 'number'
Date:
ISO:
$fields:
year: { $type: Integer, $patterns: '\d{4}' }
month: { $type: Integer, $patterns: '\d{2}' }
day: { $type: Integer, $patterns: '\d{2}' }
$patterns: '#{year}-#{month}-#{day}'types/payment-terms.yml
$imports: 'number'
PaymentTerms:
NetDays:
$fields:
days: Integer
$patterns: 'Net #{days}'
DueOnReceipt: 'Due on Receipt'Sample input
Invoice No: INV-1042
Invoice Date: 2026-05-15
Bill To: Acme Corp
Subtotal: $1,200.00
Tax: $96.00
Total Amount: USD 1,296.00
Payment Terms: Net 30
Invoice #: INV-1223
Invoice Date: 2026-06-13
Bill To: Meta Corp
Subtotal: $1,200,000.00
Tax: $96,000.00
Total: USD 1,296,000.00
Payment Terms: Due on Receipt
Result
[
{
"type": "Invoice",
"text": "Invoice No: INV-1042\nInvoice Date: 2026-05-15\nBill To: Acme Corp\nSubtotal: $1,200.00\nTax: $96.00\nTotal Amount: USD 1,296.00\nPayment Terms: Net 30",
"start": 0,
"end": 145,
"fields": [
{
"type": "InvoiceNumber",
"field": "number",
"text": "INV-1042",
"start": 12,
"end": 20
},
{
"type": "Date::ISO",
"field": "date",
"text": "2026-05-15",
"start": 35,
"end": 45,
"fields": [
{
"type": "Number::Integer",
"field": "year",
"text": "2026",
"start": 35,
"end": 39
},
{
"type": "Number::Integer",
"field": "month",
"text": "05",
"start": 40,
"end": 42
},
{
"type": "Number::Integer",
"field": "day",
"text": "15",
"start": 43,
"end": 45
}
]
},
{
"type": "ClientName",
"field": "billTo",
"text": "Acme Corp",
"start": 55,
"end": 64
},
{
"type": "Money",
"field": "subtotal",
"text": "$1,200.00",
"start": 75,
"end": 84,
"fields": [
{
"type": "Currency::USD",
"field": "currency",
"text": "$",
"start": 75,
"end": 76
},
{
"type": "Number::Decimal",
"field": "amount",
"text": "1,200.00",
"start": 76,
"end": 84
}
]
},
{
"type": "Money",
"field": "tax",
"text": "$96.00",
"start": 90,
"end": 96,
"fields": [
{
"type": "Currency::USD",
"field": "currency",
"text": "$",
"start": 90,
"end": 91
},
{
"type": "Number::Decimal",
"field": "amount",
"text": "96.00",
"start": 91,
"end": 96
}
]
},
{
"type": "Money",
"field": "total",
"text": "USD 1,296.00",
"start": 111,
"end": 123,
"fields": [
{
"type": "Currency::USD",
"field": "currency",
"text": "USD",
"start": 111,
"end": 114
},
{
"type": "Number::Decimal",
"field": "amount",
"text": "1,296.00",
"start": 115,
"end": 123
}
]
},
{
"type": "PaymentTerms::NetDays",
"field": "terms",
"text": "Net 30",
"start": 139,
"end": 145,
"fields": [
{
"type": "Number::Integer",
"field": "days",
"text": "30",
"start": 143,
"end": 145
}
]
}
]
},
{
"type": "Invoice",
"text": "Invoice #: INV-1223\nInvoice Date: 2026-06-13\nBill To: Meta Corp\nSubtotal: $1,200,000.00\nTax: $96,000.00\nTotal: USD 1,296,000.00\nPayment Terms: Due on Receipt",
"start": 147,
"end": 304,
"fields": [
{
"type": "InvoiceNumber",
"field": "number",
"text": "INV-1223",
"start": 158,
"end": 166
},
{
"type": "Date::ISO",
"field": "date",
"text": "2026-06-13",
"start": 181,
"end": 191,
"fields": [
{
"type": "Number::Integer",
"field": "year",
"text": "2026",
"start": 181,
"end": 185
},
{
"type": "Number::Integer",
"field": "month",
"text": "06",
"start": 186,
"end": 188
},
{
"type": "Number::Integer",
"field": "day",
"text": "13",
"start": 189,
"end": 191
}
]
},
{
"type": "ClientName",
"field": "billTo",
"text": "Meta Corp",
"start": 201,
"end": 210
},
{
"type": "Money",
"field": "subtotal",
"text": "$1,200,000.00",
"start": 221,
"end": 234,
"fields": [
{
"type": "Currency::USD",
"field": "currency",
"text": "$",
"start": 221,
"end": 222
},
{
"type": "Number::Decimal",
"field": "amount",
"text": "1,200,000.00",
"start": 222,
"end": 234
}
]
},
{
"type": "Money",
"field": "tax",
"text": "$96,000.00",
"start": 240,
"end": 250,
"fields": [
{
"type": "Currency::USD",
"field": "currency",
"text": "$",
"start": 240,
"end": 241
},
{
"type": "Number::Decimal",
"field": "amount",
"text": "96,000.00",
"start": 241,
"end": 250
}
]
},
{
"type": "Money",
"field": "total",
"text": "USD 1,296,000.00",
"start": 258,
"end": 274,
"fields": [
{
"type": "Currency::USD",
"field": "currency",
"text": "USD",
"start": 258,
"end": 261
},
{
"type": "Number::Decimal",
"field": "amount",
"text": "1,296,000.00",
"start": 262,
"end": 274
}
]
},
{
"type": "PaymentTerms::DueOnReceipt",
"field": "terms",
"text": "Due on Receipt",
"start": 290,
"end": 304
}
]
}
]Notes
# Invoice Parsing
Extract invoice fields from a multi-line document without one giant regex.
## Notice
- `spec.yml` imports reusable money, date, and payment-term specs.
- `Header` recognizes the invoice number.
- `Section` contains the document fields: date, client, subtotal, tax, total, and terms.
- `Invoice` composes one header with repeated sections.
```yaml
$imports:
- types/money
- types/date
- types/payment-terms
```
Try changing `Net 30` to `Due on Receipt`, or `Total Amount` to `Total`.