Write Zig or Lua functions and use them in Excel. Stream data from any source. Build on Windows, Mac or Linux, deploy to Windows.
Write a normal Zig or Lua function. Declare 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. Implement a handler; the framework takes care of COM, vtables, registration, and lifecycle.
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.
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.1.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 running inside the Excel process. The C SDK is from the early 1990s. A hostile beast, zigxll makes it approachable.
Zig consumes C headers directly. No bindings or code generation step. Minimal overhead at runtime.
All the boilerplate (exports, type conversions, registration, COM vtables) is generated at compile time from your function definitions. This means 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 any platform.
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.
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.
Full 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. ~100KB binary.
Clone the template, write your functions, run zig build, load in Excel.