DEV Community

Gu
Gu

Posted on

IIF, the QuickBooks import format with no way to report an error

I maintain a browser-based converter that turns bank CSV exports into the file formats QuickBooks accepts. One of those formats is IIF, which is what QuickBooks Desktop uses for bulk imports, and it has a property I hadn't thought carefully about until last week: it has no way to report an error.

An IIF file is tab-separated text. Here is one my converter produced for a credit card statement, which is the file the rest of this post is about:

!TRNS   TRNSID  TRNSTYPE    DATE    ACCNT   NAME    AMOUNT  DOCNUM  MEMO
!SPL    SPLID   TRNSTYPE    DATE    ACCNT   NAME    AMOUNT  DOCNUM  MEMO
!ENDTRNS
TRNS        CREDIT CARD CHARGE  01/05/2024  Visa Card       -12.40      COFFEE SHOP 4471
SPL     CREDIT CARD CHARGE  01/05/2024  Uncategorized Expense       12.40       COFFEE SHOP 4471
ENDTRNS
Enter fullscreen mode Exit fullscreen mode

Rows beginning ! declare columns; the rest are data. Each transaction names the account it belongs to in ACCNT — by name, as a string, matched against whatever is in the user's chart of accounts. There is no account number and no identifier, and no column anywhere says what kind of account it is. Just a name.

Every write-up I could find mentions what happens when that name doesn't match: QuickBooks creates a new account rather than complaining. I had that sentence on my own site, sourced from several places that all said the same thing, and I'd never seen it happen. So I finally set up a scratch company file and watched.

The test

Two imports through File › Utilities › Import › IIF Files, both from a credit card statement, both containing the same transactions with CREDIT CARD CHARGE and CREDIT CARD CREDIT transaction types. The only difference was the account name.

The first import named an account that did not exist. QuickBooks reported success. It did not prompt, it did not warn, and it created the account. All of that matched what I expected.

What I hadn't expected was the account it created. It was a Bank account — for a credit card statement. And that changed the transactions as well, because a Bank account cannot hold a credit card charge. The register showed CHK and DEP where the file had said CREDIT CARD CHARGE and CREDIT CARD CREDIT.

The second import pointed the same file at a Credit Card account that already existed, and everything landed correctly:

In the file Real Credit Card account Auto-created account
CREDIT CARD CHARGE CC CHK
CREDIT CARD CREDIT CC CRED DEP

Signs behaved correctly in the second case too — a charge increased the balance owed, a payment reduced it — which settled a separate question I'd had, since published sources disagree about whether the right transaction types are CREDIT CARD CHARGE/CREDIT CARD CREDIT or CREDIT CARD/CCARD REFUND. The first pair is correct.

Why it behaves this way

A typo in an account name doesn't produce a duplicate account you clean up later. It produces an account of the wrong kind, holding transactions of the wrong type, and the import that did it reported success. In bookkeeping terms a liability has quietly become an asset. Nothing in the process ever says so; you find out at reconciliation.

That outcome isn't a bug so much as the only thing the format leaves available. IIF was designed for bulk loading, so the importer is built to complete rather than to validate, and it runs without a user sitting in front of it. When it meets a name it doesn't know it has two options: refuse the whole file, or guess. It guesses, and it guesses Bank.

What makes it worth a second look is that the guess ignores evidence the file does supply. Look at the row above: TRNSTYPE and ACCNT are on the same line, so the line that names the unknown account also says CREDIT CARD CHARGE. The importer creates a Bank account anyway, and then rewrites the transaction into something a Bank account can hold.

Every part of that is locally reasonable. The result is still a silent corruption of someone's books.

It's a useful contrast with the other format I generate. A QBO file lands in the bank feed's review queue, where each transaction is matched and approved before anything is recorded, and the account is identified by a number the bank issued rather than a name a human typed. The failure mode there is "nothing happens and you don't know why," which is annoying but recoverable. The IIF failure mode is "everything happens and looks fine."

What I changed

The copy on my converter used to say what everyone else says: make sure the account name matches your chart of accounts exactly, because otherwise QuickBooks will create a new account. That's true, and it's incomplete in a way that matters, because it implies the worst case is an extra account. The requirement is actually stronger — the account has to already exist, with the correct type, because nothing in the file says what kind of account it is, the importer won't infer it from the transaction type, and it will not ask.

I also can't tell you whether changing the invented account's type from Bank to Credit Card afterwards produces correct results, because I didn't test it and I'm not going to assume. Restoring the backup is the fix I can vouch for, which is a good argument for taking one, since an IIF import writes straight into the company file with no undo.

One more gap worth stating: I ran this on QuickBooks Desktop for Mac. The IIF importer is the same feature on Windows and I have no particular reason to expect it to differ, but I haven't checked, so I'd be glad to hear from anyone who has.

The full write-up, with both imports side by side, is at qbofile.com if it's useful to you.

Top comments (1)

Collapse
 
amitfeldman profile image
Amit Feldman

The IIF error-reporting gap is a great catch — "the format has no way to say what went wrong" is exactly the kind of design constraint that only shows up once real users' files hit it.

Quick public check of qbofile.com while reading (headers + public HTML only): the setup is genuinely tight — CSP with frame-ancestors 'none' and object-src 'none', nosniff, no-referrer, a real Permissions-Policy, valid TLS 1.3, clean redirects on both http and www, ~430ms load. One gap: HSTS is missing. Since you're behind Cloudflare it's close to a one-toggle fix (SSL/TLS → Edge Certificates → Enable HSTS, max-age 12 months, includeSubDomains). For a tool people upload bank CSVs to it matters more than usual — without HSTS, a first-time visitor on a hostile network can be steered to plaintext before the 301 ever fires.

Happy to re-run the check free once it lands — the before/after deltas are satisfying.