Skip to content

Repository files navigation

invoiceit

Generate beautiful PDF invoices from a simple TOML config - straight from the CLI.

Published as @depoledna/invoiceit.

npx @depoledna/invoiceit init                    # create a starter invoice.toml (+ schema)
npx @depoledna/invoiceit generate invoice.toml   # -> 2026-001.pdf

Or install it globally, after which the command is just invoice:

npm i -g @depoledna/invoiceit
invoice generate invoice.toml -o acme.pdf

Config

An invoice is one TOML file. Minimal example:

[invoice]
number   = "2026-001"
currency = "EUR"
locale   = "en-US"        # controls number/date formatting (e.g. "de-DE")

[issuer]
name    = "Your Company Ltd."
address = ["221B Baker Street", "London", "UK"]
payment = "IBAN: GB00 ... · BIC: ..."
# logo  = "logo.png"       # optional PNG/JPG, relative to this file

[client]
name    = "Acme Corporation"
address = ["100 Industrial Way", "Springfield", "USA"]

[[items]]
description = "Consulting"
quantity    = 10
unit        = "h"
unit_price  = 120.00
tax_rate    = 0.19        # 19% VAT

[theme]
accent = "#4F46E5"        # header/table color

Defaults: date = today, due_days = 14, currency = EUR, locale = en-US, quantity = 1, tax_rate = 0. See examples/invoice.toml for a full one.

Currencies & languages

  • Currency - set currency to any ISO 4217 code (USD, GBP, JPY, CHF, CZK, …). The symbol, decimal places, and grouping come from the currency + locale (e.g. JPY shows no decimals automatically). Use currency_display = "code" to show CZK instead of the symbol ("symbol" is the default; "narrowSymbol" is also available).
  • Number/date formatting - set locale (BCP-47), e.g. de-DE gives 1.800,00 € and 27. Juli 2026.
  • Label language - set language for the invoice's static text. Built in: en, de, fr, es, it, nl, cs (Czech; cz also accepted). Defaults to the locale's language.
  • Any other language / wording - override individual strings under [labels] (e.g. tax_name = "GST"). Keys: title, invoice_no, issue_date, due_date, from, bill_to, description, qty, unit_price, tax, amount, subtotal, total, notes, payment_details, tax_name, page.

Date variables

Write {variable} in any text field and it resolves when the invoice is generated. Everything derives from the issue date, so a config you reuse every month stays correct without editing dates by hand.

[custom]
"Service period" = "{month_start-1m:iso} - {month_end-1m:iso}"
#                → 2026-07-01 - 2026-07-31

[invoice]
number = "{date:yyyy}-{date:MM}-001"   # → 2026-08-001, and names the PDF

[[items]]
description = "Retainer, {month_start-1m:MMMM yyyy}"   # → Retainer, July 2026

Variables — with date = "2026-08-05":

Variable Value
{date} issue date (invoice.date, or today) - Aug 5, 2026
{due_date} issue date + due_days - Aug 19, 2026
{today} the real today, even when date is pinned
{month_start} Aug 1, 2026
{month_end} Aug 31, 2026

Offsets+N / -N with d (days), m (months), y (years): {date+7d}, {date-1m}, {due_date+30d}, {month_start-1m}.

Month and year offsets never overflow: {month_end-1m} from Mar 31 is Feb 28, and {month_end+1m} from Feb 28 is Mar 31.

Where they work: invoice.number, invoice.notes, issuer.payment, item description, and the values in [custom], [issuer.custom], [client.custom]. Everywhere else a brace is just a brace. Write {{ for a literal {. An unknown variable is a config error with a "did you mean" hint, not a silent blank.

Date formats

date_format sets how the issue and due dates print, and is the default for date variables. A variable can override it after a : - {date:iso}.

[invoice]
date_format = "dd.MM.yyyy"
Preset en-US Preset en-US
long August 5, 2026 month August
medium (default) Aug 5, 2026 month_short Aug
short 8/5/26 year 2026
iso 2026-08-05

Presets follow locale, so long reads correctly in every language (5. August 2026, 5 août 2026).

Anything that isn't a preset name is a pattern:

Token Output Token Output
yyyy 2026 MMM Aug
yy 26 MM 08
MMMM August M 8
dd 05 d 5

"dd.MM.yyyy"05.08.2026, "d MMMM yyyy"5 August 2026. Month names still follow locale. Non-letters pass through as separators; to keep a literal word, quote it: "d 'of' MMMM".

Custom fields

Three places for free-form label/value lines:

[custom]            # header meta (PO number, project, ...)
"PO Number" = "PO-2026-0042"

[issuer.custom]     # extra lines under the contractor (left) block
"Reg. No." = "HRB 123456 B"

[client.custom]     # extra lines under the contracted (right) block
"Attn." = "Jane Doe"

Item column alignment

[columns]           # left | center | right (defaults: text left, numbers right)
description = "left"
qty         = "center"
amount      = "right"

How it works

  • Config - src/config/ loads the TOML and validates it with Zod, so a typo becomes a readable error, not a broken PDF.
  • Engine - src/engine/ does all money math in integer cents and groups tax by rate, so the printed breakdown always sums to the printed total. It also resolves date variables, right after the config loads - which is why invoice.number can contain one and still name the PDF.
  • PDF - src/pdf/ renders a React-PDF template with the bundled Inter font, so invoices look identical on every machine (no system fonts required).

Editor support & validation

invoice init also writes invoice.schema.json and adds a #:schema hint at the top of the TOML, so editors that support it (e.g. the "Even Better TOML" VS Code extension) give you autocomplete and inline validation. Regenerate it anytime with invoice schema.

Configs are validated strictly: unknown keys are rejected with a "did you mean" suggestion, so [isuer] or unit_prise surfaces immediately instead of being silently ignored.

Develop

npm install
npm run dev generate examples/invoice.toml   # run from source (tsx)
npm run build                                 # bundle to dist/ (tsup)
npm run typecheck

Stack

TypeScript · commander (CLI) · Zod (validation) · smol-toml · @react-pdf/renderer.

About

Generate beautiful PDF invoices from a simple TOML config, straight from the CLI.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages