Why your bank statement CSV will not import cleanly
“My bank's CSV export will not import into my accounting tool without mapping every column by hand.”
You export the month from your bank, hand the file to your accounting tool, and the importer stops to ask you six questions. Does the file have a header row. What separates the fields. Which column holds the date, and in what order are its parts. Which column holds the amount, and is a payment a negative number or is there a second column for it.
You answer, the import runs, and either it works or you find out next month that a hundred transactions came in twice.
None of that is your accounting tool being awkward. It is being honest about something the file genuinely does not tell it.
A CSV does not carry its own meaning
The format is older than most of the software that reads it, and it was written down long after everyone had already agreed to disagree. RFC 4180, the document that finally described it in 2005, and then only as an Informational note rather than a standard, opens by admitting the problem:
Surprisingly, while this format is very common, it has never been formally documented.
and then, about the state of the world it was documenting:
Due to lack of a single specification, there are considerable differences among implementations.
That is the whole story. A CSV file is rows of text with something between the fields. It has no schema, no type information, no declared encoding, and no agreed way to say what any column is for. Even the header row is optional, and the RFC is explicit that nothing in the file resolves the question:
Implementations deciding not to use the optional "header" parameter must make their own decision as to whether the header is absent or present.
A spreadsheet hides this from you, because it guesses, and its guesses are usually right enough to look correct. An importer that is going to write to your ledger cannot afford to guess, so it asks. The six questions are the format's missing schema, and you are being asked to supply it.
The three facts you are supplying
Firefly III's data importer, which is as good a worked example as any, tells you to establish three things about the file before you upload it:
- Whether it has headers
- What the delimiter is
- What each column contains
and then says why:
This is important, because in a later step during the import you must tell the importer the role of each column in your CSV file.
The first two are quick. The third is where bank exports get interesting, because banks do not agree on how to represent a transaction:
One amount column, or two. Some exports carry a single signed amount. Others carry separate debit and credit columns, where the sign is implied by which column the number sits in and both are positive. A tool configured for the first shape reads the second as a month with no spending in it.
A running balance that is not a transaction. Many statement exports include the account balance after each line. It is a useful column and it is not an amount. Map it as one and every figure you produce afterwards is wrong.
Dates that are ambiguous rather than wrong. 03/04/2026 is two different days
depending on the bank's country, and no part of the file says which. Firefly III's
configuration screen makes you declare the pattern explicitly, down to a country prefix
for month names in another language. That is not fussiness. It is the only place the
information exists.
Decimal separators, for the same reason. 1.234,56 and 1,234.56 are the same
amount written by two conventions, and if the comma is also your delimiter the row does
not even parse into the right number of fields.
The fourth decision, which costs more later
The three above fail loudly. This one fails quietly, months afterwards.
Your importer has to decide whether a transaction it is looking at is already in your ledger. Firefly III does it by hashing the transaction it is about to submit, and its reference on duplicate detection is direct about what goes into that hash:
If you change the mapping, or the roles of the data before it gets send to Firefly III, the hash changes
So your column mapping is not just a setup step. It is part of the identity of every transaction you import. Map the description column slightly differently in November than you did in October, and November's import will not recognise October's rows as the same transactions. They come in again, and nothing warns you, because from the importer's point of view they really are new.
The same reference notes the other half of the trap, for files that carry no transaction reference at all:
Each transaction you wish to import must have a unique identifier in a column.
Plenty of bank exports do not have one. The importer's own guidance on why duplicate detection misfires puts it plainly: it happens "when your data source has too little information to go on. For example, a CSV file with only a very few columns."
So, in order
- Open the file and look at it before you upload it. Header row, delimiter, and what each column actually is, including which one is a balance. Five minutes here removes most of the failures below.
- Write the answers down, or save the configuration. Every importer worth using lets you save the import profile and reload it next month. Firefly III's mapping guide ends by telling you to download the configuration file "so you don't have to do those again next time". Use it. The consistency matters more than the initial correctness.
- Normalise the file rather than the importer, where you can. If a date column is a mix of formats, fixing it in the file once is more durable than finding a pattern string that covers both.
- Do not send a statement to a random online converter. It is a complete record of where your money goes and who you pay. Whatever you use should run on your own machine.
If your bank will give you something other than CSV
It is worth asking, because the entire problem above is specific to CSV. Firefly III's importer takes CSV and CAMT.053 files, and after listing the three facts you have to establish about a CSV it adds one line:
This step does not apply to other file types.
That is the difference in a sentence. A structured statement format carries its own meaning, so there is nothing for you to declare and nothing to get wrong in January that you got right in December. If your bank can produce one, the setup work disappears rather than getting easier.
Two boundaries on that. We have not tested the ISO 20022 route ourselves, so what we can report is what that importer's documentation says it accepts, not how well it goes. And PDF is not the answer either: the same docs say flatly that "PDF files will never be supported", which is worth knowing before you spend an evening looking for a converter.
Where TableRune fits
TableRune is our spreadsheet for files that have outgrown one. It is relevant to exactly one part of this problem, step 3 above, and not to the rest.
What it does here:
- It opens the CSV in a browser tab on your own machine. Row data never goes to a server, which is the property that matters when the file is a bank statement.
- Clean dates parses a column of mixed date formats into real ISO dates in place, which is the fix for the ambiguous-date problem at the source rather than in the importer's configuration.
- Every step you apply is recorded, and you can save the sequence as a named workflow and reload it onto next month's file. That is the same discipline as saving your import profile, applied one stage earlier.
What it does not do: it is not a bank statement importer and it does not talk to your ledger. It cleans the file you then hand to the tool that does.
If your statements already import cleanly, you do not need any of this. If you are re-deriving the same column mapping every month from memory, the fix is to stop doing it from memory, and the tool you use to stop is much less important than the stopping.
What this post covers. It describes CSV as a format and one importer's documented behaviour, which was read from that project's own docs. It does not cover any specific bank's export, and it is not a comparison of accounting tools.