Write Zig or Lua functions and use them in Excel. Stream data from any source. Build and test on Windows, Mac or Linux, then deploy to Windows.
Write a function. Register it with ExcelFunction(). The framework handles everything else: type conversions, UTF-16, error mapping, Excel registration.
#VALUE! in Excel.is_async = trueconst xll = @import("xll"); pub const add = xll.ExcelFunction(.{ .name = "add", .description = "Add two numbers", .category = "My Functions", .params = &[_]xll.ParamMeta{ .{ .name = "a", .description = "First number" }, .{ .name = "b", .description = "Second number" }, }, .func = addImpl, }); fn addImpl(a: f64, b: f64) !f64 { return a + b; }
--- Calculate hypotenuse -- @param a number Side a -- @param b number Side b function hypotenuse(a, b) return math.sqrt(a * a + b * b) end --- Fibonacci with async support -- @param n number Index -- @async function slow_fib(n) local a, b = 0, 1 for i = 1, n do a, b = b, a + b end return a end
Not everything needs to be Zig. For many operations, Lua is not meaningfully slower than Zig. Annotate standard Lua functions and the framework generates Excel UDFs from them automatically. Scripts are embedded in the .xll at build time.
The Lua runtime is sandboxed (no filesystem, no shell access) and thread-safe by default. A pool of independent Lua states allows parallel recalculation with no contention.
@param, @async, @categoryxll.get/xll.setThe NATS connector turns Excel into a reactive computation engine. Subscribe to message streams with a formula. Derive cross-rates, calculate rolling statistics, publish alerts back. All in the spreadsheet.
The NATS.SUBWIN() function provides windowed buffers for time-series analysis directly in cells.
The NATS connector above is built on zigxll's RTD server framework. You can build your own to consume any source, such as a websocket.
The counter example in the repo shows how little code it takes. Implement a handler struct and the framework generates the COM server, handles registration, and manages the Excel callback lifecycle.
ctx.notifyExcel()=RTD("zigxll.rtd",,"")const TimerHandler = struct { counter: i32 = 0, timer_thread: ?std.Thread = null, running: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), pub fn onStart(self: *TimerHandler, ctx: *rtd.RtdContext) void { self.running.store(true, .release); self.timer_thread = std.Thread.spawn( .{}, timerProc, .{ self, ctx }, ) catch return; } pub fn onRefreshValue(self: *TimerHandler, _: *rtd.RtdContext, _: i32) rtd.RtdValue { return .{ .int = self.counter }; } fn timerProc(self: *TimerHandler, ctx: *rtd.RtdContext) void { while (self.running.load(.acquire)) { std.Thread.sleep(2 * std.time.ns_per_s); self.counter += 1; ctx.markAllDirty(); ctx.notifyExcel(); } } };
pub const slow_calc = xll.ExcelFunction(.{ .name = "SlowCalc", .description = "Heavy computation", .is_async = true, .params = &[_]xll.ParamMeta{ .{ .name = "n" }, }, .func = slowCalcImpl, }); fn slowCalcImpl(n: f64) !f64 { // Runs on a background thread. // Cell shows #N/A, then updates // when complete. return expensive_work(n); }
Add .is_async = true to any function definition. This make it run on a background thread pool. The cell shows #N/A while computing, then updates with the result automatically.
Results are cached, keyed by function arguments, so subsequent recalculations return instantly. Works for both Zig and Lua functions. Useful for slow computations, network calls, or anything you don't want blocking Excel's main thread.
Prefer to read real code first? The example project is a complete, working XLL you can clone, build and load into Excel.
Use the GitHub template to scaffold a new repo with everything wired up, or add zigxll as a dependency in an existing project's build.zig.zon:
.xll = .{ .url = "https://github.com/alexjreid/zigxll/archive/refs/tags/v0.3.25.tar.gz", .hash = "..." }
Write Zig functions, declare them with ExcelFunction(). List your modules in main.zig:
pub const function_modules = .{ @import("my_functions.zig") };
Cross-compiles to a Windows .xll from any platform. Tests run natively.
zig build # produces zig-out/lib/my_functions.xll
zig build test # runs tests natively, no Windows SDK needed
Double-click zig-out/lib/my_functions.xll to load in Excel.
XLL add-ins are native DLLs for Excel on Windows that run inside the Excel process. The C SDK is from the early 1990s. It's a hostile beast, zigxll makes it approachable.
All the boilerplate (exports, type conversions, registration, COM vtables) is generated at compile time from your function definitions. This equals high performance at runtime.
A single .xll file, a few hundred KB.
Build Windows .xll files from macOS or Linux. No Windows SDK, no Visual Studio. Tests run natively on all platforms.
Excel can parallelise function calls across cores during recalculation. ZigXLL functions are thread-safe by default (MTR).
Arena allocators and explicit lifetime management. No GC pauses, no hidden allocations == predictable performance and stability.
The Excel C SDK yields the highest performance add-ins, but its verbosity makes it unfeasible for most users. zigxll gives you the same raw speed without the pain.
The motivation behind zigxll. Why Zig, how comptime eliminates boilerplate, and real-world implementation details.
Using zigxll's NATS connector to turn Excel into a reactive stream processor for FX monitoring.
Docs covering function definitions, real-time data servers, embedded Lua scripting, and how it all works.
A full example project you can clone, build, and load into Excel.
Start a new project with the template. Everything set up and ready to go.
Stream NATS messages into Excel as live data. Subscribe, derive, publish back. ~500KB binary.
Clone the template, write your functions, run zig build, load in Excel.