ExcelJS で .xlsx をブラウザで生成・ダウンロードする
ExcelJS でブラウザ完結に Excel ファイルを作成し、ヘッダ装飾 / 数式 / 通貨書式 / 集計行を最小コードで組み立てるパターン。ARGB の alpha 桁、writeBuffer の Browser/Node 差、numFmt の番号書式コード、mergeCells 後の値設定が無視される罠まで実装メモ。
検証日: 2026-05-26
使用バージョン:
exceljs@4.4.0(2026-05 時点でlatest)対象: ブラウザでユーザに
.xlsxをダウンロードさせたい / Node 上で帳票生成したい場面
ExcelJS で .xlsx をブラウザ完結で生成・ダウンロード するパターン。書式付きセル / 数式 / 複数シート / 画像埋め込み / Node 出力までを動く demo で確認します。
触って試す
ボタンを押すと、装飾ヘッダ + 数式 + 通貨書式付きの xlsx が生成されてダウンロードされる。
セットアップ(npm 1 行 / Node + Browser 同 API)
npm i exceljs
| 項目 | 値 |
|---|---|
| 公開バージョン | 4.4.0 |
| Node 必要バージョン | 18+ |
| TypeScript 型 | 本体同梱(別途 @types/exceljs は不要、deprecated) |
| ブラウザ対応 | UMD bundle 同梱(exceljs/dist/exceljs.min.js)、ESM でも import 可 |
| バンドル影響 | min+gzip で 200KB 前後(フォントサブセット内部リソースあり)、main bundle に同梱せず動的 import 推奨 |
| ライセンス | MIT |
ブラウザで使うなら import ExcelJS from "exceljs"。Vite / webpack で警告が出る場合は optimizeDeps.include に exceljs を入れる。Cloudflare Workers のような Web API しかない実行環境 では fs 依存の writeFile は使えないので、writeBuffer() + Response で配信する形になる(後述)。
最小サンプル(ExcelJS 4.4、ブラウザで 5 ステップ)
ワークブックを 1 つ作って、ヘッダ + 行を流し込み、Blob 化して .xlsx をダウンロードするまでの最小コード:
import ExcelJS from "exceljs";
const wb = new ExcelJS.Workbook();
const sheet = wb.addWorksheet("Report");
sheet.columns = [
{ header: "ID", key: "id", width: 8 },
{ header: "Name", key: "name", width: 24 },
{ header: "Total", key: "total", width: 12 },
];
sheet.addRow({ id: 1, name: "Alice", total: 1500 });
sheet.addRow({ id: 2, name: "Bob", total: 1100 });
const buf = await wb.xlsx.writeBuffer();
const blob = new Blob([buf], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "report.xlsx";
a.click();
URL.revokeObjectURL(url);
writeBuffer() で ArrayBuffer を取得 → Blob でダウンロード。
装飾 2 種(ヘッダ太字 + 背景色 ARGB)
ヘッダ行の font を太字に、塗りつぶしを pattern: "solid" で指定:
sheet.getRow(1).font = { bold: true };
sheet.getRow(1).fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFE2E8F0" }, // 8 桁 ARGB(先頭は alpha = FF)
};
ARGB の先頭 2 桁は alpha(FF = 不透明)。HTML の hex とは桁数が違うので注意。
数式 + 集計(SUM の 2 パターン)
セル値の代わりに { formula: "..." } を渡すと、Excel で開いた時に計算式として扱われる:
sheet.addRow({
id: 1, qty: 5, price: 200,
total: { formula: "C2*D2" }, // セル参照
});
sheet.addRow({
id: "", qty: { formula: "SUM(C2:C10)" }, price: "",
total: { formula: "SUM(E2:E10)" },
});
{ formula: "..." } で数式を埋める。Excel が開いた時に値が自動計算される。
数値書式 3 種(通貨 / パーセント / 日付)
列単位で numFmt(Excel の番号書式コード)を当てると、各セルの表示が一括で揃う:
sheet.getColumn("price").numFmt = '"¥"#,##0';
sheet.getColumn("rate").numFmt = "0.00%";
sheet.getColumn("date").numFmt = "yyyy/mm/dd";
Excel の番号書式コードがそのまま使える。
罫線 4 辺 + mergeCells
全セルに罫線を引いて、ヘッダ行は mergeCells で 1 行ぶん結合:
// すべてのセルに罫線
sheet.eachRow((row) => {
row.eachCell((cell) => {
cell.border = {
top: { style: "thin" }, bottom: { style: "thin" },
left: { style: "thin" }, right: { style: "thin" },
};
});
});
// セルマージ
sheet.mergeCells("A1:C1");
CSV ↔ xlsx の 2 方向変換
wb.csv / wb.xlsx でフォーマット間を相互変換できる(Node 想定、stream API):
// CSV を xlsx に
const wb = new ExcelJS.Workbook();
await wb.csv.read(stream); // Node の Readable Stream
await wb.xlsx.writeFile("out.xlsx");
// xlsx を CSV に
await wb.xlsx.readFile("in.xlsx");
await wb.csv.writeFile("out.csv");
Cloudflare Workers / Edge で 1 endpoint .xlsx 配信
exceljs の writeBuffer() は Buffer | ArrayBuffer を返すので、Node fs を経由せず Response にそのまま流せる:
export async function onRequestGet() {
const ExcelJS = await import("exceljs");
const wb = new ExcelJS.Workbook();
const sheet = wb.addWorksheet("Report");
sheet.addRow(["id", "name"]);
sheet.addRow([1, "Alice"]);
// Node では Buffer、ブラウザ / Workers では ArrayBuffer
const buf = await wb.xlsx.writeBuffer();
return new Response(buf, {
headers: {
"Content-Type":
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"Content-Disposition": 'attachment; filename="report.xlsx"',
},
});
}
Workers のサイズ制限(1MB scripts、25MB free / 100MB paid)を超えないように、exceljs は dynamic import で route 単位にロードするのが安全。fonts / 画像など重い機能を使わなければ実用範囲に収まる。
7 つの落とし穴と直し方
| 症状 | 原因 | 直し方 |
|---|---|---|
| 塗りが反映されない | ARGB の 先頭 2 桁が alpha | HTML hex E2E8F0 の前に FF を付けて FFE2E8F0 にする |
| 配列 index で行を指定すると ずれる | Cell reference は 1-based(C2 = 2 行目) | sheet.getRow(1) がヘッダ、getRow(2) が 1 行目データ |
writeBuffer() の戻り値が型エラー | 環境で Buffer (Node) / ArrayBuffer (Browser) が違う | Blob([buf]) は両方受け取るので Blob 化してから扱う |
| 数式の参照がズレる | addRow で動的に行が増えると数式の絶対セル参照も追従しない | formula を文字列補完で組み立てる(sheet.rowCount で最終行を取って SUM(C2:C + n + ) のように生成) |
| mergeCells 後に値を入れたのに表示されない | マージ後は 左上セル以外の値が無視される | mergeCells する前に値を入れるか、左上セル getCell("A1") に書く |
通貨が ¥1,500.00 になってしまう | numFmt のコード末尾に小数指定 | '"¥"#,##0'(整数)/ '"¥"#,##0.00'(小数 2 桁)を使い分け |
| 10 万行で OOM | workbook を全部 memory に保持 | wb.xlsx.write(stream) で書き出し(Node)、または xlsx-populate / node-xlsx 等の軽量代替 |
評価
| 観点 | 評価 | コメント |
|---|---|---|
| 学習コスト | ○ | API は分かりやすいが、書式コード仕様(numFmt)に Excel 知識必要 |
| 機能網羅 | ◎ | 装飾 / 数式 / グラフ / 画像 / mergeCells すべて対応 |
| ブラウザ対応 | ◎ | Node / browser 同 API |
| バンドル | △ | 200KB 前後、フォントサブセットなどの内部リソース大きい |
| TypeScript | ○ | 型同梱、ただし一部 any |
向く / 向かないケース
- 向く: 帳票出力、レポート PDF と並ぶ業務系出力、ユーザダウンロード機能
- 向かない: シンプルな CSV 出力(Papa.unparse のほうが軽い)
- 向かない: 100 万行超(streaming write or 別ツール)
関連 Topic / 関連書籍
この記事と関係する tech-book.net の Topic と、それぞれの Topic に紐づく書籍:
JavaScriptによるはじめてのアルゴリズム入門 | tech-book.net
関連テーマ・学習ロードマップから次の一冊へ。
Python と JavaScriptではじめるデータビジュアライゼーション | tech-book.net
関連テーマ・学習ロードマップから次の一冊へ。
React Native+Expoではじめるスマホアプリ開発 : JavaScriptによるアプリ構築の実際 | tech-book.net
関連テーマ・学習ロードマップから次の一冊へ。
はじめてのWebデザイン&プログラミング : HTML、CSS、JavaScript、PHPの基本 | tech-book.net
関連テーマ・学習ロードマップから次の一冊へ。
フロントエンドの知識地図ーー 一冊でHTML/CSS/JavaScriptの開発技術が学べる本 | tech-book.net
関連テーマ・学習ロードマップから次の一冊へ。