Claude
Skill
xlsx-official
Generating Excel files with xlsx/exceljs in Node.js. Use when generating .xlsx reports, data exports, dashboards, or spreadsheets from database data.
Virus-scanned
Reviewed automatically before listing.
Download
sabahattink-antigravity-fullstack-hq-skills_xlsx-official-1acbfa7.zip · 3 KB
Install
skills CLI
npx skills add https://github.com/sabahattink/antigravity-fullstack-hq/tree/main/skills/xlsx-official
Claude Code
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install sabahattink-antigravity-fullstack-hq@llmmart
Git
git clone https://github.com/sabahattink/antigravity-fullstack-hq.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole sabahattink/antigravity-fullstack-hq collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Excel Generation
Option A — ExcelJS (Feature-Rich)
npm install exceljs
npm install -D @types/node
// lib/excel/report.excel.ts
import ExcelJS from 'exceljs'
export async function generateExcelReport(data: ReportData): Promise<Buffer> {
const workbook = new ExcelJS.Workbook()
workbook.creator = 'Full Stack HQ'
workbook.lastModifiedBy = 'system'
workbook.created = new Date()
workbook.modified = new Date()
const sheet = workbook.addWorksheet('Report', {
pageSetup: {
paperSize: 9, // A4
orientation: 'landscape',
fitToPage: true,
},
views: [{ state: 'frozen', xSplit: 0, ySplit: 1 }], // freeze header row
})
// ─── Column Definitions ───────────────────────────────────
sheet.columns = data.columns.map(col => ({
header: col.header,
key: col.key,
width: col.width ?? 20,
style: {
alignment: { horizontal: col.align ?? 'left', wrapText: false },
numFmt: col.format, // e.g. '#,##0.00', 'yyyy-mm-dd'
},
}))
// ─── Style the Header Row ─────────────────────────────────
const headerRow = sheet.getRow(1)
headerRow.eachCell(cell => {
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FF2563EB' },
}
cell.font = {
name: 'Calibri',
size: 11,
bold: true,
color: { argb: 'FFFFFFFF' },
}
cell.border = {
bottom: { style: 'medium', color: { argb: 'FF1D4ED8' } },
}
})
headerRow.height = 24
// ─── Add Data Rows ────────────────────────────────────────
data.rows.forEach((row, index) => {
const excelRow = sheet.addRow(row)
excelRow.height = 18
// Alternating row colors
if (index % 2 === 0) {
excelRow.eachCell(cell => {
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFF8FAFC' },
}
})
}
excelRow.eachCell(cell => {
cell.font = { name: 'Calibri', size: 10 }
cell.border = {
bottom: { style: 'thin', color: { argb: 'FFE5E7EB' } },
}
})
})
// ─── Summary Row ──────────────────────────────────────────
const summaryRow = sheet.addRow(data.summary)
summaryRow.eachCell(cell => {
cell.font = { name: 'Calibri', size: 10, bold: true }
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFEFF6FF' },
}
cell.border = {
top: { style: 'medium', color: { argb: 'FF2563EB' } },
bottom: { style: 'medium', color: { argb: 'FF2563EB' } },
}
})
// ─── Auto-filter ──────────────────────────────────────────
sheet.autoFilter = {
from: { row: 1, column: 1 },
to: { row: 1, column: data.columns.length },
}
// ─── Return Buffer ────────────────────────────────────────
return workbook.xlsx.writeBuffer() as Promise<Buffer>
}
Multiple Sheets
async function generateMultiSheetReport(report: MultiSheetReport): Promise<Buffer> {
const workbook = new ExcelJS.Workbook()
// Summary sheet
const summarySheet = workbook.addWorksheet('Summary')
addSummarySheet(summarySheet, report.summary)
// Data sheets
for (const section of report.sections) {
const sheet = workbook.addWorksheet(section.name.substring(0, 31)) // Excel 31-char limit
addDataSheet(sheet, section.columns, section.rows)
}
// Charts sheet (using data from other sheets)
const chartSheet = workbook.addWorksheet('Charts')
addChart(workbook, chartSheet, report)
return workbook.xlsx.writeBuffer() as Promise<Buffer>
}
Charts in ExcelJS
function addChart(workbook: ExcelJS.Workbook, sheet: ExcelJS.Worksheet, report: Report) {
// ExcelJS doesn't support charts directly — use chartjs-to-image or
// embed a pre-generated chart image
// Generate chart as PNG buffer (using chartjs-node-canvas)
const chartImage = generateChartPng(report.chartData)
const imageId = workbook.addImage({
buffer: chartImage,
extension: 'png',
})
sheet.addImage(imageId, {
tl: { col: 0, row: 0 },
ext: { width: 800, height: 400 },
editAs: 'oneCell',
})
}
Option B — SheetJS (xlsx) — Simpler
npm install xlsx
npm install -D @types/xlsx
import * as XLSX from 'xlsx'
export function generateSimpleXlsx(data: {
headers: string[]
rows: (string | number | Date)[][]
sheetName?: string
}): Buffer {
const wb = XLSX.utils.book_new()
// Convert to worksheet
const wsData = [data.headers, ...data.rows]
const ws = XLSX.utils.aoa_to_sheet(wsData)
// Set column widths
ws['!cols'] = data.headers.map(() => ({ wch: 20 }))
// Freeze top row
ws['!freeze'] = { xSplit: 0, ySplit: 1 }
XLSX.utils.book_append_sheet(wb, ws, data.sheetName ?? 'Sheet1')
return Buffer.from(XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' }))
}
// From an array of objects
export function objectsToXlsx<T extends Record<string, unknown>>(
rows: T[],
sheetName = 'Data'
): Buffer {
const wb = XLSX.utils.book_new()
const ws = XLSX.utils.json_to_sheet(rows)
XLSX.utils.book_append_sheet(wb, ws, sheetName)
return Buffer.from(XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' }))
}
NestJS Integration
// exports/exports.service.ts
@Injectable()
export class ExportsService {
constructor(private readonly ordersRepo: OrdersRepository) {}
async exportOrders(filter: OrderFilterDto): Promise<Buffer> {
const orders = await this.ordersRepo.findMany(filter)
return generateExcelReport({
columns: [
{ header: 'Order ID', key: 'id', width: 12 },
{ header: 'Customer', key: 'customer', width: 25 },
{ header: 'Date', key: 'date', width: 15, format: 'yyyy-mm-dd' },
{ header: 'Total', key: 'total', width: 15, format: '#,##0.00', align: 'right' },
{ header: 'Status', key: 'status', width: 15 },
],
rows: orders.map(o => ({
id: o.id,
customer: o.customer.name,
date: o.createdAt,
total: o.total,
status: o.status,
})),
summary: {
id: `Total: ${orders.length} orders`,
customer: '',
date: '',
total: orders.reduce((sum, o) => sum + o.total, 0),
status: '',
},
})
}
}
// exports/exports.controller.ts
@Get('orders.xlsx')
@UseGuards(JwtAuthGuard)
async exportOrders(
@Query() filter: OrderFilterDto,
@Res() res: Response,
) {
const buffer = await this.exportsService.exportOrders(filter)
const filename = `orders-${format(new Date(), 'yyyy-MM-dd')}.xlsx`
res.set({
'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'Content-Disposition': `attachment; filename="${filename}"`,
'Content-Length': buffer.length,
})
res.end(buffer)
}
Parsing Uploaded Excel Files
import ExcelJS from 'exceljs'
async function parseUploadedExcel(buffer: Buffer): Promise<Record<string, unknown>[]> {
const workbook = new ExcelJS.Workbook()
await workbook.xlsx.load(buffer)
const sheet = workbook.worksheets[0]
if (!sheet) throw new Error('No worksheets found')
const headers: string[] = []
const rows: Record<string, unknown>[] = []
sheet.eachRow((row, rowIndex) => {
if (rowIndex === 1) {
row.eachCell(cell => headers.push(String(cell.value ?? '')))
return
}
const record: Record<string, unknown> = {}
row.eachCell((cell, colIndex) => {
const header = headers[colIndex - 1]
if (header) record[header] = cell.value
})
rows.push(record)
})
return rows
}
Forbidden Patterns
- Never use synchronous file writes (
workbook.xlsx.writeFile) in a web server — always usewriteBuffer - Never pass unvalidated user data directly into cells — sanitize to prevent formula injection (
=SYSTEM()) - Never stream huge workbooks without chunking — build in batches for >100k rows
- Never trust uploaded Excel filenames — generate your own download filename
Files (antigravity-fullstack-hq)
-
SKILL.md 8.7 KB
--- name: xlsx-official description: Generating Excel files with xlsx/exceljs in Node.js. Use when generating .xlsx reports, data exports, dashboards, or spreadsheets from database data. --- # Excel Generation ## Option A — ExcelJS (Feature-Rich) ```bash npm install exceljs npm install -D @types/node ``` ```typescript // lib/excel/report.excel.ts import ExcelJS from 'exceljs' export async function generateExcelReport(data: ReportData): Promise<Buffer> { const workbook = new ExcelJS.Workbook() workbook.creator = 'Full Stack HQ' workbook.lastModifiedBy = 'system' workbook.created = new Date() workbook.modified = new Date() const sheet = workbook.addWorksheet('Report', { pageSetup: { paperSize: 9, // A4 orientation: 'landscape', fitToPage: true, }, views: [{ state: 'frozen', xSplit: 0, ySplit: 1 }], // freeze header row }) // ─── Column Definitions ─────────────────────────────────── sheet.columns = data.columns.map(col => ({ header: col.header, key: col.key, width: col.width ?? 20, style: { alignment: { horizontal: col.align ?? 'left', wrapText: false }, numFmt: col.format, // e.g. '#,##0.00', 'yyyy-mm-dd' }, })) // ─── Style the Header Row ───────────────────────────────── const headerRow = sheet.getRow(1) headerRow.eachCell(cell => { cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF2563EB' }, } cell.font = { name: 'Calibri', size: 11, bold: true, color: { argb: 'FFFFFFFF' }, } cell.border = { bottom: { style: 'medium', color: { argb: 'FF1D4ED8' } }, } }) headerRow.height = 24 // ─── Add Data Rows ──────────────────────────────────────── data.rows.forEach((row, index) => { const excelRow = sheet.addRow(row) excelRow.height = 18 // Alternating row colors if (index % 2 === 0) { excelRow.eachCell(cell => { cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF8FAFC' }, } }) } excelRow.eachCell(cell => { cell.font = { name: 'Calibri', size: 10 } cell.border = { bottom: { style: 'thin', color: { argb: 'FFE5E7EB' } }, } }) }) // ─── Summary Row ────────────────────────────────────────── const summaryRow = sheet.addRow(data.summary) summaryRow.eachCell(cell => { cell.font = { name: 'Calibri', size: 10, bold: true } cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFEFF6FF' }, } cell.border = { top: { style: 'medium', color: { argb: 'FF2563EB' } }, bottom: { style: 'medium', color: { argb: 'FF2563EB' } }, } }) // ─── Auto-filter ────────────────────────────────────────── sheet.autoFilter = { from: { row: 1, column: 1 }, to: { row: 1, column: data.columns.length }, } // ─── Return Buffer ──────────────────────────────────────── return workbook.xlsx.writeBuffer() as Promise<Buffer> } ``` ## Multiple Sheets ```typescript async function generateMultiSheetReport(report: MultiSheetReport): Promise<Buffer> { const workbook = new ExcelJS.Workbook() // Summary sheet const summarySheet = workbook.addWorksheet('Summary') addSummarySheet(summarySheet, report.summary) // Data sheets for (const section of report.sections) { const sheet = workbook.addWorksheet(section.name.substring(0, 31)) // Excel 31-char limit addDataSheet(sheet, section.columns, section.rows) } // Charts sheet (using data from other sheets) const chartSheet = workbook.addWorksheet('Charts') addChart(workbook, chartSheet, report) return workbook.xlsx.writeBuffer() as Promise<Buffer> } ``` ## Charts in ExcelJS ```typescript function addChart(workbook: ExcelJS.Workbook, sheet: ExcelJS.Worksheet, report: Report) { // ExcelJS doesn't support charts directly — use chartjs-to-image or // embed a pre-generated chart image // Generate chart as PNG buffer (using chartjs-node-canvas) const chartImage = generateChartPng(report.chartData) const imageId = workbook.addImage({ buffer: chartImage, extension: 'png', }) sheet.addImage(imageId, { tl: { col: 0, row: 0 }, ext: { width: 800, height: 400 }, editAs: 'oneCell', }) } ``` ## Option B — SheetJS (xlsx) — Simpler ```bash npm install xlsx npm install -D @types/xlsx ``` ```typescript import * as XLSX from 'xlsx' export function generateSimpleXlsx(data: { headers: string[] rows: (string | number | Date)[][] sheetName?: string }): Buffer { const wb = XLSX.utils.book_new() // Convert to worksheet const wsData = [data.headers, ...data.rows] const ws = XLSX.utils.aoa_to_sheet(wsData) // Set column widths ws['!cols'] = data.headers.map(() => ({ wch: 20 })) // Freeze top row ws['!freeze'] = { xSplit: 0, ySplit: 1 } XLSX.utils.book_append_sheet(wb, ws, data.sheetName ?? 'Sheet1') return Buffer.from(XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' })) } // From an array of objects export function objectsToXlsx<T extends Record<string, unknown>>( rows: T[], sheetName = 'Data' ): Buffer { const wb = XLSX.utils.book_new() const ws = XLSX.utils.json_to_sheet(rows) XLSX.utils.book_append_sheet(wb, ws, sheetName) return Buffer.from(XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' })) } ``` ## NestJS Integration ```typescript // exports/exports.service.ts @Injectable() export class ExportsService { constructor(private readonly ordersRepo: OrdersRepository) {} async exportOrders(filter: OrderFilterDto): Promise<Buffer> { const orders = await this.ordersRepo.findMany(filter) return generateExcelReport({ columns: [ { header: 'Order ID', key: 'id', width: 12 }, { header: 'Customer', key: 'customer', width: 25 }, { header: 'Date', key: 'date', width: 15, format: 'yyyy-mm-dd' }, { header: 'Total', key: 'total', width: 15, format: '#,##0.00', align: 'right' }, { header: 'Status', key: 'status', width: 15 }, ], rows: orders.map(o => ({ id: o.id, customer: o.customer.name, date: o.createdAt, total: o.total, status: o.status, })), summary: { id: `Total: ${orders.length} orders`, customer: '', date: '', total: orders.reduce((sum, o) => sum + o.total, 0), status: '', }, }) } } // exports/exports.controller.ts @Get('orders.xlsx') @UseGuards(JwtAuthGuard) async exportOrders( @Query() filter: OrderFilterDto, @Res() res: Response, ) { const buffer = await this.exportsService.exportOrders(filter) const filename = `orders-${format(new Date(), 'yyyy-MM-dd')}.xlsx` res.set({ 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'Content-Disposition': `attachment; filename="${filename}"`, 'Content-Length': buffer.length, }) res.end(buffer) } ``` ## Parsing Uploaded Excel Files ```typescript import ExcelJS from 'exceljs' async function parseUploadedExcel(buffer: Buffer): Promise<Record<string, unknown>[]> { const workbook = new ExcelJS.Workbook() await workbook.xlsx.load(buffer) const sheet = workbook.worksheets[0] if (!sheet) throw new Error('No worksheets found') const headers: string[] = [] const rows: Record<string, unknown>[] = [] sheet.eachRow((row, rowIndex) => { if (rowIndex === 1) { row.eachCell(cell => headers.push(String(cell.value ?? ''))) return } const record: Record<string, unknown> = {} row.eachCell((cell, colIndex) => { const header = headers[colIndex - 1] if (header) record[header] = cell.value }) rows.push(record) }) return rows } ``` ## Forbidden Patterns - Never use synchronous file writes (`workbook.xlsx.writeFile`) in a web server — always use `writeBuffer` - Never pass unvalidated user data directly into cells — sanitize to prevent formula injection (`=SYSTEM()`) - Never stream huge workbooks without chunking — build in batches for >100k rows - Never trust uploaded Excel filenames — generate your own download filename
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.