C API #
The WebUI FFI (Foreign Function Interface) handler exposes the rendering pipeline as a C-compatible shared library. Any language with C interop, Go, C#, Python, Ruby, PHP, Lua, and more, can load the library and render WebUI templates without a JavaScript runtime.
Building the Shared Library #
cargo build -p webui-ffi # debug
cargo build -p webui-ffi --release # release
This produces a shared library:
| Platform | Library file |
|---|---|
| macOS | target/release/libwebui_ffi.dylib |
| Linux | target/release/libwebui_ffi.so |
| Windows | target/release/webui_ffi.dll |
The generated C header is at crates/webui-ffi/include/webui_ffi.h.
Two Rendering Modes #
One-shot: #
Parse and render in a single call. Best for simple use cases where you pass raw HTML templates.
char *html = webui_render(
"<h1>{{title}}</h1><ul><for each=\"item in items\"><li>{{item}}</li></for></ul>",
"{\"title\": \"Groceries\", \"items\": [\"Milk\", \"Eggs\"]}"
);
if (html == NULL) {
printf("Error: %s\n", webui_last_error());
} else {
printf("%s\n", html);
webui_free(html);
}
Pre-compiled: + #
Create a reusable handler and render pre-compiled protobuf protocols. Best for production use where the protocol is built once with webui build and rendered many times.
// Create handler (optionally with a plugin)
void *handler = webui_handler_create();
// or: void *handler = webui_handler_create_with_plugin("fast-v3");
// Set CSP nonce (optional โ required if your page uses Content-Security-Policy)
webui_handler_set_nonce(handler, "Ep7tTOr+HyRkByAPXxZ9ag==");
// Load protocol.bin from disk (your code)
uint8_t *data = load_file("dist/protocol.bin", &len);
// Render
char *html = webui_handler_render(handler, data, len, state_json,
"index.html", request_path);
if (html) {
// use html...
webui_free(html);
}
// Clean up
webui_handler_destroy(handler);
C API Reference #
The library exports six functions. The generated C header is at crates/webui-ffi/include/webui_ffi.h.
webui_render #
char *webui_render(const char *html, const char *data_json);
Parse an HTML template and render it with JSON state data in a single call. This is the recommended entry point for most consumers.
html, null-terminated UTF-8 string containing the HTML template.data_json, null-terminated UTF-8 JSON string with the render state.- Returns a heap-allocated null-terminated UTF-8 string with the rendered HTML, or
NULLon error. - The caller must free the returned string with
webui_free().
webui_free #
void webui_free(char *string_ptr);
Free a string returned by webui_render or webui_handler_render. Passing NULL is a safe no-op.
webui_last_error #
const char *webui_last_error();
Return the last error message for the current thread, or NULL if no error has occurred. Call this after any function returns NULL to get a human-readable diagnostic.
- The returned pointer is owned by the library. Do not free it.
- The pointer is valid until the next FFI call on the same thread.
- Each thread has its own independent error state.
webui_handler_create #
void *webui_handler_create();
Create a reusable handler instance. Returns an opaque pointer that must eventually be freed with webui_handler_destroy. Use this with webui_handler_render when rendering pre-compiled protobuf protocols.
webui_handler_create_with_plugin #
void *webui_handler_create_with_plugin(const char *plugin_id);
Create a reusable handler instance with a named plugin. Currently supported plugins: "webui", "fast-v3", deprecated "fast-v2", and deprecated "fast" as a compatibility alias for "fast-v2". Pass NULL for no plugin (equivalent to webui_handler_create).
plugin_id, null-terminated UTF-8 string identifying the plugin, orNULL.- Returns an opaque pointer on success, or
NULLon error (callwebui_last_error()for details). - The caller must free the returned pointer with
webui_handler_destroy().
webui_handler_destroy #
void webui_handler_destroy(void *handler_ptr);
Destroy a handler instance created by webui_handler_create. Passing NULL is a safe no-op.
webui_handler_set_nonce #
void webui_handler_set_nonce(void *handler_ptr, const char *nonce);
Set the CSP nonce for inline <script> tags on a handler instance. When set, all subsequent renders will include nonce="VALUE" on inline script tags and emit a <meta name="webui-nonce" content="VALUE"> tag in the <head>.
handler_ptr, pointer returned bywebui_handler_create.nonce, null-terminated UTF-8 string (typically a base64-encoded random value), orNULLto clear a previously set nonce.
The nonce is written verbatim โ pass the raw base64 string without any encoding. The same value should appear in your Content-Security-Policy header.
::: warning Thread Safety
Handler instances are not thread-safe. Do not call webui_handler_set_nonce concurrently with webui_handler_render or other operations on the same handler. Serialize all access via a mutex or single-threaded use.
:::
webui_handler_render #
char *webui_handler_render(void *handler_ptr,
const uint8_t *protocol_data,
uintptr_t protocol_len,
const char *data_json,
const char *entry_id,
const char *request_path);
Render a pre-compiled WebUI protocol (protobuf binary) with JSON state data. This is the lower-level API for callers that have already compiled their templates to protobuf via the CLI.
handler_ptr, pointer returned bywebui_handler_create.protocol_data, pointer to protobuf binary data.protocol_len, length of the protobuf data in bytes.data_json, null-terminated UTF-8 JSON string with the render state.entry_id, null-terminated UTF-8 string identifying the entry fragment (e.g.,"index.html").request_path, null-terminated UTF-8 string with the request path for route matching (e.g.,"/users/42").- Returns a heap-allocated string on success, or
NULLon error. - The caller must free the returned string with
webui_free().
Error Handling #
The FFI uses thread-local error storage following the POSIX dlerror() pattern:
- Any function that can fail returns
NULLon error. - Call
webui_last_error()immediately after to get a human-readable message. - The error pointer is valid until the next FFI call on the same thread.
- Each thread has independent error state, safe for concurrent use.
char *result = webui_render(html, json);
if (result == NULL) {
const char *err = webui_last_error(); // valid until next FFI call
fprintf(stderr, "Render failed: %s\n", err);
// do NOT free err
}
Memory Management #
Two rules to remember:
- Free what you receive. Every non-
NULLstring returned bywebui_renderorwebui_handler_renderis heap-allocated. You must free it withwebui_free(). - Don't free error strings. The pointer from
webui_last_error()is owned by the library. It remains valid until your next FFI call on the same thread.
| Pointer source | Who frees it? | How? |
|---|---|---|
webui_render | Caller | webui_free(ptr) |
webui_handler_render | Caller | webui_free(ptr) |
webui_last_error | Library (do not free) | Replaced on next call |
webui_handler_create | Caller | webui_handler_destroy(ptr) |
webui_handler_create_with_plugin | Caller | webui_handler_destroy(ptr) |
Using Plugins #
Pass a plugin identifier string to webui_handler_create_with_plugin:
// Create handler with @microsoft/fast-element 3.x hydration plugin
void *handler = webui_handler_create_with_plugin("fast-v3");
if (handler == NULL) {
printf("Error: %s\n", webui_last_error());
return 1;
}
// Render, output includes hydration markers
char *html = webui_handler_render(handler, protocol_data, protocol_len,
state_json, "index.html", "/");
webui_free(html);
webui_handler_destroy(handler);
Currently supported plugins: "webui", "fast-v3", deprecated "fast-v2", and deprecated "fast" as a compatibility alias for "fast-v2". Pass NULL for no plugin (equivalent to webui_handler_create).
See Plugins for details on what the FAST plugin versions inject.
Python #
Python's built-in ctypes module can load the shared library directly. No pip packages needed.
import ctypes
from ctypes import c_char_p, c_void_p
# Load the library
lib = ctypes.cdll.LoadLibrary("./target/debug/libwebui_ffi.dylib") # or .so / .dll
# Declare function signatures
lib.webui_render.argtypes = [c_char_p, c_char_p]
lib.webui_render.restype = c_void_p
lib.webui_free.argtypes = [c_void_p]
lib.webui_free.restype = None
lib.webui_last_error.argtypes = []
lib.webui_last_error.restype = c_char_p
# Render a template
html = b'<h1>{{title}}</h1><ul><for each="item in items"><li>{{item}}</li></for></ul>'
state = b'{"title": "Groceries", "items": ["Milk", "Eggs", "Bread"]}'
ptr = lib.webui_render(html, state)
if ptr is None or ptr == 0:
print("Error:", lib.webui_last_error().decode("utf-8"))
else:
result = ctypes.cast(ptr, c_char_p).value.decode("utf-8")
lib.webui_free(ptr)
print(result)
# Output: <h1>Groceries</h1><ul><li>Milk</li><li>Eggs</li><li>Bread</li></ul>
Why
c_void_p? Usingc_void_pas the return type instead ofc_char_ppreventsctypesfrom automatically converting the pointer to a Pythonbytesobject. This lets you copy the string first, then explicitly free the original pointer withwebui_free().
Go #
Go's cgo lets you call C functions directly. Link against libwebui_ffi and use C strings with standard lifecycle management.
package main
// #cgo LDFLAGS: -L./target/debug -lwebui_ffi
// #include <stdlib.h>
//
// extern char *webui_render(const char *html, const char *data_json);
// extern void webui_free(char *ptr);
// extern const char *webui_last_error();
import "C"
import (
"fmt"
"unsafe"
)
func render(html, dataJSON string) (string, error) {
cHTML := C.CString(html)
defer C.free(unsafe.Pointer(cHTML))
cJSON := C.CString(dataJSON)
defer C.free(unsafe.Pointer(cJSON))
ptr := C.webui_render(cHTML, cJSON)
if ptr == nil {
return "", fmt.Errorf("render failed: %s", C.GoString(C.webui_last_error()))
}
defer C.webui_free(ptr)
return C.GoString(ptr), nil
}
func main() {
html := `<h1>{{title}}</h1><ul><for each="item in items"><li>{{item}}</li></for></ul>`
state := `{"title": "Groceries", "items": ["Milk", "Eggs", "Bread"]}`
result, err := render(html, state)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(result)
// Output: <h1>Groceries</h1><ul><li>Milk</li><li>Eggs</li><li>Bread</li></ul>
}
Memory note:
C.GoString(ptr)copies the string into Go-managed memory, so it's safe to callwebui_freeimmediately after.
C# #
Use DllImport (P/Invoke) to call the C API. Strings going in can be marshalled automatically with LPUTF8Str; strings coming out require manual marshalling via IntPtr to control when the native memory is freed.
using System;
using System.Runtime.InteropServices;
class WebUI
{
[DllImport("webui_ffi")]
static extern IntPtr webui_render(
[MarshalAs(UnmanagedType.LPUTF8Str)] string html,
[MarshalAs(UnmanagedType.LPUTF8Str)] string dataJson);
[DllImport("webui_ffi")]
static extern void webui_free(IntPtr ptr);
[DllImport("webui_ffi")]
static extern IntPtr webui_last_error();
static string Render(string html, string dataJson)
{
IntPtr ptr = webui_render(html, dataJson);
if (ptr == IntPtr.Zero)
{
string err = Marshal.PtrToStringUTF8(webui_last_error()) ?? "unknown error";
throw new InvalidOperationException($"Render failed: {err}");
}
string result = Marshal.PtrToStringUTF8(ptr) ?? "";
webui_free(ptr);
return result;
}
static void Main()
{
string html = @"<h1>{{title}}</h1>
<ul><for each=""item in items""><li>{{item}}</li></for></ul>";
string state = @"{""title"": ""Groceries"", ""items"": [""Milk"", ""Eggs"", ""Bread""]}";
Console.WriteLine(Render(html, state));
// Output: <h1>Groceries</h1><ul><li>Milk</li><li>Eggs</li><li>Bread</li></ul>
}
}
Why
IntPtrfor return values? If you usestringas the return type, the .NET marshaller will try to free the memory withCoTaskMemFree, which will crash since the string was allocated by Rust. Always receive asIntPtr, copy withMarshal.PtrToStringUTF8, and free withwebui_free.
Other Languages #
Any language with C FFI support can use WebUI. The pattern is always the same:
- Load the shared library (
libwebui_ffi.dylib/.so/.dll). - Declare the functions you need, at minimum
webui_render,webui_free, andwebui_last_error. - Pass UTF-8 null-terminated strings for
htmlanddata_json. - Check the return value,
NULLmeans an error occurred. - Copy the returned string into your language's managed memory, then call
webui_free.
Next Steps #
- Plugins, Plugin system and FAST hydration
- CLI Reference, Building protocols with
webui build