

A bookkeeper receives six bank statements for a quarterly review. Three belong to the business's operating account and three to its reserve account. One bank labels outgoing payments “Withdrawals.” The other uses “Debits.” Dates, descriptions, and balances appear in different places on the page.
The bookkeeper needs to read those transactions together: find a payment, follow an account across months, and check that the quarter is complete. Copying each PDF into a separate spreadsheet leaves the work of aligning columns and tracking sources for later.
PdfParse combines the records by extracting each statement into the same table structure. Statement details go into a shared statements table. Repeating entries go into a shared transactions table, with each entry linked to the statement that produced it. Those relationships let the transactions form a single ledger while preserving their original context.
This article follows that workflow in plain language, then explains the engineering behind the combined view. Here, “ledger” means a consolidated bank transaction history that can support bookkeeping and reporting. Assigning accounting categories and posting entries to an accounting system are subsequent steps.
Start with a complete set of statements
Our worked example uses Cedar Studio, a fictional business with two USD deposit accounts: an operating account at Harbor Bank ending in 1842 and a reserve account at Meadow Bank ending in 7710. Each account has an April, May, and June 2026 statement.
The figures below are synthetic examples of the intended result, rather than output from a recorded extraction run.
| Account | Month | Opening balance | Total credits | Total debits | Closing balance |
|---|---|---|---|---|---|
| Harbor · 1842 | April | 10,000.00 | 6,000.00 | 3,800.00 | 12,200.00 |
| Harbor · 1842 | May | 12,200.00 | 4,500.00 | 3,300.00 | 13,400.00 |
| Harbor · 1842 | June | 13,400.00 | 5,200.00 | 3,500.00 | 15,100.00 |
| Meadow · 7710 | April | 2,000.00 | 300.00 | 100.00 | 2,200.00 |
| Meadow · 7710 | May | 2,200.00 | 300.00 | 100.00 | 2,400.00 |
| Meadow · 7710 | June | 2,400.00 | 300.00 | 150.00 | 2,550.00 |
Before uploading, list the account and period covered by each PDF. Check that every statement includes all its pages and that the folder contains one copy of each statement. A combined ledger can look plausible even when a month is missing.
In this example, there should be six distinct account-period combinations. A second copy of Harbor's May statement needs investigation; it cannot stand in for a missing June statement.
Give different bank layouts the same destination
The key decision is to use a common extraction schema: the list of fields and relationships that every statement should produce.
For Cedar Studio, the statement fields are bank name, masked account identifier, currency, period start, period end, opening balance, and closing balance. Transaction fields are posting date, full description, reference, debit, credit, running balance, and sequence within the statement.
Keep masked account identifiers as text. Keep dates consistent. Use positive numbers in the debit and credit columns, with only the applicable column populated for each ordinary ledger entry. Missing or unreadable amounts need review rather than an assumed zero.
These definitions give equivalent bank labels the same meaning. “Withdrawals” and “Debits” both populate debit; “Deposits” and “Credits” populate credit. A page heading or a balance carried forward remains outside the transaction rows.
In a PdfParse project, open New table, add a representative statement to Smart Extractor, and use an instruction such as:
Create a statements table with bank_name, account_number_masked, currency, period_start, period_end, opening_balance, and closing_balance. Create a transactions child table with posting_date, description, reference, debit, credit, running_balance, and sequence. Use positive debit and credit amounts. Keep wrapped descriptions together. Sequence the transactions in printed order, starting at 1 for each statement. Exclude repeated headers, summary totals, and balance-forward lines from the transactions.
Select Generate schema, then review the fields and the parent-child relationship before selecting Create Table. Amounts should have numeric types, dates should have date types, and repeating transactions should have their own child table. Correct an unsuitable generated schema before processing files.
The same definitions can be used across both layouts, but they still need to be tested against each bank. For a detailed first-statement walkthrough, see how to extract transactions from a PDF bank statement.
Process each statement into the shared tables
Begin with April's statement from each bank. Open statements, select Manage files, upload the two PDFs, select them, and choose Process Selected.
When processing finishes, inspect both tables. For the example, expect two statement rows and six transaction rows: four from Harbor and two from Meadow. Compare their dates, descriptions, amounts, and statement relationships against both PDFs.
This small first batch answers a useful question: does the shared schema handle both layouts correctly? If Meadow's withdrawals appear as credits, revise the field instructions and reprocess the affected sample before continuing.
Once both samples look correct, process the four remaining statements into the same tables. Leave the two reviewed April files out of that selection. The complete worked example has six statement rows and 18 transaction rows.
Check every file's status. A failed document leaves a gap in the quarter even when other statements have produced rows. Investigate the affected file and its processing result before accepting the ledger as complete.
Keep the account and source attached to every transaction
With all six statements in one project, opening transactions gives you the combined collection of ledger entries. Each row retains its relationship to one statement and its source document.
The statement relationship supplies the account, currency, and period when you need them. The source-document control lets you compare a questionable amount or wrapped description with the original PDF.
For example, a flattened view of Cedar Studio's April records would include these entries:
| Posting date | Bank · account | Description | Debit | Credit |
|---|---|---|---|---|
| 2026-04-03 | Harbor · 1842 | Customer payment | 6,000.00 | |
| 2026-04-05 | Harbor · 1842 | Office rent | 2,500.00 | |
| 2026-04-10 | Harbor · 1842 | Supplies | 1,000.00 | |
| 2026-04-15 | Harbor · 1842 | Transfer to reserve | 300.00 | |
| 2026-04-15 | Meadow · 7710 | Transfer from operating | 300.00 | |
| 2026-04-20 | Meadow · 7710 | Hosting | 100.00 |
The two transfers have the same date and amount, but represent entries in different accounts. Retaining that account context is essential when reviewing the combined history.
The engineering section below shows how to put statement fields beside transaction fields in one query result. In the dashboard, you can review the related records directly without writing that query.
Review the quarter before relying on its totals
Combining statements introduces checks that a single-document extraction cannot answer on its own.
First, compare the six statement rows with the original account-period list. Confirm the bank, account, currency, and dates against each PDF. In this small example, the bank and masked identifier distinguish the accounts. In a larger dataset, match records to a verified account list; the same last four digits can occur on different accounts.
Next, check each statement's balances. For these deposit accounts, opening balance plus credits minus debits should equal closing balance. Harbor's April figures give 10,000.00 + 6,000.00 − 3,800.00 = 12,200.00.
Then check continuity within each account. Harbor's April closing balance of 12,200.00 matches its May opening balance. May's closing balance of 13,400.00 matches June's opening balance. Compare the periods as well: matching balances alone cannot establish that there is no missing interval.
Investigate overlapping periods and repeated files separately. Two transactions with the same date, description, and amount can both be legitimate. Use the account, statement period, reference, source document, and surrounding entries to decide whether a row is duplicated. Keep suspected duplicates available for review until that decision is made.
Finally, compare the rows with the source pages, including page boundaries and the last entries. A correct closing balance does not prove that every transaction was captured: omitted entries can cancel each other out. These are review steps you perform on the extracted dataset; the shared schema alone does not establish completeness.
Treat transfers carefully when combining accounts
Cedar Studio moves 300.00 from its operating account to its reserve account each month. The quarter therefore contains 900.00 of outgoing transfers in Harbor and 900.00 of incoming transfers in Meadow.
Both sides belong in the bank ledger because each explains movement in its own account. For a report of external receipts and payments, identify those pairs and exclude them from the relevant report totals while preserving the source records.
Across both accounts, the example has 16,600.00 in credits and 10,950.00 in debits. Those figures include the transfers. Calling all credits “sales” would misstate what the dataset contains.
Keep balances scoped to an account, too. A running balance printed on a Harbor statement cannot be continued using the next transaction from Meadow merely because it appears next in date order. If your real statements use several currencies, retain currency on every reporting result and calculate totals separately.
Engineering: how the records become one ledger
This optional section uses the PdfParse TypeScript SDK to read the reviewed statements and transactions tables. It assumes a server-side TypeScript application running Node.js 22 or later, the SDK available in that application, and a project API key with rows:read permission. The import below uses the current workspace package, @ocr-monorepo/client-sdk, which is not yet published on npm.
The shared schema gives every document a consistent output shape. PdfParse processes each document with that schema, then writes the statement record and its repeating transactions into the destination tables. Document processing status remains separate from the rows, so an integration needs to check both completion and the resulting data.
The insertion code writes the parent record and uses its identifier to connect the child records. For a parent named statements, the generated child relationship column is internal_fk_statements_id. The records also carry document_record_id, which connects them to their source document. These identifiers are supplied by the application rather than inferred from a printed bank reference.


