3 weeks ago - last edited 3 weeks ago
Every time I needed to generate .xlsx files in a CAP project the answer was the same: pull in exceljs or xlsx, wire up the API, and deal with the bundle weight. I decided to solve it differently.
I built @capgio-js/excel — a CAP plugin that generates Excel workbooks using only Node.js built-ins (zlib and buffer). No external runtime dependencies at all.
It's not public yet. Before publishing I want to know if this is just a problem I have or if it resonates with more people.
HOW IT WORKS
Install the package and it auto-discovers itself as a CAP plugin — no manual require in your handlers. The full API is available under cds.excel.
Entity-driven tables — the part I use most
The thing that saves the most time: pass a CDS entity and the plugin builds the columns automatically. It reads your entity's elements, skips associations and compositions, and generates one column per primitive field. It picks up @title and @Common.Label annotations as column headers and maps CDS types to the right Excel cell styles out of the box.
srv.on('exportReport', async (req) => {
const { ExcelBuilder } = cds.excel
const rows = await SELECT.from('MyService.Orders').orderBy('createdAt desc')
const wb = new ExcelBuilder()
wb.addSheet('Orders').addEntity(srv.entities.Orders, {
rows,
labels: true, // column headers from @title / @Common.Label
autoFormat: true, // integer, decimal, date styles from CDS types
exclude: ['internalField', 'technicalKey'],
order: ['orderDate', 'customer', 'total'],
overrides: { total: { header: 'Total (USD)' } },
theme: 'blue',
})
return wb
})CDS type to Excel style mapping:
Integer, Int16, Int32, Int64, UInt8 → Whole number
Decimal, Double → 2 decimal places
Date, DateTime, Timestamp → Short date
Everything else → General
If you prefer incremental configuration there is a fluent EntityBuilder that defers rendering until serialisation time:
const cfg = sheet.getEntity(srv.entities.Orders)
cfg
.setLabels(true)
.setAutoFormat(true)
.include(['orderDate', 'customer', 'amount', 'status'])
.setOrder(['orderDate', 'customer'])
.addRows(rows)Pair it with the @Excel.export annotation and the download becomes fully declarative — annotate the action in your .cds model, return the ExcelBuilder instance from the handler, and the plugin intercepts the result and sends the binary response automatically:
// in your .cds model
@Excel.export: { filename: 'orders.xlsx' }
action exportOrders();
// in your handler
srv.on('exportOrders', async (req) => {
const wb = new ExcelBuilder()
wb.addSheet('Orders').addEntity(srv.entities.Orders, {
rows: await SELECT.from('MyService.Orders'),
labels: true,
autoFormat: true,
})
return wb // plugin handles the binary response
})Tests:
This is not mocked or manually created — it is a direct output from the current implementation.
Other features
- Multi-sheet workbooks — add as many named sheets as you need per workbook
- 8 colour themes (blue, green, orange, red, purple, dark, light, default) with header rows and alternating stripes
- Formula support — SUM, PRODUCT, cell multiply, and arbitrary inline formulas
- Three download patterns — Base64 string for OData actions, direct binary via cds.excel.download(), or fully declarative via @Excel.export
- Worksheet protection — sheet.protect({ password }) to prevent accidental edits
- Sensitivity labels — embed classification metadata as custom document properties
- Structured logging — integrates with cds.log('excel'), falls back to console.* outside CAP
- Deterministic output — fixed timestamps produce byte-identical ZIP archives across builds
- Standalone — works without CAP via require('@cap-js/excel')
WHY NO DEPENDENCIES?
exceljs is around 200 KB minified and pulls sub-dependencies. xlsx has a dual license that can get complicated in corporate environments. Since .xlsx is just a ZIP of XML files with an open spec (OOXML / ECMA-376), implementing the essentials with Node's native zlib is doable — and it removes any supply chain, license, or bundle-size conversation entirely.
IS THIS USEFUL TO YOU?
A few questions before I decide whether to publish:
- Do you use CAP and need to generate Excel today? How are you solving it?
- Does the zero-dependency angle matter in your context — audits, license policies, bundle size?
- Is there a feature that would make this actually useful for you that isn't listed here?
Happy to share more details or a demo. Any feedback appreciated.
Request clarification before answering.
| User | Count |
|---|---|
| 15 | |
| 9 | |
| 6 | |
| 5 | |
| 4 | |
| 4 | |
| 3 | |
| 2 | |
| 2 | |
| 2 |
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.