xllify produces two kinds of add-in from the same function code. One is a native XLL - a DLL that loads directly into Excel’s process on Windows. The other is an Office Add-in that runs in a browser-based sandbox and works on Windows, macOS, and Excel for the web. The Luau you write is identical in both cases.
The XLL runtime
An XLL is a PE file (a DLL by another extension). It loads into Excel’s process, registers functions via the C SDK, and gets called directly on the calculation thread. This comes with a trust model worth understanding - see XLL security: what you need to know. There is no bridge, no serialisation, no process boundary. Arguments come in as XLOPER12 values and results go back the same way. It is fast because it is close to the metal, and supports multi-threaded recalculation.
The Luau runtime inside the XLL is a C++ VM compiled into the binary. When Excel calls a function, the XLL looks up the corresponding Luau function, pushes arguments onto the Lua stack, runs the VM, and pulls the result back out. The whole thing happens in-process.
The Office Add-in runtime
An Office Add-in is a web application. Custom functions run inside a JavaScript engine in a sandboxed iframe and communicate with Excel over an async bridge via the Office.js API. The function has to return a Promise, Excel polls for the result, and the call crosses a process boundary every time.
xllify provides implements a browser runtime by compiling the Luau VM to WebAssembly. The same VM that runs natively in the XLL runs inside the browser as a WASM module. Your Luau bytecode is bundled alongside it. When Excel calls a function, JavaScript calls into WASM, the Luau VM executes, and the result comes back.
What stays the same
The Luau source is compiled to bytecode once. That bytecode is what gets embedded in both outputs - bundled into the XLL for the native build, and bundled as a binary file for the Office Add-in. The VM executes it identically in both cases. As a side benefit, your source code is never shipped - bytecode is not easily read or reconstructed, so your logic stays yours.
The xllify.ExcelFunction API, parameter types, and standard library are the same across both targets. If a function works in one it works in the other.
The xllify runtime also exposes a set of built-in functions with optimised C++ implementations - things like Levenshtein distance, JSON parsing, and string utilities - available to your Luau code on both runtimes. In practice, because Luau itself is fast, the gains over a pure Luau implementation can be marginal. But for functions called across large ranges in a multi-threaded recalc, they add up.
To build each target separately:
xllify build xll -o myaddin.xll functions/myfuncs.luau
xllify build officejs -o myaddin.zip functions/myfuncs.luau
The two build targets are separate commands, but the source is the same. Both commands run on Mac, Linux, and Windows. Building an XLL from a Mac or Linux machine was traditionally not possible without a Windows toolchain - the xllify CLI handles the compilation server-side, so the platform you develop on no longer matters. Fully on-premises tooling is available by arrangement, and still runs on Mac or Linux - the same cross-platform packaging applies. Either way, builds are fast: a typical XLL takes about a second.