R
Rizqy Eka Putra
Guest
TLDR; The Origin Story
So, recently while i work on my weekend projects, as well in some projects with my team at Nodewave, there are a significant amount of time (and pains...) consumed when we need to generate a structured XLSX with complex tables that has merged cells (row, columns). Before, we are always writing a custom excel builder with
xlsx-js-style
for EVERY single XLSX file output, which you can already tell must be really time consuming, nevermind the back and forth to check the result while testing it.Then, in a discussion, we came with an idea, why not write it with a
table
components in HTML, which natively already has colspan
, rowspan
attribute, and also has inline-css
for the styling.As a bunch of curious engineers we started to get going by discussing it with an expert every developer's reach out whenever they have a new idea (TLDR; AI)

Initially, all we wanted to do is just to parse an html table that contains
rowspan
and colspan
attributes, process it with cheerio
feed it into a custom function that builds excel rows with xlsx-js-style
and call it a day.But then again, we came to think, why not also take care of the styling,
xlsx-js-style
has the capability to set various things like background color
, text color
and many more.So in a span of just several hours, we already came with 4 Minor VERSIONS of the library.
v0.1.0 -> Basic HTML table processor, which have a separate title config (we removed this immediately later on)
v0.1.1 -> Some small refactors
v0.1.2 -> Yet again another refactors, to handle title within the conversion process
v0.2.0 -> Handles inline CSS
So, without further ado, we presents you
@nodewave/table-to-xlsx
, the idea is simple, it takes an html table in a string, then returns an excel corresponding of that tables, we support 2 output mode :- Buffer Mode
- File Output Mode.
Let's See It In Action!
Now that we've covered the origin story, let me showcase the incredible capabilities of our
@nodewave/table-to-xlsx
package with some real-world examples.
Basic Usage - Simple Table Conversion
Let's start with the basics. Here's how simple it is to convert a basic HTML table:
Code:
import { convert } from '@nodewave/table-to-xlsx'
const simpleTable = `
<table>
<tr>
<th>Name</th>
<th>Age</th>
<th>City</th>
</tr>
<tr>
<td>John Doe</td>
<td>30</td>
<td>New York</td>
</tr>
<tr>
<td>Jane Smith</td>
<td>25</td>
<td>Los Angeles</td>
</tr>
</table>
`
// Convert to buffer (in-memory)
const buffer = await convert(simpleTable)
// Or save directly to file
await convert(simpleTable, './employees.xlsx')
Advanced Features - Merged Cells & Complex Layouts
This is where our package truly shines! Let's create a complex table with merged cells that would be a nightmare to build manually with Excel APIs:
Code:
const complexTable = `
<table>
<tr>
<th style="background-color: #2E86AB; color: white; font-size: 18px; text-align: center;" colspan="6">
π’ Company Financial Report Q4 2024
</th>
</tr>
<tr>
<td style="border: none;" colspan="6"></td>
</tr>
<tr>
<th style="background-color: #F8F9FA; font-weight: bold;" rowspan="2">Department</th>
<th style="background-color: #F8F9FA; font-weight: bold;" rowspan="2">Team</th>
<th style="background-color: #F8F9FA; font-weight: bold; text-align: center;" colspan="2">Q3 Results</th>
<th style="background-color: #F8F9FA; font-weight: bold; text-align: center;" colspan="2">Q4 Results</th>
</tr>
<tr>
<th style="background-color: #E9ECEF; text-align: center;">Revenue</th>
<th style="background-color: #E9ECEF; text-align: center;">Growth</th>
<th style="background-color: #E9ECEF; text-align: center;">Revenue</th>
<th style="background-color: #E9ECEF; text-align: center;">Growth</th>
</tr>
<tr>
<td style="font-weight: bold; color: #2E86AB;">Engineering</td>
<td>Backend Team</td>
<td style="text-align: right; color: #28A745;">$2.5M</td>
<td style="text-align: center; color: #28A745;">+15%</td>
<td style="text-align: right; color: #28A745;">$2.9M</td>
<td style="text-align: center; color: #28A745;">+16%</td>
</tr>
<tr>
<td style="font-weight: bold; color: #2E86AB;">Engineering</td>
<td>Frontend Team</td>
<td style="text-align: right; color: #28A745;">$1.8M</td>
<td style="text-align: center; color: #28A745;">+12%</td>
<td style="text-align: right; color: #28A745;">$2.1M</td>
<td style="text-align: center; color: #28A745;">+17%</td>
</tr>
<tr>
<td style="font-weight: bold; color: #2E86AB;">Sales</td>
<td>Enterprise</td>
<td style="text-align: right; color: #28A745;">$5.2M</td>
<td style="text-align: center; color: #28A745;">+8%</td>
<td style="text-align: right; color: #28A745;">$5.8M</td>
<td style="text-align: center; color: #28A745;">+12%</td>
</tr>
<tr>
<td style="font-weight: bold; color: #2E86AB;">Sales</td>
<td>SMB</td>
<td style="text-align: right; color: #FFC107;">$1.2M</td>
<td style="text-align: center; color: #FFC107;">-3%</td>
<td style="text-align: right; color: #28A745;">$1.4M</td>
<td style="text-align: center; color: #28A745;">+17%</td>
</tr>
<tr>
<td style="border: none;" colspan="6"></td>
</tr>
<tr>
<th style="background-color: #D4EDDA; color: #155724; font-size: 16px; text-align: center;" colspan="6">
π Total Q4 Revenue: $12.2M (+15% YoY)
</th>
</tr>
</table>
`
// Convert this complex table with merged cells and styling
await convert(complexTable, './financial-report.xlsx')
Styling Capabilities - CSS Classes & Inline Styles
Our package supports both inline CSS and CSS classes, making it incredibly flexible:
Code:
const styledTable = `
<table>
<tr>
<th class="header-cell text-center font-bold" style="background-color: #6C757D; color: white; font-size: 20px;" colspan="4">
π― Product Performance Dashboard
</th>
</tr>
<tr>
<td style="border: none;" colspan="4"></td>
</tr>
<tr>
<th class="subheader text-left font-bold" style="background-color: #F8F9FA;">Product Name</th>
<th class="subheader text-center font-bold" style="background-color: #F8F9FA;">Category</th>
<th class="subheader text-center font-bold" style="background-color: #F8F9FA;">Sales</th>
<th class="subheader text-center font-bold" style="background-color: #F8F9FA;">Rating</th>
</tr>
<tr>
<td style="color: #2E86AB; font-weight: bold;">π Product A</td>
<td class="text-center">Software</td>
<td class="text-right font-bold" style="color: #28A745;">$125K</td>
<td class="text-center font-bold" style="background-color: #D4EDDA; color: #155724;">βββββ</td>
</tr>
<tr>
<td style="color: #2E86AB; font-weight: bold;">π‘ Product B</td>
<td class="text-center">Hardware</td>
<td class="text-right font-bold" style="color: #28A745;">$89K</td>
<td class="text-center font-bold" style="background-color: #D4EDDA; color: #155724;">ββββ</td>
</tr>
<tr>
<td style="color: #2E86AB; font-weight: bold;">π§ Product C</td>
<td class="text-center">Service</td>
<td class="text-right font-bold" style="color: #FFC107;">$67K</td>
<td class="text-center font-bold" style="background-color: #FFF3CD; color: #856404;">βββ</td>
</tr>
</table>
`
await convert(styledTable, './product-dashboard.xlsx')
Real-World Use Case - Invoice Table
Here's a practical example of an invoice table that demonstrates complex merging and professional styling:
Code:
const invoiceTable = `
<table>
<tr>
<td style="border: none; font-size: 24px; font-weight: bold; color: #2E86AB;" colspan="6">
οΏ½οΏ½ NODEWAVE TECHNOLOGIES
</td>
</tr>
<tr>
<td style="border: none; font-size: 14px; color: #6C757D;" colspan="6">
123 Innovation Street, Tech City, TC 12345 | Phone: (555) 123-4567
</td>
</tr>
<tr>
<td style="border: none;" colspan="6"></td>
</tr>
<tr>
<td style="border: none; font-weight: bold;" colspan="3">Bill To:</td>
<td style="border: none; font-weight: bold; text-align: right;" colspan="3">Invoice Details:</td>
</tr>
<tr>
<td style="border: none;" colspan="3">
John Smith<br>
ABC Company<br>
456 Business Ave<br>
Corporate City, CC 67890
</td>
<td style="border: none; text-align: right;" colspan="3">
Invoice #: INV-2024-001<br>
Date: ${new Date().toLocaleDateString()}<br>
Due Date: ${new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toLocaleDateString()}<br>
Terms: Net 30
</td>
</tr>
<tr>
<td style="border: none;" colspan="6"></td>
</tr>
<tr>
<th style="background-color: #2E86AB; color: white; text-align: center;">Item</th>
<th style="background-color: #2E86AB; color: white; text-align: center;">Description</th>
<th style="background-color: #2E86AB; color: white; text-align: center;">Qty</th>
<th style="background-color: #2E86AB; color: white; text-align: center;">Rate</th>
<th style="background-color: #2E86AB; color: white; text-align: center;">Amount</th>
<th style="background-color: #2E86AB; color: white; text-align: center;">Tax</th>
</tr>
<tr>
<td style="font-weight: bold;">Web Development</td>
<td>Custom e-commerce platform with admin dashboard</td>
<td style="text-align: center;">1</td>
<td style="text-align: right;">$15,000</td>
<td style="text-align: right; font-weight: bold;">$15,000</td>
<td style="text-align: right;">$1,500</td>
</tr>
<tr>
<td style="font-weight: bold;">UI/UX Design</td>
<td>User interface and experience design</td>
<td style="text-align: center;">1</td>
<td style="text-align: right;">$5,000</td>
<td style="text-align: right; font-weight: bold;">$5,000</td>
<td style="text-align: right;">$500</td>
</tr>
<tr>
<td style="font-weight: bold;">Testing</td>
<td>Quality assurance and testing services</td>
<td style="text-align: center;">1</td>
<td style="text-align: right;">$3,000</td>
<td style="text-align: right; font-weight: bold;">$3,000</td>
<td style="text-align: right;">$300</td>
</tr>
<tr>
<td style="border: none;" colspan="6"></td>
</tr>
<tr>
<td style="border: none;" colspan="4"></td>
<td style="background-color: #F8F9FA; font-weight: bold; text-align: right;">Subtotal:</td>
<td style="background-color: #F8F9FA; font-weight: bold; text-align: right;">$23,000</td>
</tr>
<tr>
<td style="border: none;" colspan="4"></td>
<td style="background-color: #F8F9FA; font-weight: bold; text-align: right;">Tax (10%):</td>
<td style="background-color: #F8F9FA; font-weight: bold; text-align: right;">$2,300</td>
</tr>
<tr>
<td style="border: none;" colspan="4"></td>
<td style="background-color: #2E86AB; color: white; font-weight: bold; font-size: 16px; text-align: right;">TOTAL:</td>
<td style="background-color: #2E86AB; color: white; font-weight: bold; font-size: 16px; text-align: right;">$25,300</td>
</tr>
</table>
`
await convert(invoiceTable, './invoice.xlsx')
API Flexibility - Multiple Import Styles
Our package supports multiple import styles for maximum flexibility:
Code:
// Style 1: Default import (class-based)
import TableToXlsx from '@nodewave/table-to-xlsx'
const buffer1 = await TableToXlsx.convert(html)
// Style 2: Named imports (functional)
import { convert, convertToFile, convertToBuffer } from '@nodewave/table-to-xlsx'
const buffer2 = await convert(html)
const filePath = await convertToFile(html, './output.xlsx')
// Style 3: Namespace import
import * as TableToXlsx from '@nodewave/table-to-xlsx'
const buffer3 = await TableToXlsx.convert(html)
Supported CSS Properties
Our package has the ability to parse and converts these CSS properties:
- Colors:
background-color
,color
,border-color
- Typography:
font-size
,font-weight
,text-align
- Borders:
border
,border-style
,border-color
- CSS Classes:
text-center
,text-left
,text-right
,font-bold
,font-normal
Get Started Today
Code:
npm install @nodewave/table-to-xlsx
No more painstaking XLSX producer API calls. No more complex cell merging logic. Just write your table in HTML (which you probably already know how to do, or ask AI to do it for you

Contributing to this Package
At Nodewave, we believe in collaboration to boost developer productivity and experience. So if you have any idea for this package to extends it functionality any further, please feel free to open an issue and create your PR at our repository. And don't forget to drop some


Continue reading...