If you build business software, you've probably met report engines: JasperReports, SSRS, Crystal Reports. They're powerful, but they need a JVM or .NET, often a paid server, and they don't fit a JavaScript stack.
The JavaScript options weren't great either. You could print HTML through headless Chrome (slow, heavy, falls over on big reports), or hand-code layouts with a document builder like pdfmake.
So I built ReportWright: an open-source (MIT) report engine for JavaScript.
What it does
You describe a report in JSON (page bands, groups, tables, charts, expressions), give it data, and it renders:
- PDF (including PDF/A for archiving and PDF/UA for accessibility)
- Excel, Word and PowerPoint
- interactive HTML, where groups expand and drill down inside the file
It runs in Node.js or the browser. There's a drag-and-drop designer, a viewer for React, Vue, Svelte and Angular, and a CLI.
A report in a few lines
npm i @reportwright/engine@next @reportwright/fonts@next
import fs from 'node:fs';
import { render, exportPdf, defaultFontStore } from '@reportwright/engine';
const report = {
$schema: 'reportwright/report@1',
name: 'Orders',
page: { size: 'A4', margins: [36, 36, 36, 36] },
dataSources: [{ name: 'src', type: 'json', data: { rows: [
{ customer: 'Acme', amount: 1200 },
{ customer: 'Globex', amount: 850 },
] } }],
dataSets: [{ name: 'Orders', source: 'src', path: '$.rows' }],
sections: { body: { items: [{
type: 'table', dataSet: 'Orders', x: 0, y: 0, w: 523, h: 40,
columns: [{ width: 360 }, { width: 163 }],
header: [{ height: 18, cells: [{ value: 'Customer' }, { value: 'Amount' }] }],
detail: [{ height: 16, cells: [{ value: '=Fields.customer' }, { value: '=Fields.amount', style: { format: 'N2' } }] }],
}] } },
};
const fonts = defaultFontStore();
const model = await render(report, { fontStore: fonts });
fs.writeFileSync('orders.pdf', await exportPdf(model, { fonts }));
Moving from SSRS or JasperReports
npx @reportwright/cli@next import ./my-reports --out ./imported
It converts .rdl, .rdlc, .jrxml, BIRT and ActiveReports definitions, and writes a report of what carried over and what needs a manual fix.
How fast is it?
I benchmarked it on GitHub Actions (4 vCPU) against other engines, checking every output file.
| Grouped report | ReportWright | JasperReports | QuestPDF (.NET) |
|---|---|---|---|
| 10,000 rows | 0.91 s | 3.9 s | 2.7 s |
| 100,000 rows | 6.6 s | 11.8 s | 24.1 s |
| 1,000 separate reports | 8 s | 33 s | 26 s |
A million rows streams in 53 s with flat memory. Against Python, the PDF library writes a million rows in 29 s versus ReportLab's 215 s.
Where it loses
I'd rather you hear it from me:
- QuestPDF is about 2× faster on single invoices.
- Go and Rust PDF writers are faster on tiny files, mostly because Node takes ~30 ms to start.
- C tools like MuPDF and qpdf win quick edits such as merge and encrypt.
All results, wins and losses: https://github.com/MrArun005/reportwright/blob/bench-deep/bench/deep/RESULTS.md
Why JavaScript can be this fast
- V8 compiles hot code to machine code, so tight loops (layout, text measuring) run fast.
- The PDF writer streams pages out as they're done, so memory stays flat even at a million rows.
- Binary work uses typed arrays, not millions of small objects.
Try it
- Demo in your browser: https://mrarun005.github.io/reportwright-demo
- Repo: https://github.com/MrArun005/reportwright
- PDF library on its own: https://github.com/MrArun005/reportwright-pdf
It's free and MIT-licensed. If you have SSRS or Jasper reports that don't import well, please open an issue. Those are the most useful bug reports I can get.



Top comments (0)