Input files
Your input files will probably be CSV files with a line for each
transaction, although other file types will work fine if you use a
preprocess or a construct script that can read them. These scripts
are explained later.
We mostly use conventions based on a predefined directory structure for your input statements.
For example, assuming you have a savings account at mybank, you’ll
put your first CSV statement here:
import/john/mybank/savings/1-in/2018/123456789_2018-06-30.csv.
An owner, bank, or account can itself be named import. For example,
import/john/mybank/import/1-in/2018/april.csv belongs to John’s account
named import at mybank. Run hledger-flow import and hledger-flow status
from the ledger root to use that layout.
Some people may want to include accounts belonging to their spouse as
part of the household finances:
import/spouse/otherbank/checking/1-in/2018/987654321_2018-06-30.csv.
Parallel import converts one statement per core at a time, and starts another
as soon as a worker finishes. Use --batch-size N to set how many run at once
or --sequential to run one at a time.
More About Input Files
Section titled “More About Input Files”All files and directories under the import directory are related to the
automatic importing and classification of transactions.
The directory directly under import is meant to indicate the owner or
custodian of the accounts below it. It mostly has an impact on
reporting. You may want to have separate reports for import/mycompany
and import/personal.
You can create an owner directory before its first statement arrives.
hledger-flow report skips that owner until import creates its
all-years.journal and continues generating reports for the other owners.
Below the directory for the owner we can indicate where an account is
held. For a bank account you may choose to name it import/john/mybank.
If your underground bunker filled with gold has CSV statements linked to
it, then you can absolutely create import/john/secret-treasure-room.
Under the directory for the financial institution, you’ll have a
directory for each account at that institution, e.g.
import/mycompany/bigbankinc/customer-deposits and
import/mycompany/bigbankinc/expense-account.
Next you’ll create a directory named 1-in. This is to distinguish it
from 2-preprocessed and 3-journal which will be auto-generated
later.
Under 1-in you’ll create a directory for the year, e.g. 2018, and
within that you can copy the statements for that year:
import/john/mybank/savings/1-in/2018/123456789_2018-06-30.csv
Keep the filenames your bank provides. For example, April [1].csv
and April 1.csv can sit in the same year directory. Import treats the
brackets literally and includes each statement’s transactions once. If an
earlier import duplicated one statement and omitted the other, run
hledger-flow import to rebuild the journals from both files.
Incremental imports
Section titled “Incremental imports”hledger-flow import --new-files-only compares each generated file’s
modification time with its source. It rebuilds a preprocessed CSV or journal
when its source is newer, even if the two timestamps differ by only a
fraction of a second. Equal timestamps count as up to date. If you replace
a statement and preserve its timestamp, run hledger-flow import to force
a rebuild.
Generated Files and Recovery
Section titled “Generated Files and Recovery”When an import or report fails, the command finishes writing its queued
diagnostics before it exits, even if a pipe consumer reads slowly. To save
the failure message for inspection, capture standard error, for example
with hledger-flow import 2>import-errors.log.
1-in is the source of truth. Both 2-preprocessed and 3-journal
contain generated output; user-supplied files in those stages are not
supported. Use manually managed journals
for transactions without an input statement.
Import writes each rules-generated journal and each changed include file to a temporary file before replacing the destination. If a write fails, the previous complete copy of that file stays in place. Correct the cause of the failure and rerun the same import. Files written successfully earlier in the run may already contain the new data.
After all selected inputs have been converted successfully, import moves
orphaned 2-preprocessed/YYYY/*.csv and 3-journal/YYYY/*.journal files
into the account’s .trash directory. A corresponding input must be in
1-in for the same account and year, with a filename that produces that
output when its extension is replaced. An old preprocessed CSV does not
keep an orphaned journal active. If two inputs would produce the same
output, import stops before converting or moving files and names the
conflicting inputs. If a symlink points to a source-backed journal, import keeps
the link and includes the journal once, preferring the generated filename.
For example, removing or renaming 1-in/2026/march.csv retires its old
outputs to:
.trash/2026/2-preprocessed/march.csv.trash/2026/3-journal/march.journalEach move is reported. Existing trash entries are preserved, with .1,
.2, and so on appended to subsequent filenames. The trash is never
imported or emptied automatically. Restore the input and run import again
to regenerate its outputs, or recover handwritten transactions from a
trashed journal into _manual_. Restoring just the generated file without
its input will cause it to be retired again.
If you keep your base directory in Git, add .trash/ to your .gitignore
so retired outputs stay out of version control.
Trash moves use an account-local .trash/.move-lock directory to prevent
concurrent imports from overwriting recovery files. If an interrupted
process leaves this lock behind, the error explains how to remove the
empty lock directory once no import is running and retry. A failed run may
have moved some outputs already; they remain recoverable in .trash.
Running hledger-flow import from anywhere below an account selects the
whole account for statement conversion and orphan cleanup. For example,
running it from import/john/mybank/savings/1-in/2018/archive/ selects
savings across its statement years and leaves sibling accounts’ statements
and generated journals alone. The same scope applies when you pass that
directory to import. Use --start-year 2024 to limit conversion and cleanup
to 2024 and later years, including years whose last input was removed. Supply
exactly four ASCII digits or use --start-year current for the current year.
An invalid value such as 24 stops the command before import begins.
Include files are rebuilt globally from source-backed journals and supported manual files, so orphaned journals outside the cleanup scope are also excluded from the ledger. Obsolete generated year and account indexes are rewritten as empty ledgers. Manual-only and empty imports succeed without dummy input files.
Only the documented stage/year layout is processed. Unrelated files such as PDFs are left untouched; hidden files and directories are ignored. Deleting an input can therefore remove its transactions from the ledger, but does not permanently delete the generated files.
Stability
Section titled “Stability”The basic owner/bank/account/year structure has been used and tested fairly extensively, I don’t expect a need for it to change.
I’m open to suggestions for improvement though.