If you've ever tried to get transactions out of two different banks' PDF statements into the same spreadsheet, you've discovered something mildly infuriating: there is no standard. None. Every bank invented its own layout, and they all differ in ways that are trivial for a human to read past and awkward for software.
A US bank writes 03/04/2026 meaning March 4th. A UK bank writes the same characters meaning 4th March. Others use 2026-03-04, or "Mar 04, 2026", or "4 March 2026". The characters 03/04/2026 are ambiguous in isolation. And a converter that guesses wrong silently shifts your transactions by months.
The only reliable escape is context: if any date in the same statement has a day greater than 12 (like 25/03/2026), the convention is settled for the entire document. Statements where every date is ambiguous do exist, and honest software should tell you it's guessing rather than quietly picking one.
Money leaving your account might appear as -142.50, (142.50), 142.50-, 142.50 DR, or as a plain 142.50 that happens to sit in a column headed "Withdrawals." All five mean the same thing.
Some statements use two columns. Debits and credits, where each row fills exactly one. Others use a single signed column. Some include a running balance; some don't. Some add a reference-number column between the date and the description, which is easy to mistake for an amount if you're not careful.
Most of the English-speaking world writes 1,234.56. Much of continental Europe writes 1.234,56, the separators are swapped. Reading one as the other turns a €1,234.56 transaction into €1.23.
A long transaction description often continues onto a second line with no date on it. Software has to recognize that this line belongs to the row above rather than treating it as a new transaction or, worse, discarding it. And it has to distinguish those continuation lines from footer rows like "Total Withdrawals" that also lack a date.
The obvious fix is a template per bank: know that Bank X puts the date at this position, the balance at that one. This works beautifully. Until the bank redesigns its statement, which they do without warning and without telling anyone maintaining a converter. Then that template silently produces wrong output, or breaks entirely, and someone has to notice and fix it.
Multiply that by every bank you want to support, in every country, across every account type (a business account statement rarely matches a personal one from the same bank), and maintenance becomes the entire product.
The alternative is to derive the layout from the document itself: find lines that start with something date-shaped, cluster the numbers by their horizontal position into columns, then work out what each column is from its behavior. A column whose changes always match another column's values is a running balance, two columns that never both appear on the same row are a debit/credit pair.
The real advantage of this approach is that it degrades gracefully. When a bank changes its layout, inferred structure usually still works, where a hardcoded template would break outright.
It also gives you something templates can't: a way to check the answer. If a statement has a running balance, then each transaction amount should equal the difference between consecutive balances. That's arithmetic the document itself provides, and it catches misread rows automatically. Which is a much stronger guarantee than "we support this bank."
Our converter infers structure rather than using per-bank templates. And reconciles against the running balance where one exists.
Try Statement → CSV