ExcelJS reads and writes XLSX beautifully but has no PDF export — a gap documented in GitHub issues 1562 and 1806 [1][2]. After workbook.xlsx.writeBuffer(), you have a buffer going nowhere. The community's reflex is to shell out to headless LibreOffice in Docker, inheriting a second class of production problems: font substitution, broken column widths, conversions failing under concurrent requests, and broken characters in Alpine images [3][4][5][6].
ExcelJS issue 1562 asks directly whether Excel files created with the library can convert to PDF, and issue 1806 proposes adding PDF or HTML export as a native feature [1][2]. Neither has shipped. On Stack Overflow, developers hit the same wall after workbook.xlsx.writeBuffer() in Angular — the buffer has no rendering target [7]. Reddit's r/node threads show the same pattern: users with styled spreadsheets unable to find any standalone JS library that handles XLSX-to-PDF properly, with answers pointing to paid external APIs or manual HTML table rendering [8][9].
The default community answer is LibreOffice headless. It reads the format, but the results are never perfect and sometimes unacceptable [10]. Font substitution is immediate: Arial and Times New Roman become LiberationSerif throughout the document [3]. Column widths break, with some columns wrapping onto the next line in the PDF instead of displaying as a single table [4]. Under concurrent requests, the conversion stops working entirely — a bug tracked in LibreOffice Bugzilla 124798 [5]. In Alpine Docker images, missing font packages produce broken characters, worked around by manual installation of msttcorefonts-installer, fontconfig, and Google fonts [6].
Nutrient offers a Node.js SDK that claims no external dependencies, serverless readiness, and PDF/A support, unlike the libreoffice-convert wrapper it also documents. By default, PDFs carry a Nutrient watermark removed only with a license key. PDF4.dev notes there is no free public API for this conversion and suggests self-hosting LibreOffice behind Gotenberg. The remaining option — parsing XLSX yourself with a library like ExcelJS, then rebuilding layout, pagination, and styling in PDFKit or Puppeteer — means reimplementing a spreadsheet renderer from scratch.
MarkupGo's Office to PDF API handles over 130 formats including .xlsx, currently in beta [11]. The official markupgo-node client exposes the same OfficeProperties options [12].
Install the client:
npm install markupgo-node
Read your XLSX file and convert:
import { MarkupGoClient } from 'markupgo-node';
import fs from 'fs';
const client = new MarkupGoClient({
apiKey: process.env.MARKUPGO_API_KEY
});
const fileBuffer = fs.readFileSync('./report.xlsx');
const pdfBuffer = await client.officeToPdf({
files: [fileBuffer],
properties: {
landscape: true,
singlePageSheets: false
}
});
fs.writeFileSync('./report.pdf', pdfBuffer);
The files array accepts buffers or streams. The client handles the multipart/form-data encoding the API requires [11]. If you want the PDF as a buffer rather than a task object, the API supports that — just append /buffer to the conversion URL [11]. For a financial report with wide columns, landscape: true suits wide tables [11]. Keeping singlePageSheets: false preserves normal pagination across multiple pages rather than compressing each sheet to one page [11].
The properties object controls how spreadsheet structure maps to PDF pages.
landscape: rotate to landscape orientation for wide tables, financial models, or dashboards with many columns [11]singlePageSheets: fit each sheet onto a single PDF page, useful for summary dashboards you intend to screenshot or thumbnail; hidden sheets are included [11]merge: combine multiple uploaded workbooks into one PDF, ordered alphanumerically [11]quality and maxImageResolution: control compression for charts and embedded images — default quality is 90, with optional resolution reduction to 75, 150, 300, 600, or 1200 DPI [11]For a monthly finance package with three workbook files, set merge: true and receive one combined PDF. For a board presentation with embedded charts, raise quality and leave resolution untouched.
The Office to PDF API is in beta, so monitor conversion results for edge cases in your specific spreadsheets [11]. Each document is limited to 5MB and 10 files per request; contact MarkupGo for higher limits [11]. The upload uses multipart/form-data, not JSON, but the Node client abstracts this [11].
Each conversion consumes credits from your plan. The free trial includes 100 credits per month with no credit card required, enough to validate the pipeline before committing.
Generate PDF Invoices from Google Sheets Automatically (No Code)
A new row lands in your Google Sheets client tracker. Thirty seconds later, a designed, branded invoice PDF is sitting in your Drive folder and on its way to…
chrome-headless-shell vs Full Chromium for PDF Rendering (and the ARM64 Problem Nobody Warns You About)
Chrome 132 killed --headless=old. That flag now prints an error instead of launching, so every PDF pipeline that relied on it must pick a new binary: the…