Financial reports are the backbone of business analysis. Income statements, balance sheets, and cash flow statements are produced and archived every single month, and they must be accurate, neatly formatted, and trustworthy. Traditional financial systems generate these reports through backend components or desktop plugins, which are complicated to deploy, frequently break on Chinese fonts, and make browser-side printing hard to control. dompdf.js opens a new path: financial data is assembled into HTML tables in the frontend, and the Rust and WebAssembly core renders it to vector PDF with table borders, column alignment, and repeating table headers across pages handled automatically. This article explains the complete financial report PDF workflow, including report template design, number formatting, exporting multiple statements in one file, and the common pitfalls, so finance systems can ship frontend export capability quickly. We will also cover the compliance details that make reports acceptable to auditors, the template versioning discipline that accounting standards changes demand, and the validation checks that keep the numbers honest. If you work on finance systems, ERP products, or internal reporting tools, the patterns here translate directly to your stack, and the same template can drive both the on-screen preview and the printed output without divergence.
Financial reports are essential for internal management, bank credit applications, tax filing, and investor communication. Finance teams export income statements, balance sheets, and other reports every month, and the output must be properly formatted, numerically accurate, and clean. A single misaligned column or garbled character undermines the credibility of the whole report.
Traditional approaches struggle. Backend PDF generation means operating an extra rendering service with finicky font environments, and template updates require a release pipeline. Browser printing depends on the user's browser and printer settings, so margins and scaling vary wildly and the output is inconsistent across machines.
Frontend generation fits finance perfectly: report data comes from APIs, the frontend organizes it into tables by accounting subject, and dompdf.js renders a vector PDF with crisp numbers, precise borders, and uniform fonts. No server environment to maintain, template changes take effect instantly, and finance staff can even maintain the templates themselves.
Audit and due diligence raise the bar even higher. The header must state the full entity name, the reporting period, and the unit; the footer should carry the preparer, the reviewer, and page numbers; and every figure must agree with the books. A frontend template bakes these compliance elements in by construction, so every export is compliant by default rather than by luck.
Different stakeholders need different cuts of the same data. Management may want a one-page summary, the bank wants the full statement with notes, and tax filing wants the standard format. With one template engine and a few layout variants, the same validated data serves all of them, which keeps the system simple while covering a diverse audience.
There is also the question of report cadence. Monthly statements, quarterly board packs, and annual audited statements have different levels of detail. Rather than building three separate systems, keep one template engine and parameterize the depth: a monthly statement lists line items, an annual report adds notes and comparative columns, and the board pack emphasizes key ratios.
Consistency between the numbers on screen and the numbers in the PDF is a compliance concern in itself. Because the frontend renders both from the same data source with the same formatting functions, there is no second implementation to drift, and finance can trust that what they reviewed in the browser is what was archived.
Financial reports are highly structured. At their core they are two-dimensional tables of rows (accounting subjects) and columns (current period, prior period, cumulative), which map naturally onto HTML tables. With dompdf.js you map financial data directly to table rows, with no proprietary report-engine syntax to learn, so development is fast.
Number alignment is the lifeblood of financial statements. dompdf.js supports table styles fully: columns can be right-aligned so digits line up digit by digit, and thousands separators and parenthesized negatives are controlled in the template. The exported report matches what finance software produces natively.
Vector rendering keeps the report sharp at any zoom level, whether printed on paper or shown on a projector. PDF files are small enough for email and archival, and automatic pagination lets dozens of subject rows flow gracefully across pages with the header repeated on every page.
Maintenance is a strong argument in favor of frontend generation. Financial templates change as accounting standards and company policies evolve, and a frontend template can be adjusted and validated the same day. Keep template versions numbered so that the exact layout used for any historical report can be reconstructed if an auditor asks what the old format looked like.
Another advantage is the clean separation of data and presentation. The accounting system owns the numbers; the template owns the layout. Because the mapping layer is thin and explicit, adding a new subject or renaming an existing one is a data change rather than a code change, which is exactly how finance teams like to work.
An income statement template typically lists operating revenue, operating cost, taxes and surcharges, period expenses, operating profit, total profit, and net profit, using three columns: item, current period amount, and prior period amount. The header must show the reporting entity, the reporting period, and the currency unit, for example in yuan or ten-thousand yuan.
A balance sheet uses a two-column structure: assets on the left, liabilities and owners' equity on the right, with a final row comparing total assets against total liabilities plus owners' equity. The two sides must balance exactly. Use thin borders between cells so the report reads clearly and is easy to verify.
Use the built-in Source Han Sans SC font for the report. Bold and center the title, left-align subject rows, and right-align amount rows. State the unit, such as yuan, prominently in the header to prevent magnitude misunderstandings, which is standard practice among finance professionals.
Small formatting conventions separate a professional statement from a rough one: bold totals and subtotals with a light background, uniform row heights, header rows repeated at the top of every page, and no stray spaces inside numeric cells. Collecting these conventions in one style block keeps every statement in the company looking like it came from the same team.
Currency presentation deserves a decision up front. Decide whether amounts are shown in yuan, thousand yuan, or ten-thousand yuan, state the unit in the header, and format all numbers consistently. Changing the unit later is a data-layer change, but only if the templates never hard-code the unit anywhere, which is a common source of embarrassing magnitude errors in printed reports.
Here is the income statement generation code. Financial data is organized as an array, table rows are produced with a map call, and the whole thing is rendered to PDF. The code is clean and fully data-driven, so adding or removing subjects only means editing the array.
The balance sheet follows the same structure with finer subject grouping: render sections for current assets, non-current assets, and so on, and use merged cells for subtotals. Format amounts before rendering, write negatives in parentheses, and verify the balance-sheet equation before exporting.
The example structure extends directly to production: swap the array for real API data, map subject names, amounts, and periods from the backend, and let the frontend handle presentation only. Do all formatting in the data layer, converting to thousands-separated strings and parenthesized negatives, so the rendering layer receives final display strings and never does arithmetic.
To export the income statement, balance sheet, and cash flow statement together, build each table's HTML and call addPage for each, then save once. Put the report name in the header of the first page of each statement and add a unified page-number footer, and the package reads as one coherent document rather than three files stapled together.
import { DomPDF } from 'dompdf.js';
const incomeItems = [
{ name: 'I. Operating revenue', current: '1,285,000.00', last: '1,020,000.00' },
{ name: 'Less: Operating cost', current: '812,400.00', last: '690,300.00' },
{ name: 'Less: Taxes and surcharges', current: '38,550.00', last: '30,600.00' },
{ name: 'II. Operating profit', current: '434,050.00', last: '299,100.00' },
{ name: 'Add: Non-operating income', current: '12,000.00', last: '8,000.00' },
{ name: 'III. Total profit', current: '446,050.00', last: '307,100.00' },
{ name: 'Less: Income tax expense', current: '111,512.50', last: '76,775.00' },
{ name: 'IV. Net profit', current: '334,537.50', last: '230,325.00' },
];
const rows = incomeItems.map(item => `
<tr>
<td style='border:1px solid #333;padding:6px'>${item.name}</td>
<td style='border:1px solid #333;padding:6px;text-align:right'>${item.current}</td>
<td style='border:1px solid #333;padding:6px;text-align:right'>${item.last}</td>
</tr>
`).join('');
const reportHtml = `
<h1 style='text-align:center;font-size:16pt'>Income Statement</h1>
<p style='text-align:center;font-size:10pt'>Acme Technology Co., Ltd. Jan-Jul 2026 Unit: USD</p>
<table style='width:100%;border-collapse:collapse'>
<tr style='background:#f5f5f5'>
<th style='border:1px solid #333;padding:6px'>Item</th>
<th style='border:1px solid #333;padding:6px'>Current Period</th>
<th style='border:1px solid #333;padding:6px'>Prior Period</th>
</tr>
${rows}
</table>
`;
const pdf = new DomPDF();
pdf.addPage(reportHtml, { format: 'A4', margin: '20mm' });
pdf.save('income-statement-2026-jan-jul.pdf');
When a financial report has many subjects and cannot fit on one page, dompdf.js splits the table across pages automatically and repeats the header row at the top of each new page. Readers never lose track of what each column means, which is a very practical capability for finance.
The @page media properties give precise control over page geometry, for example @page { size: A4; margin: 18mm; }. If the company uses specific paper or needs a binding margin, adjust the margin value so the printed layout matches the screen exactly.
Validate the data before rendering: check that all subject rows are present, that debits equal credits, and that totals equal the sum of the details. Only render once validation passes, which prevents the classic export-then-redo cycle.
When a row lands exactly at a page break, with the next row pushed to the following page, readability suffers. Protect totals and subtotals so they stay with their detail rows, and verify that margins are generous enough that no text is clipped at the page edge. These details matter most in printed statements that auditors will flip through by hand.
It is also wise to preview the pagination before the final save. Generate a draft, scan the page breaks, and adjust row heights or font sizes if a break falls in an awkward spot. The cost of a two-minute preview is trivial compared with reissuing a statement that has already been distributed to the bank.
Problem one: amount columns do not align. This usually comes from mixed font widths or stray spaces in cells. Use a monospaced numeric font or force right alignment and strip extra spaces so thousands-separated digits line up digit by digit.
Problem two: table headers disappear across pages. Make sure the table uses standard table markup without a fixed height. dompdf.js repeats headers most reliably on standard tables, so avoid absolute positioning and other unusual layouts.
Problem three: Chinese characters render incorrectly. Report text relies on the bundled Source Han Sans SC. If you load custom fonts, confirm they are actually loaded; for finance reports, stick with the built-in Chinese font so rendering is consistent across all machines.
Finally, standardize the file naming convention, for example income-statement-2026-07.pdf, and include the reporting period in the title metadata so archives sort correctly. When reports contain sensitive figures, distribute them through controlled channels and consider opening passwords for files sent by email, so the numbers never travel in the clear.
Also decide who can generate which reports. In many organizations, only certain roles may export full statements. Enforcing that in the frontend is easy, but pair it with server-side checks if the data API itself could be called directly, so permissions hold regardless of how the client behaves.
Testing is not optional for financial output. Build a small fixture set covering typical months, zero-revenue months, negative equity, and currency changes, and assert that the generated HTML contains the expected formatted values. These tests run in milliseconds and catch regressions before they reach a stakeholder's inbox.
Finally, consider what happens when the template and the data disagree, for example a new accounting standard that adds a required line. Fail loudly during a dry run rather than shipping a statement that silently omits a mandatory disclosure, and keep a changelog of template revisions tied to the standards they implement.
下面的按钮用 dompdf.js 在浏览器端实时生成 PDF,无需后端:
这是由 dompdf.js 渲染的示例 PDF 内容。