Parsing bank statement PDFs in the browser, with a CSP that proves nothing gets uploaded
People who freelance often can't get a CSV out of their bank. Some banks only give PDF statements, others limit downloads to 90 days, and a few dropped CSV and QFX exports entirely. But every bookkeeping spreadsheet wants rows of Date, Description, Amount . The usual fix is an online converter, which means uploading a year of bank statements to a stranger's server. I didn't want to ask anyone to…
Freelancers often struggle to obtain CSV files from their banks. Some institutions only provide PDF statements, impose 90-day download limits, and have abandoned CSV and QFX exports altogether. Nonetheless, every bookkeeping spreadsheet requires rows containing Date, Description, and Amount. The typical solution involves an online converter, which necessitates uploading years' worth of bank statements to a third-party server.
This converter, built for Paid & Filed, operates entirely within a web browser tab: paidandfiled.com/bank-statement-converter. This article delves into the three most challenging aspects of the project.
1. Converting PDF.js text runs into lines. The page.getTextContent() function does not yield lines; instead, it provides text runs, each with a transform matrix and in the order the PDF generator wrote them. A single statement row like "03-14 ACH DEPOSIT CLIENT CO 1,250.00 4,310.27" may be presented as six runs, out of sequence, with the minus sign of a debit split into its own run.
Consequently, the initial step involves rebuilding lines geometrically: sorting by baseline (transform[5], top to bottom), then x position, grouping runs whose baselines are within 2 points, and joining runs that physically touch without a space, ensuring words split by pdf.js remain intact. The function itemsToLines() implements this logic, returning an array of strings.
2. Developing a single reader capable of handling various bank formats. The initial approach was to create a template for each bank. However, this design proved impractical as the same bank's layout could vary between checking and savings accounts, between years, and between web and mailed PDFs. The generic rule proved to be straightforward.
A transaction line begins with a date and concludes with one or two amounts; the latter is the running balance. Everything in between constitutes the description. Dates can appear as "03/14," "03-14," "Mar 14," or "14 Mar 2026," and when a row lacks a year, it originates from the statement period indicated in the header, accounting for December statements that extend into January.
The most challenging aspect pertains to determining the sign. Bank PDFs express direction in at least five ways: separate debit and credit columns (which merge into one column once the page is flattened into text), a trailing minus (45.10-), CR and DR codes, section headings like "Deposits and other credits," or the absence of any indication. Therefore, direction is prioritized in the following order: let direction be "in" or "out."
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.