Account identity and statement identity serve different purposes. Harbor's three monthly statements share an account, but each has its own statement record. This design keeps the extracted account details on each statement; it does not assume that matching names or masked identifiers automatically resolve to one global account record.
Use client.sql.query() to join the records in the project's database and return the combined ledger to your application. Set PDFPARSE_API_KEY in your server environment; keep it out of browser code and source control. The key identifies the project, so the SDK call needs no project ID.
ledger.rows contains one row for each transaction with a matching statement, adding the bank, account, currency, and period. The SDK sends a read-only query and returns the rows without creating another stored copy of the ledger. The SQL endpoint returns at most 100 rows per request; the SDK exposes truncated so your application can detect an incomplete result. Larger ledgers need explicit pagination or bounded queries whose results are each checked for completeness.
For the worked example, the final log should be { transactionCount: 18, statementCount: 6 }, with ledger.truncated equal to false. These are the expected counts for the synthetic dataset. See the API reference for the query request and response contract.
Compare ledger.rowCount with the transaction table count; an inner join leaves out a transaction whose parent is missing. Confirm that each transaction points to the expected statement and source document. If your table or relationship column has a different name, inspect the schema and adjust the query before running it.
The date ordering gives you a convenient combined history. Statement ID and sequence keep same-date entries in a stable order within each statement; they do not establish an exact time order between banks. Preserve printed sequence when checking an individual statement's running balances.
For the broader modeling rationale, read JSON arrays versus child tables. Keeping transactions as related rows makes these cross-statement queries possible without unpacking a separate array for every PDF.
Use the reviewed ledger for the next month's work
The completed example brings six statements and 18 transactions into one reviewable dataset. Each account has three consecutive periods, and every transaction retains its statement relationship and source document.
Before handing it off, confirm those counts, review the six statement balances, and verify the three transfer pairs. The June closing balances should be 15,100.00 for Harbor and 2,550.00 for Meadow. Together they total 17,650.00, up from the two April opening balances totaling 12,000.00. That change of 5,650.00 agrees with the quarter's credits minus debits.
For a spreadsheet handoff, use Export → Export as CSV on the reviewed tables and retain the identifiers needed to join them. SQLite Database Export in Project Settings keeps the tables together for continued querying. The result remains useful when July arrives: process the new statements into the existing structure and repeat the account, period, source, and balance checks.
Start with one statement from each bank using PdfParse's bank statement parser. Once the shared schema handles those layouts correctly, extend the same ledger across the periods you need to review.