MIT Licensed · v0.3.1

Native Excel add-ins,
written in Zig

Write normal Zig or Lua functions. zigxll generates all the Excel boilerplate at compile time: exports, type conversions, registration, COM vtables. You can even develop on a Mac or Linux.

Get started View on GitHub

Why Zig for Excel add-ins?

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.

Direct C interop

Zig consumes C headers directly. No bindings or code generation step. Minimal overhead at runtime.

Comptime code generation

All the boilerplate (exports, type conversions, registration, COM vtables) is generated at compile time from your function definitions. This means high performance at runtime.

📦

Small, self-contained binaries

A single .xll file, a few hundred KB.

🌐

Cross-compile from anywhere

Build Windows .xll files from macOS or Linux. No Windows SDK, no Visual Studio. Tests run natively on any platform.

🧵

Multi-threaded by default

Excel can parallelise function calls across cores during recalculation. ZigXLL functions are thread-safe by default (MTR).

🛡

Explicit memory control

Arena allocators and explicit lifetime management. No GC pauses, no hidden allocations, predictable performance.

Define a function, get an Excel UDF

Write a normal Zig or Lua function. Declare it with ExcelFunction(). The framework handles everything else: type conversions, UTF-16, error mapping, Excel registration.

  • Zig types convert to XLOPER12 automatically
  • Zig errors become #VALUE! in Excel
  • UTF-8 strings handled natively, framework converts to UTF-16
  • Async support with .is_async = true
  • Return arrays, strings, errors, or scalars
my_functions.zig
const 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;
}
Live data via RTD
A
B
C
1
Pair
Formula
Value
2
EURUSD
=NATS.SUB("fx.EURUSD")
1.0842
3
GBPUSD
=NATS.SUB("fx.GBPUSD")
1.2631
4
USDJPY
=NATS.SUB("fx.USDJPY")
149.82
5
EURGBP
=B2/B3
0.8584

Stream live data into Excel

ZigXLL includes a pure Zig RTD server framework for building your own real-time data sources. Implement a handler; the framework takes care of COM, vtables, registration, and lifecycle.

One implementation built on this is the NATS connector, which 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.

Learn more about the NATS connector →

Write Excel functions in Lua

Not everything needs to be Zig. For many operations, Lua is as fast as 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.

  • Annotate with @param, @async, @category
  • Automatic type marshalling between Excel and Lua
  • Async Lua functions run on worker threads with result caching
  • Shared state across pool states via xll.get/xll.set
  • Mix Lua and Zig functions in the same add-in
functions.lua
--- 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

Async functions

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.

async_example.zig
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);
}

Get started in minutes

1

Create a new project from the template

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 = "..." }
2

Define your functions

Write Zig functions, declare them with ExcelFunction(). List your modules in main.zig:

pub const function_modules = .{ @import("my_functions.zig") };
3

Build

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
4

Load

Double-click zig-out/lib/my_functions.xll to load in Excel.

Learn more

Blog post

Building Excel XLL Add-ins in Zig

The motivation behind zigxll. Why Zig, how comptime eliminates boilerplate, and real-world implementation details.

Blog post

Excel: The Accidental Stream Processor

Using zigxll's NATS connector to turn Excel into a reactive stream processor for FX monitoring.

Documentation

Functions, RTD, Lua, Architecture

Full docs covering function definitions, real-time data servers, embedded Lua scripting, and how it all works.

Example

Complete Working Project

A full example project you can clone, build, and load into Excel.

Template

Standalone Template Repo

Start a new project with the template. Everything set up and ready to go.

Connector

NATS Pub/Sub for Excel

Stream NATS messages into Excel as live data. Subscribe, derive, publish back. ~100KB binary.

Build your first XLL

Clone the template, write your functions, run zig build, load in Excel.

Use the template Read the source