Fresh data for each run
| A | B | C | D | |
|---|---|---|---|---|
| 1 | ||||
| 2 | ||||
| 3 | ||||
| 4 | ||||
| 5 | ||||
| 6 | ||||
| 7 | ||||
| 8 |
Pass the latest customers, amounts, and owners as .xlsx or JSON. This is the input data before the template is applied.
Excel template engine for recurring business documents
Edit the transformation logic inside the Excel file — no developer ticket. Supply the data and xl3 executes it: styles preserved, same result every time.
How it works
The business user edits layout in Excel; the application supplies data and inputs; xl3 executes the workbook deterministically. Follow the three workbooks side by side, from raw data to finished output.
| A | B | C | D | |
|---|---|---|---|---|
| 1 | ||||
| 2 | ||||
| 3 | ||||
| 4 | ||||
| 5 | ||||
| 6 | ||||
| 7 | ||||
| 8 |
Pass the latest customers, amounts, and owners as .xlsx or JSON. This is the input data before the template is applied.
| A | B | C | D | E | |
|---|---|---|---|---|---|
| 1 | Customer Renewal Report | ||||
| 2 | |||||
| 3 | |||||
| 4 | |||||
| 5 | |||||
| 6 | |||||
| 7 | |||||
| 8 | |||||
Style the title, fonts, borders, and merged cells in Excel. Add XTL {{ … }} cells where data belongs.
| A | B | C | D | E | |
|---|---|---|---|---|---|
| 1 | Customer Renewal Report | ||||
| 2 | |||||
| 3 | |||||
| 4 | |||||
| 5 | |||||
| 6 | |||||
| 7 | |||||
| 8 | |||||
The data changes; the template’s title fill, fonts, merged cells, borders, and number formats stay the same.
Responsibility-driven automation
Most Excel automation tools make developers faster. xl3 aims at a different outcome: letting operators take over the document changes that used to require a developer. When every business partner needs its own format, having one engineer implement each one — and re-edit it on every change — is the bottleneck.
xl3 was shaped by an internal service where, over months of operation, non-developers maintained templates and conversion rules directly in Excel while developers focused almost entirely on the runtime. The important reduction was not just lines of code — it was the amount of work that had to be done by developers at all.
Owns the runtime: engine behavior, validation, integration, deployment, and reliability.
runtimeOwns the template: layout, columns, repeat rules, output format, and document-specific logic — edited directly in Excel.
template.xlsxUses the finished workbook without waiting for every recurring document change to become a release.
result.xlsxxl3 is not trying to replace developers. Developers own the runtime; operators own the templates; document automation becomes an organizational capability instead of a developer-only task.
Why Excel is the template
ExcelJS, SheetJS, openpyxl, and Apache POI are the DOM APIs of spreadsheets: powerful, but verbose. When report layout, styles, merged cells, and loops live in code, every design change — a new column, a moved subtotal, a reformatted header — becomes a deployment.
xl3 moves the recurring contract back into Excel. The workbook is already the view; XTL makes it executable, while the application only supplies data and runs the engine. A template is an ordinary .xlsx — no macros, no vendor cloud — so you can diff it, review it in a pull request, and hand it to someone who has never heard of xl3.
An open standard, not a library
xl3 is defined as an open, implementation-independent standard in three parts. The XTL surface stays small on purpose, so templates remain readable by humans and easy for AI systems to draft.
The normative definition: the xl3 workbook format and XTL, its small embedded expression language. A function lives in XTL only when its value must be known before the workbook is written (ADR-0043).
spec/Language-neutral fixtures that every implementation runs to prove it conforms. The corpus — not any single implementation — is the contract a port must match.
conformance/@xl3-lang/xl3 (TypeScript) runs in browsers and Node. It is useful, but not normative — Rust/WASM and Python ports are in progress.
@xl3-lang/xl3XTL 0.1 ships with 79 ADRs and 171 conformance fixtures, all green at Stage 2. The TypeScript reference implementation is published at @xl3-lang/xl3 — the Porter's Guide documents the contract so a second-language port can match it.
How it compares
| Approach | Best at | Tradeoff |
|---|---|---|
| xl3 | Declarative Excel template execution. The workbook already exists; xl3 runs it with data. | Release candidate; one maintainer; the 1.0 contract is in technical soak through 2026-11-28. |
| Workbook APIs (ExcelJS, SheetJS, openpyxl, POI) | Low-level or full-featured workbook generation from application code. | Layout, styles, merges, loops, and business rules become code. Non-developers cannot safely edit the template. |
| Python / VBA scripts | Fast one-off automation close to existing spreadsheets. | Rules live in code or one maintainer's memory; layout changes still need code changes. |
| Power Query / Office Scripts | Microsoft 365 workflows and data shaping inside the Excel ecosystem. | Tenant-bound; the workflow rules do not travel with the workbook. |
| Template engines (JXLS, xltpl, jsreport xlsx) | Server-side report generation from spreadsheet-like templates. | Useful prior art, but often tied to one runtime and not positioned as a small, portable Excel rule format. |
| Doc-gen SaaS (Plumsail, Conga, Formstack) | Managed document workflows, integrations, approvals, and delivery. | Rules live in a vendor service, not in a portable workbook template you can review and run yourself. |
| Direct LLM → xlsx | Quick exploratory drafting, one-off charts. | Not a deterministic transformation contract for recurring operations; styles and totals drift between runs. |
Developer API
Pass data your application already has in memory, apply an Excel template, and receive completed .xlsx workbooks. convertJson() accepts a JavaScript object directly, so no source data file or workbook round-trip is required.
$npm install @xl3-lang/xl3import { convertJson } from '@xl3-lang/xl3';
const outputs = await convertJson(templateBuffer, {
version: 'xl3-source-json/0.1',
sources: { default: {
headers: ['Customer', 'Amount'],
rows: orders.map(o => [o.customer, o.amount]),
} },
});
// OutputFile[].data → .xlsx Uint8ArrayCookbook 01 — Getting started in 5 minutes · Read the spec · Porter's Guide