xl3 — a standard for declarative Excel transformation
Jinja made HTML executable as templates; xl3 makes Excel workbooks executable as templates — as an open, implementation-independent standard, not a single library.
Status: alpha · XTL spec 0.1 (draft) · reference implementation
@xl3-lang/xl3 0.9.0 · breaking changes possible until 1.0
xl3 is an open standard for turning an ordinary .xlsx workbook into a
deterministic, declarative transformation template: the layout, styles,
merged cells, and rules live inside the workbook, and any conforming
engine executes it against supplied data — same inputs, same output, every
time. This repository defines the standard, in three parts:
- Spec — the normative definition, including XTL, the small embedded expression language for what must be decided before the workbook is written (filters, groups, aggregates, filename patterns), plus the design record (ADRs).
- Conformance suite — language-neutral fixtures that every implementation runs to prove it conforms.
- Reference implementation —
@xl3-lang/xl3(TypeScript, inimpl/js/) — one of several implementations (Rust/WASM and Python in progress).
Three names, one stack: xl3 is the standard (this format) · XTL
is its embedded expression language · @xl3-lang/xl3 is the
TypeScript reference implementation of it.
It fits when recurring Excel documents — invoices, settlement statements, monthly reports, financial workbooks — must stay editable in Excel while execution remains deterministic, inspectable, and verifiable. AI authoring is a strong fit too: an LLM can emit a compact template contract more reliably than hundreds of lines of workbook-API code.
English · 한국어 · 日本語 · 简体中文 · Website · Spec · LLM authoring guide · Implementations · Roadmap · Governance
The split: Excel is the template, xl3 executes it
┌──────────────────────────┐
│ Business user / designer│
│ edits layout in Excel │
└─────────────┬────────────┘
│ template.xlsx
▼
┌──────────────────────────┐ ┌──────────────────────────┐
│ Developer app │ │ xl3 │
│ supplies data + inputs ├──────►│ executes the template │
└──────────────────────────┘ └─────────────┬────────────┘
│
▼
result.xlsx
Workbook APIs are the DOM APIs of spreadsheets: powerful, but verbose. For recurring documents, the workbook itself should be the view and the template contract. xl3 lets the application supply data while Excel keeps the layout and business-facing rules.
This split is what docs/llm-template-authoring.md,
the 160-fixture conformance corpus, and the intentionally small XTL
surface are designed for.
Quick example
A template can contain ordinary Excel content, __config__, and xl3 expressions:
__config__ key | Value |
|---|---|
source_sheet | Raw |
source_table | 1 |
output_file_pattern | customer-renewal-report.xlsx |
| Cell | Template value |
|---|---|
| A5 | {{ [Account] }} |
| B5 | {{ [Region] }} |
| C5 | {{ [Renewal] }} |
| E5 | {{ IF([Renewal] > 10000, "Priority", "Standard") }} |
Given this data workbook:
| Account | Region | Renewal | Owner |
|---|---|---|---|
| Acme Logistics | Seoul | 18400 | Mina |
| Beta Works | Busan | 7200 | Joon |
xl3 renders:
| Account | Region | Renewal | Owner | Tier |
|---|---|---|---|---|
| Acme Logistics | Seoul | 18400 | Mina | Priority |
| Beta Works | Busan | 7200 | Joon | Standard |
…with the template's number formats, fills, borders, merged headers, and
footer rows preserved verbatim. The output is an .xlsx you can open in
Excel, Numbers, or Google Sheets without conversion.
See spec/ for the language draft and conformance/ for the implementation-neutral fixture corpus and runner protocol.
Why Excel should be the template
The pitch in one paragraph: Excel is already the place where report layout lives. Moving that layout into code makes every row insertion, style tweak, merge change, or subtotal rule a deployment. xl3 keeps the layout in Excel and makes the workbook executable: the application owns data and deployment; the template owns the recurring document contract.
Concretely:
- A small, auditable XTL surface (ADR-0043). A function lives in XTL only when its value must be known before the workbook is written. Everything else is a normal Excel cell formula and Excel evaluates it at open time. The smaller the language, the easier it is for humans to review — and for AI systems to draft. See Cookbook 16 for the side-by-side guide.
- Conformance corpus. 160 fixtures, all green, across 74 ADRs. This is the test bed for the transformation contract.
- One implementation, one spec. The
spec/directory defines XTL independently of this TypeScript reference. Ports to other runtimes are welcome; the corpus is the contract. - No macros, no vendor cloud. A template is an ordinary
.xlsx. You can diff it, review it in a pull request, and hand it to a human reviewer who has never heard of xl3.
The same properties make xl3 useful even without an LLM in the loop:
operators and analysts can read and edit templates directly, because
expressions are written with the same IF, SUM, and column references
they already use day to day. AI friendliness is a consequence of the
same design choice: small, declarative, reviewable rules.
Responsibility-driven document automation
Most Excel automation tools improve developer productivity. xl3 aims at a different outcome: moving recurring document changes out of the developer-only queue.
In the usual model, every layout change, column addition, repeat rule, or output-format tweak becomes a development request and a deployment. xl3 separates those responsibilities:
Developer -> Runtime maintenance
Operator -> Template maintenance
Business -> Result usage
The developer keeps the engine, validation, integration, and deployment reliable. The operator edits the Excel template, including document-level transformation rules. The business consumes the generated workbook.
This is not only a theory. 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 code volume; it was the amount of work that had to be done by developers.
That is why XTL keeps Excel syntax wherever possible. The intended experience is not "learn a new programming language", but "use Excel a little more powerfully." xl3 is not a project to replace developers: developers own the runtime, operators own the templates, and document automation becomes an organizational capability.
How it compares
| Approach | Best at | Tradeoff |
|---|---|---|
| xl3 | Declarative Excel template execution. The workbook already exists; xl3 runs it with data. | Alpha; one maintainer; the XTL surface is intentionally small and still evolving until 1.0. |
| Workbook APIs (ExcelJS / SheetJS / openpyxl / Apache POI) | Low-level or full-featured workbook generation from code. | Layout, styles, merges, loops, and business rules become application 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 / Power Automate | Microsoft 365 workflows, data shaping, and action automation inside the Excel ecosystem. | Tenant-bound; the workflow rules don't travel with the workbook. |
| JXLS / xltpl / jsreport xlsx recipe | 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. |
| Document-generation 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. |
Install
npm install @xl3-lang/xl3
Optional acceleration (rc):
npm install @xl3-lang/xl3@rc xl3-wasm
Usage
import { convert } from '@xl3-lang/xl3';
const templateBuffer = await fetch('./template.xlsx').then((r) => r.arrayBuffer());
const dataBuffer = await fetch('./data.xlsx').then((r) => r.arrayBuffer());
const outputs = await convert(templateBuffer, dataBuffer);
// outputs: OutputFile[] — one or more .xlsx, depending on grouping rules in the template
Runs in browsers and Node (≥20.12).
Acceleration (opt-in)
convert and preview accept an engine option that selects the
rendering backend:
await convert(templateBuffer, dataBuffer, { engine: 'auto' });
// 'auto' (default): try xl3-wasm if installed, fall back to JS silently
// 'wasm' : require xl3-wasm; throw if missing or unsupported feature
// 'js' : force the ExcelJS path
Install xl3-wasm alongside
xl3 to activate the auto path; the JS engine remains the canonical
reference, and the wasm engine falls back to it for any template it
can't yet handle. Available in 0.9.0-rc.1 onwards.
Browser via <script> (no bundler)
For projects that don't use a bundler, a self-contained IIFE bundle
exposes window.xl3:
<script src="https://cdn.jsdelivr.net/npm/@xl3-lang/xl3@0.8.0/dist/xl3.bundle.iife.min.js"></script>
<script>
const tpl = await fetch('./template.xlsx').then((r) => r.arrayBuffer());
const data = await fetch('./data.xlsx').then((r) => r.arrayBuffer());
const outputs = await xl3.convert(tpl, data);
</script>
Bundle is ~1 MB minified (~300 KB gzipped). ExcelJS + JSZip are inlined; no other dependencies are needed.
You can try the browser flow on xl3.io: run the attached sample files as-is, download the raw/template workbooks, or replace either file with your own.