Skip to content

Repository files navigation

ExcelForge 📊

npm versionLicense: MIT

A complete TypeScript library for reading and writing Excel .xlsx and .xlsm (macro-enabled) files with zero external dependencies. Works in browsers, Node.js, Deno, Bun, and edge runtimes.

ExcelForge gives you the full power of the OOXML spec — including real DEFLATE compression, round-trip editing of existing files, and rich property support.


Features

CategoryFeatures
Read existing filesLoad .xlsx from file, Uint8Array, base64, or Blob
Patch-only writesRe-serialise only changed sheets; preserve pivot tables, VBA, charts, unknown parts verbatim
CompressionFull LZ77 + Huffman DEFLATE (levels 0–9). Typical XML compresses 80–85%
Cell ValuesStrings, numbers, booleans, dates, formulas, array formulas, dynamic arrays, shared formulas, rich text
StylesFonts, solid/pattern/gradient fills, all border styles, alignment, 30+ number format presets
LayoutMerge cells, freeze/split panes, column widths, row heights, hide rows/cols, outline grouping
ChartsBar, column (stacked/100%), line, area, pie, doughnut, scatter, radar, bubble; chart sheets; modern styling with 18 color palettes, gradients, data labels, shadows; chart templates
ImagesPNG, JPEG, GIF, BMP, SVG, WebP, ICO, EMF, WMF, TIFF — two-cell, one-cell, or absolute anchors
In-Cell PicturesEmbed images directly inside cells via richData/metadata (Excel 365+)
Shapes28 preset shapes (rect, ellipse, arrows, flowchart, etc.) with fill, line, text, rotation
WordArtText effects with 20 preset transforms (arch, wave, inflate, etc.)
TablesStyled Excel tables with totals row, filter buttons, custom table styles, table slicers
Conditional FormattingCell rules, color scales, data bars, icon sets (incl. custom), cross-worksheet refs
Data ValidationDropdowns, whole number, decimal, date, time, text length, custom formula
SparklinesLine, bar, stacked — with high/low/first/last/negative colors
Pivot TablesRow/column/data fields, aggregation, calculated fields, grouping, custom styles, slicers
Page SetupPaper size, orientation, margins, headers/footers (odd/even/first), print options, page breaks
ProtectionSheet protection with password, cell locking/hiding
Named RangesWorkbook and sheet-scoped
ConnectionsOLEDB, ODBC, text/CSV, web — create, read, round-trip; query tables
Power QueryRead M formulas from DataMashup; full round-trip preservation
External LinksCross-workbook references with sheet names and defined names
VBA MacrosCreate/read .xlsm with standard modules, class modules, document modules; code signing; full round-trip
Auto FilterDropdown filters — value, date, custom, top-10, dynamic filters
HyperlinksExternal URLs, mailto, internal navigation
Form ControlsButton, checkbox, combobox, listbox, radio, groupbox, label, scrollbar, spinner — with macro assignment
Dialog SheetsExcel 5 dialog sheets with dialog frame, OK/Cancel buttons, combo boxes
CommentsCell comments with author, rich text formatting
ThemesFull Office theme XML with customizable colors and fonts
Multiple SheetsAny number, hidden/veryHidden, tab colors
Formula Engine60+ functions including GETPIVOTDATA — tree-shakeable
ExportCSV, JSON, HTML (with CF visualization, sparklines, charts, shapes, form controls), PDF (styled, paginated)
EncryptionOOXML Agile Encryption with AES-256-CBC + SHA-512 via Web Crypto API
Digital SignaturesPackage signing (XML-DSig) + VBA code signing (PKCS#7/CMS, SHA-256)
LocaleConfigurable decimal/thousands separators, date format, currency symbol
Core PropertiesTitle, author, subject, keywords, description, language, revision, category…
Extended PropertiesCompany, manager, application, appVersion, hyperlinkBase, word/line/page counts…
Custom PropertiesTyped key-value store: string, int, decimal, bool, date, r8, i8

Installation

# Copy the src/ directory into your project, or compile to dist/ first:
tsc --outDir dist --target ES2020 --module NodeNext --moduleResolution NodeNext \
--declaration --strict --skipLibCheck src/index.ts [all src files]

No npm install required — zero runtime dependencies.


Quick Start — Create a workbook

import{Workbook,style,Colors,NumFmt}from'./src/index.js';constwb=newWorkbook();wb.coreProperties={title: 'Q4 Report',creator: 'Alice',language: 'en-US'};wb.extendedProperties={company: 'Acme Corp',appVersion: '1.0'};constws=wb.addSheet('Sales Data');// Header rowws.writeRow(1,1,['Product','Q1','Q2','Q3','Q4','Total']);for(letc=1;c<=6;c++){ws.setStyle(1,c,style().bold().bg(Colors.ExcelBlue).fontColor(Colors.White).center().build());}// Data rowsws.writeArray(2,1,[['Widget A',1200,1350,1100,1500],['Widget B',800,950,870,1020],['Gadget X',2100,1980,2250,2400],]);// SUM formulasfor(letr=2;r<=4;r++){ws.setFormula(r,6,`SUM(B${r}:E${r})`);ws.setStyle(r,6,style().bold().build());}ws.freeze(1,0);// freeze first row// Output — compression level 6 by default (80–85% smaller than STORE)awaitwb.writeFile('./report.xlsx');// Node.jsawaitwb.download('report.xlsx');// Browserconstbytes=awaitwb.build();// Uint8Array (any runtime)constb64=awaitwb.buildBase64();// base64 string

Reading & modifying existing files

ExcelForge can load existing .xlsx files and either read their contents or patch them. Only the sheets you mark as dirty are re-serialised on write; everything else — pivot tables, VBA, drawings, slicers, macros — is preserved verbatim from the original ZIP.

Loading

// Node.js / Deno / Bunconstwb=awaitWorkbook.fromFile('./existing.xlsx');// Universal (Uint8Array)constwb=awaitWorkbook.fromBytes(uint8Array);// Browser (File / Blob input element)constwb=awaitWorkbook.fromBlob(fileInputElement.files[0]);// base64 string (e.g. from an API or email attachment)constwb=awaitWorkbook.fromBase64(base64String);

Reading data

console.log(wb.getSheetNames());// ['Sheet1', 'Summary', 'Config']constws=wb.getSheet('Summary');constcell=ws.getCell(3,2);// row 3, col 2console.log(cell.value);// 'Q4 Revenue'console.log(cell.formula);// 'SUM(B10:B20)'console.log(cell.style?.font?.bold);// true

Modifying and saving

constwb=awaitWorkbook.fromFile('./report.xlsx');constws=wb.getSheet('Sales');// Make changesws.setValue(5,3,99000);ws.setStyle(5,3,style().bg(Colors.LightGreen).build());ws.writeRow(20,1,['TOTAL','','=SUM(C2:C19)']);// Mark the sheet dirty — it will be re-serialised on write.// Sheets NOT marked dirty are written back byte-for-byte from the original.ws.markDirty();// preferred: call on the sheet instance directlywb.markDirty('Sales');// or via the workbook (equivalent)// Patch properties without re-serialising any sheetswb.coreProperties.title='Updated Report';wb.setCustomProperty('Status',{type: 'string',value: 'Approved'});awaitwb.writeFile('./report_updated.xlsx');

Tip: If you forget to call markDirty(), your cell changes won't appear in the output because the original sheet XML will be used. Always call it after modifying a loaded sheet.

Active sheet

// Set the active (initially visible) sheet — workbook levelwb.setActiveSheet('Summary');// by namewb.setActiveSheet(2);// by 0-based index// Or directly on the sheet instanceconstws=wb.getSheet('Summary');ws.setActive();

Compression

ExcelForge includes a full pure-TypeScript DEFLATE implementation (LZ77 lazy matching + dynamic/fixed Huffman coding) with no external dependencies. XML content — the bulk of any .xlsx — typically compresses to 80–85% of its original size.

Setting the compression level

constwb=newWorkbook();wb.compressionLevel=6;// 0–9, default 6
LevelDescriptionTypical size vs STORE
0STORE — no compression, fastestbaseline
1FAST — fixed Huffman, minimal LZ77~75% smaller
6DEFAULT — dynamic Huffman + lazy LZ77~82% smaller
9BEST — maximum LZ77 effort~83% smaller (marginal gain over 6)

Level 6 is the default and the recommended choice — it achieves most of the compression benefit of level 9 at a fraction of the CPU cost.

Per-entry level override

The buildZip function used internally also supports per-entry overrides, useful if you want images (already compressed) stored uncompressed while XML entries are compressed:

import{buildZip}from'./src/utils/zip.js';constzip=buildZip([{name: 'xl/worksheets/sheet1.xml',data: xmlBytes},// uses global level{name: 'xl/media/image1.png',data: pngBytes,level: 0},// forced STORE{name: 'xl/styles.xml',data: stylesBytes,level: 9},// max compression],{level: 6});

By default, buildZip automatically stores image file types (png, jpg, gif, tiff, emf, wmf) uncompressed since they're already compressed formats.


Document Properties

ExcelForge reads and writes all three OOXML property namespaces.

Core properties (docProps/core.xml)

wb.coreProperties={title: 'Annual Report 2024',subject: 'Financial Summary',creator: 'Finance Team',keywords: 'excel quarterly finance',description: 'Auto-generated from ERP export',lastModifiedBy: 'Alice',revision: '3',language: 'en-US',category: 'Finance',contentStatus: 'Final',created: newDate('2024-01-01'),// modified is always set to current time on write};

Extended properties (docProps/app.xml)

wb.extendedProperties={application: 'ExcelForge',appVersion: '1.0.0',company: 'Acme Corp',manager: 'Bob Smith',hyperlinkBase: 'https://intranet.acme.com/',docSecurity: 0,linksUpToDate: true,// These are computed automatically on write:// titlesOfParts, headingPairs};

Custom properties (docProps/custom.xml)

Custom properties support typed values — they appear in Excel under File → Properties → Custom.

// Set custom properties at workbook levelwb.customProperties=[{name: 'ProjectCode',value: {type: 'string',value: 'PRJ-2024-007'}},{name: 'Revision',value: {type: 'int',value: 5}},{name: 'Budget',value: {type: 'decimal',value: 125000.00}},{name: 'IsApproved',value: {type: 'bool',value: true}},{name: 'ReviewDate',value: {type: 'date',value: newDate()}},];// Or use the helper methodswb.setCustomProperty('Status',{type: 'string',value: 'In Review'});wb.setCustomProperty('Score',{type: 'decimal',value: 9.7});wb.removeCustomProperty('OldField');// Read backconstproj=wb.getCustomProperty('ProjectCode');console.log(proj?.value.value);// 'PRJ-2024-007'// Full listfor(constpofwb.customProperties){console.log(p.name,p.value.type,p.value.value);}

Available value types: string, int, decimal, bool, date, r8 (8-byte float), i8 (BigInt).


Cell API reference

Writing values

ws.setValue(row,col,value);// string | number | boolean | Datews.setFormula(row,col,'SUM(A1:A5)');ws.setArrayFormula(row,col,'row*col formula','A1:C3');ws.setStyle(row,col,cellStyle);ws.setCell(row,col,{ value, formula, style, comment, hyperlink });// Bulk writesws.writeRow(row,startCol,[v1,v2,v3]);ws.writeArray(startRow,startCol,[[...],[...], ...]);

Reading values

constcell=ws.getCell(row,col);cell.value// the stored value (string | number | boolean | undefined)cell.formula// formula string if presentcell.style// CellStyle object

Styles

import{style,Colors,NumFmt,Styles}from'./src/index.js';// Fluent builderconstheaderStyle=style().bold().italic().fontSize(13).fontColor(Colors.White).bg(Colors.ExcelBlue).border('thin').center().wrapText().numFmt(NumFmt.Currency).build();// Built-in presetsws.setStyle(1,1,Styles.bold);ws.setStyle(1,2,Styles.headerBlue);ws.setStyle(2,3,Styles.currency);ws.setStyle(3,4,Styles.percent);

Number formats

NumFmt.General// GeneralNumFmt.Integer// 0NumFmt.Decimal2// #,##0.00NumFmt.Currency// $#,##0.00NumFmt.Percent// 0%NumFmt.Percent2// 0.00%NumFmt.Scientific// 0.00E+00NumFmt.ShortDate// mm-dd-yyNumFmt.LongDate// d-mmm-yyNumFmt.Time// h:mm:ss AM/PMNumFmt.DateTime// m/d/yy h:mmNumFmt.Accounting// _($* #,##0.00_)NumFmt.Text// @

Layout

ws.merge(r1,c1,r2,c2);// merge a rangews.mergeByRef('A1:D1');ws.freeze(rows,cols);// freeze panesws.setColumn(colIndex,{width: 20,hidden: false, style });ws.setRow(rowIndex,{height: 30,hidden: false});ws.autoFilter={ref: 'A1:E1'};

Conditional formatting

ws.addConditionalFormat({sqref: 'C2:C100',type: 'colorScale',colorScale: {min: {type: 'min',color: 'FFF8696B'},max: {type: 'max',color: 'FF63BE7B'},},priority: 1,});ws.addConditionalFormat({sqref: 'D2:D100',type: 'dataBar',dataBar: {color: 'FF638EC6'},priority: 2,});

Data validation

ws.addDataValidation({sqref: 'B2:B100',type: 'list',formula1: '"North,South,East,West"',showDropDown: false,errorTitle: 'Invalid Region',error: 'Please select a valid region.',});

Charts

ws.addChart({type: 'bar',title: 'Sales by Region',series: [{name: 'Q1 Sales',dataRange: 'Sheet1!B2:B6',catRange: 'Sheet1!A2:A6'}],position: {from: {row: 1,col: 8},to: {row: 20,col: 16}},legend: {position: 'bottom'},});

Supported chart types: bar, col, colStacked, col100, barStacked, bar100, line, lineStacked, area, pie, doughnut, scatter, radar, bubble.

Modern chart styling (Excel 2019+):

ws.addChart({type: 'column',title: 'Styled Chart',series: [{name: 'Revenue',values: "'Sheet1'!$A$2:$D$2",dataLabels: {showValue: true,position: 'outEnd'},fillType: 'gradient',gradientStops: [{pos: 0,color: '4472C4'},{pos: 100,color: 'B4C7E7'}],}],from: {col: 0,row: 5},to: {col: 8,row: 20},colorPalette: 'blue',// 18 palettes: office, blue, orange, green, red, purple, teal...shadow: true,roundedCorners: true,dataLabels: {showPercent: true},// global data labels});

Chart templates:

import{saveChartTemplate,applyChartTemplate,serializeChartTemplate,deserializeChartTemplate}from'excelforge';// Save a chart's style as a templateconsttemplate=saveChartTemplate(chart);constjson=serializeChartTemplate(template);// serialize to JSON stringconstrestored=deserializeChartTemplate(json);// deserialize back// Apply template to a new chartconstnewChart=applyChartTemplate(template,{series: [{name: 'New',values: "'Sheet1'!$A$1:$A$5"}],from: {col: 0,row: 0},to: {col: 5,row: 10},});

Images

Supported formats: png, jpeg, gif, bmp, svg, webp, ico, emf, wmf, tiff.

import{readFileSync}from'fs';constimgData=readFileSync('./logo.png');// Floating image (two-cell anchor)ws.addImage({data: imgData,// Buffer, Uint8Array, or base64 stringformat: 'png',from: {row: 1,col: 1},to: {row: 8,col: 4},});// One-cell anchor with explicit pixel sizews.addImage({data: readFileSync('./icon.svg'),format: 'svg',from: {row: 1,col: 6},width: 80,height: 80,altText: 'Company icon',});// Absolute positioning (not tied to any cell)ws.addImage({data: imgData,format: 'png',position: {x: 200,y: 100},// pixels from top-left of sheetwidth: 120,height: 80,});

In-Cell Pictures

Embed images directly inside cells (Excel 365+ feature). Uses richData/metadata internally.

importtype{CellImage}from'@node-projects/excelforge';ws.addCellImage({data: readFileSync('./photo.png'),format: 'png',cell: 'B2',// cell referencealtText: 'Product photo',});

Pivot tables

constwb=newWorkbook();// Source data sheetconstwsData=wb.addSheet('Data');wsData.writeRow(1,1,['Region','Product','Sales','Units']);wsData.writeArray(2,1,[['North','Widget',12000,150],['South','Widget',9500,120],['North','Gadget',8700,90],['South','Gadget',11200,140],]);// Pivot table on a separate sheetconstwsPivot=wb.addSheet('Summary');wsPivot.addPivotTable({name: 'SalesBreakdown',sourceSheet: 'Data',sourceRef: 'A1:D5',targetCell: 'A1',rowFields: ['Region'],colFields: ['Product'],dataFields: [{field: 'Sales',name: 'Sum of Sales',func: 'sum'}],style: 'PivotStyleMedium9',rowGrandTotals: true,colGrandTotals: true,});awaitwb.writeFile('./pivot_report.xlsx');

Available aggregation functions: sum, count, average, max, min, product, countNums, stdDev, stdDevp, var, varp.

VBA macros

ExcelForge can create, read, and round-trip .xlsm files with VBA macros. All module types are supported: standard modules, class modules, and document modules (auto-created for ThisWorkbook and each worksheet).

import{Workbook,VbaProject}from'./src/index.js';constwb=newWorkbook();constws=wb.addSheet('Sheet1');ws.setValue(1,1,'Hello');constvba=newVbaProject();// Standard modulevba.addModule({name: 'Module1',type: 'standard',code: 'Sub HelloWorld()\r\n MsgBox "Hello from VBA!"\r\nEnd Sub\r\n',});// Class modulevba.addModule({name: 'MyClass',type: 'class',code: ['Private pValue As String','Public Property Get Value() As String',' Value = pValue','End Property','Public Property Let Value(v As String)',' pValue = v','End Property',].join('\r\n')+'\r\n',});wb.vbaProject=vba;awaitwb.writeFile('./macros.xlsm');// must use .xlsm extension

Reading VBA from existing files:

constwb=awaitWorkbook.fromFile('./macros.xlsm');if(wb.vbaProject){for(constmodofwb.vbaProject.modules){console.log(`${mod.name} (${mod.type}): ${mod.code.length} chars`);}}// Modify and re-save — existing modules are preservedwb.vbaProject.addModule({name: 'Module2',type: 'standard',code: '...'});wb.vbaProject.removeModule('OldModule');awaitwb.writeFile('./macros_updated.xlsm');

Note: Document modules for ThisWorkbook and each worksheet are automatically created if not explicitly provided. VBA code uses \r\n line endings.

VBA UserForms

ExcelForge supports creating VBA UserForm modules with form controls. UserForms are embedded in the VBA project with their designer data and can be viewed/edited in the VBA editor.

import{Workbook,VbaProject}from'excelforge';constwb=newWorkbook();wb.addSheet('Sheet1').setValue(1,1,'UserForm Demo');constvba=newVbaProject();// Standard module to show the formvba.addModule({name: 'Module1',type: 'standard',code: 'Sub ShowForm()\n MyForm.Show\nEnd Sub',});// UserForm with controlsvba.addModule({name: 'MyForm',type: 'userform',controls: [{type: 'Label',name: 'Label1',caption: 'Enter name:',left: 10,top: 10,width: 100,height: 18},{type: 'TextBox',name: 'TextBox1',caption: '',left: 10,top: 32,width: 160,height: 22},{type: 'CommandButton',name: 'btnOK',caption: 'OK',left: 50,top: 64,width: 72,height: 26},],code: ['Private Sub btnOK_Click()',' MsgBox "Hello, " & TextBox1.Text',' Unload Me','End Sub',].join('\n'),});wb.vbaProject=vba;awaitwb.writeFile('./userform_demo.xlsm');

Supported control types: CommandButton, TextBox, Label, CheckBox, OptionButton, ComboBox, ListBox, Frame, Image, ScrollBar, SpinButton.

Workbook Calc Settings

Control how Excel recalculates formulas when the workbook is opened.

constwb=newWorkbook();wb.calcSettings={calcMode: 'manual',// 'auto' | 'manual' | 'autoNoTable'iterate: true,// enable iterative calculationiterateCount: 200,// max iterationsiterateDelta: 0.0001,// convergence thresholdfullCalcOnLoad: false,// don't force full recalc on opencalcOnSave: true,// recalculate before savingfullPrecision: true,// use full 15-digit precisionconcurrentCalc: false,// disable multi-threaded calc};

Settings are preserved during round-trip editing. When reading an existing file, wb.calcSettings reflects the workbook's current calculation configuration.

OLE Objects

Embed binary OLE objects (files, packages) into worksheets.

constwb=newWorkbook();constws=wb.addSheet('Sheet1');ws.addOleObject({name: 'EmbeddedFile',progId: 'Package',// OLE program IDfileName: 'data.bin',// display namedata: fileBytes,// Uint8Array of the embedded contentfrom: {col: 1,row: 3},// top-left anchorto: {col: 5,row: 10},// bottom-right anchor});awaitwb.writeFile('./with_ole.xlsx');

Encryption

Encrypt workbooks with a password using OOXML Agile Encryption (AES-256 + SHA-512).

import{Workbook,encryptWorkbook,decryptWorkbook,isEncrypted}from'excelforge';constwb=newWorkbook();wb.addSheet('Secret').setValue(1,1,'Confidential');constxlsxData=awaitwb.build();// Encryptconstencrypted=awaitencryptWorkbook(xlsxData,'myPassword');// Save encrypted file (still uses .xlsx extension)import{writeFileSync}from'fs';writeFileSync('./protected.xlsx',encrypted);// Check if a file is encryptedconsole.log(isEncrypted(encrypted));// true// Decryptconstdecrypted=awaitdecryptWorkbook(encrypted,'myPassword');constwb2=awaitWorkbook.fromBytes(decrypted);

PDF Export

Export worksheets and workbooks as PDF documents with cell styling, pagination, and fit-to-width.

import{Workbook,worksheetToPdf,workbookToPdf}from'excelforge';constwb=newWorkbook();constws=wb.addSheet('Report');// ... populate cells with styles ...// Single worksheet PDFconstpdf=worksheetToPdf(ws,{paperSize: 'a4',orientation: 'portrait',fitToWidth: true,// auto-scale to fit page widthgridLines: true,// draw cell grid linesheadings: false,// row/column headingsrepeatRows: 1,// repeat header row on each pageheaderText: 'Sales Report',footerText: 'Page &P of &N',title: 'Sales Report',author: 'ExcelForge',});import{writeFileSync}from'fs';writeFileSync('./report.pdf',pdf);// Multi-sheet workbook PDFconstwbPdf=workbookToPdf(wb,{footerText: 'Page &P / &N'});writeFileSync('./workbook.pdf',wbPdf);

HTML Export

HTML export has three presets. basic emits the plain grid, styled adds cell formatting, conditional formatting, and built-in Excel table styles, and interactive also adds Excel-style value filters, sticky filter headers, and trims preformatted empty tail rows.

import{Workbook,workbookToHtml}from'@node-projects/excelforge';import{writeFileSync}from'fs';constwb=awaitWorkbook.fromFile('./ErrorsAndWarnings.xlsx');consthtml=workbookToHtml(wb,{title: 'Errors and Warnings',includeTabs: true,mode: 'interactive',});writeFileSync('./ErrorsAndWarnings.html',html,'utf8');

The presets are only conveniences. Each extended feature can be controlled independently:

consthtml=workbookToHtml(wb,{includeStyles: true,includeConditionalFormatting: true,includeTableStyles: true,includeAutoFilters: true,stickyHeaders: true,trimEmptyRows: true,trimEmptyColumns: true,// Optional override when the workbook has no AutoFilter/table metadata.// The first row of every range is treated as the filter header.filterRanges: ['A1:L500'],});

When filterRanges is omitted, interactive export discovers worksheet AutoFilter ranges and Excel table ranges automatically. Filter menus support search, select all, blanks, multi-column filtering, applying, and clearing filters entirely in the generated standalone HTML file.

Digital Signatures

Sign OOXML packages and VBA projects using RSA with SHA-256 via Web Crypto API.

import{signPackage,signVbaProject,signWorkbook}from'excelforge';// Sign the entire packageconstparts=newMap<string,Uint8Array>();parts.set('xl/workbook.xml',workbookBytes);parts.set('xl/worksheets/sheet1.xml',sheetBytes);constsigEntries=awaitsignPackage(parts,{certificate: pemCertificate,// PEM-encoded X.509 certificateprivateKey: pemPrivateKey,// PEM-encoded PKCS#8 private key});// sigEntries contains _xmlsignatures/sig1.xml, origin.sigs, and rels// Sign a VBA projectconstvbaSignature=awaitsignVbaProject(vbaProjectBin,{certificate: pemCertificate,privateKey: pemPrivateKey,});// Or sign both at onceconstresult=awaitsignWorkbook(parts,{ certificate, privateKey },vbaProjectBin);

Page setup

ws.pageSetup={paperSize: 9,// A4orientation: 'landscape',scale: 90,fitToPage: true,fitToWidth: 1,fitToHeight: 0,};ws.pageMargins={left: 0.5,right: 0.5,top: 0.75,bottom: 0.75,header: 0.3,footer: 0.3,};ws.headerFooter={oddHeader: '&C&BQ4 Report&B',oddFooter: '&LExcelForge&RPage &P of &N',};

Page breaks

// Add manual page breaks for printingws.addRowBreak(20);// page break after row 20ws.addRowBreak(40);// page break after row 40ws.addColBreak(5);// page break after column E// Read page breaks from an existing fileconstwb=awaitWorkbook.fromBytes(data);constws=wb.getSheet('Sheet1')!;for(constbrkofws.getRowBreaks()){console.log(`Row break at ${brk.id}, manual: ${brk.manual}`);}for(constbrkofws.getColBreaks()){console.log(`Col break at ${brk.id}, manual: ${brk.manual}`);}

Page breaks are fully preserved during round-trip editing, even when sheets are modified.

Named ranges

// Define workbook-scoped named rangeswb.addNamedRange({name: 'SalesData',ref: 'Data!$A$1:$A$5'});wb.addNamedRange({name: 'Products',ref: 'Data!$B$1:$B$5',comment: 'Product list'});// Define sheet-scoped named rangewb.addNamedRange({name: 'LocalTotal',ref: 'Data!$A$6',scope: 'Data'});// Use in formulasws.setFormula(1,1,'SUM(SalesData)');// Read named ranges from an existing fileconstwb2=awaitWorkbook.fromBytes(data);constranges=wb2.getNamedRanges();// all named rangesconstsales=wb2.getNamedRange('SalesData');// find by nameconsole.log(sales?.ref);// "Data!$A$1:$A$5"// Remove a named rangewb2.removeNamedRange('SalesData');

Named ranges (including scope and comments) are fully preserved during round-trip editing.

Connections & Power Query

// Add a data connection (OLEDB, ODBC, text/CSV, web, etc.)wb.addConnection({id: 1,name: 'SalesDB',type: 'oledb',// 'odbc' | 'dao' | 'file' | 'web' | 'oledb' | 'text' | 'dsp'connectionString: 'Provider=SQLOLEDB;Data Source=server;Initial Catalog=Sales;',command: 'SELECT * FROM Orders',commandType: 'sql',// 'sql' | 'table' | 'default' | 'web' | 'oledb'description: 'Sales database connection',background: true,saveData: true,});// Read connections from an existing fileconstwb2=awaitWorkbook.fromBytes(data);constconns=wb2.getConnections();// all connectionsconstsales=wb2.getConnection('SalesDB');// find by namewb2.removeConnection('SalesDB');// remove by name// Read Power Query M formulas (extracted from DataMashup)constqueries=wb2.getPowerQueries();// all queriesconstq=wb2.getPowerQuery('MyQuery');// find by nameconsole.log(q?.formula);// Power Query M code

Connections are fully preserved during round-trip editing. Power Query formulas (M code) stored in DataMashup binary blobs are automatically extracted for read access. Power Query/Power Pivot data models created in Excel are preserved verbatim during round-trip — you can safely open, modify cells, and save without losing any Power Query or Power Pivot features.

Form Controls

// Add a button with a macrows.addFormControl({type: 'button',from: {col: 1,row: 2},to: {col: 3,row: 4},text: 'Run Report',macro: 'Sheet1.RunReport',});// Button sized by width/height (no 'to' anchor needed)ws.addFormControl({type: 'button',from: {col: 1,row: 5},width: 120,height: 30,// pixelstext: 'Compact Button',});// CheckBox linked to a cellws.addFormControl({type: 'checkBox',from: {col: 1,row: 7},to: {col: 3,row: 8},text: 'Enable Feature',linkedCell: '$B$10',checked: 'checked',// 'checked' | 'unchecked' | 'mixed'});// ComboBox (dropdown) with input rangews.addFormControl({type: 'comboBox',from: {col: 1,row: 7},to: {col: 3,row: 8},linkedCell: '$B$11',inputRange: '$D$1:$D$5',dropLines: 5,});// ListBox, OptionButton, GroupBox, Label, ScrollBar, Spinnerws.addFormControl({type: 'scrollBar',from: {col: 4,row: 6},to: {col: 6,row: 7},linkedCell: '$B$14',min: 0,max: 100,inc: 1,page: 10,val: 50,});// Read form controls from an existing fileconstwb2=awaitWorkbook.fromBytes(data);constcontrols=ws.getFormControls();for(constctrlofcontrols){console.log(ctrl.type,ctrl.linkedCell,ctrl.macro);}

Supported control types: button, checkBox, comboBox, listBox, optionButton, groupBox, label, scrollBar, spinner. All control types support macro assignment and are fully preserved during round-trip editing.

Shapes

ws.addShape({type: 'roundRect',from: {col: 1,row: 3},to: {col: 5,row: 8},fillColor: '4472C4',lineColor: '2F5496',text: 'Process Step',rotation: 0,});

Supported shape types: rect, roundRect, ellipse, triangle, diamond, pentagon, hexagon, octagon, star5, star6, rightArrow, leftArrow, upArrow, downArrow, line, curvedConnector3, callout1, callout2, cloud, heart, lightningBolt, sun, moon, smileyFace, flowChartProcess, flowChartDecision, flowChartTerminator, flowChartDocument.

WordArt

ws.addWordArt({text: 'SALE!',preset: 'textArchUp',font: {name: 'Impact',size: 48,bold: true},fillColor: 'FF0000',outlineColor: '990000',from: {col: 1,row: 1},to: {col: 8,row: 6},});

Supported presets: textPlain, textArchUp, textArchDown, textCircle, textWave1, textWave2, textInflate, textDeflate, textFadeUp, textFadeDown, textSlantUp, textSlantDown, and more.

Themes

wb.theme={name: 'Corporate Theme',colors: [{name: 'dk1',color: '000000'},{name: 'lt1',color: 'FFFFFF'},{name: 'dk2',color: '44546A'},{name: 'lt2',color: 'E7E6E6'},{name: 'accent1',color: '4472C4'},{name: 'accent2',color: 'ED7D31'},{name: 'accent3',color: 'A5A5A5'},{name: 'accent4',color: 'FFC000'},{name: 'accent5',color: '5B9BD5'},{name: 'accent6',color: '70AD47'},{name: 'hlink',color: '0563C1'},{name: 'folHlink',color: '954F72'},],majorFont: 'Calibri Light',minorFont: 'Calibri',};

Table Slicers

ws.addTableSlicer({name: 'RegionSlicer',tableName: 'SalesTable',columnName: 'Region',caption: 'Filter by Region',style: 'SlicerStyleLight1',});

Pivot Slicers & Custom Pivot Styles

wb.registerPivotStyle({name: 'BrandedPivot',elements: [{type: 'headerRow',style: {font: {bold: true,color: 'FFFFFF'},fill: {type: 'pattern',pattern: 'solid',fgColor: '4472C4'}}},],});wb.addPivotSlicer({name: 'ProductSlicer',pivotTableName: 'SalesPivot',fieldName: 'Product',caption: 'Product Filter',});

External Links

wb.addExternalLink({target: 'file:///C:/Reports/Budget.xlsx',sheets: [{name: 'Sheet1'},{name: 'Summary'}],});// Reference external data in formulasws.setFormula(1,1,'[1]Sheet1!A1');

Locale Settings

wb.locale={decimalSeparator: ',',thousandsSeparator: '.',dateFormat: 'DD.MM.YYYY',currencySymbol: '€',};

Sheet protection

ws.protect('mypassword',{formatCells: false,// allow formattinginsertRows: false,// allow inserting rowsdeleteRows: false,sort: false,autoFilter: false,});// Lock individual cells (requires sheet protection to take effect)ws.setCell(1,1,{value: 'Locked',style: {locked: true}});ws.setCell(2,1,{value: 'Editable',style: {locked: false}});

Output methods

// Node.js: write to fileawaitwb.writeFile('./output.xlsx');// Browser: trigger downloadawaitwb.download('report.xlsx');// Any runtime: get bytesconstbytes: Uint8Array=awaitwb.build();constb64: string=awaitwb.buildBase64();

ZIP / Compression API

The buildZip and deflateRaw utilities are exported for direct use:

import{buildZip,deflateRaw,typeZipEntry,typeZipOptions}from'./src/utils/zip.js';// deflateRaw: compress bytes with raw DEFLATE (no zlib header)constcompressed=deflateRaw(data,6);// level 0–9// buildZip: assemble a ZIP archiveconstzip=buildZip(entries,{level: 6});// ZipEntry shapeinterfaceZipEntry{name: string;data: Uint8Array;level?: number;// per-entry override}// ZipOptions shapeinterfaceZipOptions{level?: number;// global default (0–9)noCompress?: string[];// extensions to always STORE}

Architecture overview

ExcelForge
├── core/
│ ├── Workbook.ts — orchestrates build/read/patch, holds properties
│ ├── Worksheet.ts — cells, formulas, styles, drawings, page setup
│ ├── WorkbookReader.ts — parse existing XLSX (ZIP → XML → object model)
│ ├── SharedStrings.ts — string deduplication table
│ ├── properties.ts — core / extended / custom property read+write
│ └── types.ts — all 80+ TypeScript interfaces
├── styles/
│ ├── StyleRegistry.ts — interns fonts/fills/borders/xfs, emits styles.xml
│ └── builders.ts — fluent style() builder, Colors/NumFmt/Styles presets
├── features/
│ ├── ChartBuilder.ts — DrawingML chart XML for 15+ chart types, templates, modern styling
│ ├── TableBuilder.ts — Excel table XML
│ ├── PivotTableBuilder.ts — pivot table + cache XML
│ ├── HtmlModule.ts — HTML/CSS export with charts, images, sparklines, shapes
│ ├── PdfModule.ts — PDF export with cell styles, pagination, images
│ ├── Encryption.ts — OOXML Agile Encryption (AES-256-CBC + SHA-512)
│ └── Signing.ts — Digital signatures (XML-DSig + VBA PKCS#7/CMS)
├── vba/
│ ├── VbaProject.ts — VBA project build/parse, module management
│ ├── cfb.ts — Compound Binary File (OLE2) reader & writer
│ └── ovba.ts — MS-OVBA compression/decompression
└── utils/
├── zip.ts — ZIP writer with full LZ77+Huffman DEFLATE
├── zipReader.ts — ZIP reader (STORE + DEFLATE via DecompressionStream)
├── xmlParser.ts — roundtrip-safe XML parser (preserves unknown nodes)
└── helpers.ts — cell ref math, XML escaping, date serials, EMU conversion

Round-trip / patch strategy

When you load an existing .xlsx and call wb.build():

  1. The original ZIP is read and every entry is retained as raw bytes.
  2. Sheets not marked dirty via wb.markDirty(name) are written back verbatim — their original bytes are preserved unchanged.
  3. Sheets that are marked dirty are re-serialised with any changes applied.
  4. Core/extended/custom properties are always rewritten (they're cheap and typically user-modified).
  5. Styles and shared strings are always rewritten (dirty sheets need fresh indices).
  6. All other parts — drawings, charts, images, pivot tables, VBA modules, custom XML, connections, theme — are preserved verbatim.

This means you can safely open a complex Excel file produced by another tool, change a few cells, and save without losing any features ExcelForge doesn't understand.


Browser usage

ExcelForge is fully tree-shakeable and has zero runtime dependencies. In the browser, use CompressionStream / DecompressionStream (available in all modern browsers since 2022) for decompression when reading files.

<inputtype="file" id="file" accept=".xlsx"><scripttype="module">import{Workbook}from'./dist/index.js';document.getElementById('file').addEventListener('change',async(e)=>{constfile=e.target.files[0];constwb=awaitWorkbook.fromBlob(file);console.log('Sheets:',wb.getSheetNames());console.log('Title:',wb.coreProperties.title);constws=wb.getSheet(wb.getSheetNames()[0]);console.log('A1:',ws.getCell(1,1).value);// Modify and re-downloadws.setValue(1,1,'Modified!');wb.markDirty(wb.getSheetNames()[0]);awaitwb.download('modified.xlsx');});</script>

Changelog

v3.0 — More

  • Form Controls - create Form Controls
  • Wordart
  • Formula Objects
  • Chart Pages
  • Many more features....

v2.4 — Pivot Tables & VBA Macros

  • Pivot tables — create pivot tables with row/column/data fields, 11 aggregation functions, customisable styles
  • VBA macros — create, read, and round-trip .xlsm files with standard, class, and document modules
  • CFB (OLE2) support — MS-CFB reader/writer for vbaProject.bin, with MS-OVBA compression
  • Automatic sheet modules — document modules for ThisWorkbook and each worksheet are auto-generated

v2.0 — Read, Modify, Compress

  • Read existing XLSX filesWorkbook.fromFile(), fromBytes(), fromBase64(), fromBlob()
  • Patch-only writes — preserve unknown parts verbatim, only re-serialise dirty sheets
  • Full DEFLATE compression — pure-TypeScript LZ77 + dynamic Huffman (levels 0–9), 80–85% smaller output
  • Extended & custom properties — full read/write of core.xml, app.xml, custom.xml
  • New utilitieszipReader.ts, xmlParser.ts, properties.ts

v1.0 — Initial release

  • Full XLSX write support: cells, formulas, styles, charts, images, tables, conditional formatting, data validation, sparklines, page setup, protection, named ranges, auto filter, hyperlinks, comments

About

library to create excel files from javvascript

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages