Qt is a C++ library with no C ABI, so bun:ffi cannot call it directly. Write a small extern "C" shim, compile it into a shared library with your C++ compiler, and load that with dlopen. The shim owns QApplication::exec(), and calls a JSCallback synchronously on the JavaScript thread.
#include <QApplication>
#include <QLabel>
extern "C" int bun_qt_window_run(const char* title, int width, int height, void (*on_created)(int, int)) {
static int argc = 1;
static char arg0[] = "bun";
static char* argv[] = {arg0, nullptr};
QApplication app(argc, argv);
QLabel window(QStringLiteral("Qt 6 through bun:ffi"));
window.setWindowTitle(QString::fromUtf8(title));
window.resize(width, height);
window.show();
on_created(width, height);
return app.exec(); // returns when the last window closes
}
c++ -std=c++20 -shared -fPIC -o libshim.so shim.cpp $(pkg-config --cflags --libs Qt6Widgets)
import { dlopen, FFIType, JSCallback } from "bun:ffi";
const { symbols } = dlopen("./libshim.so", {
bun_qt_window_run: { args: [FFIType.ptr, FFIType.i32, FFIType.i32, FFIType.ptr], returns: FFIType.i32 },
});
const onCreated = new JSCallback((width: number, height: number) => console.log(`window ${width}x${height}`), {
args: [FFIType.i32, FFIType.i32],
returns: FFIType.void,
});
const status = symbols.bun_qt_window_run(Buffer.from("Hello from Bun\0"), 640, 400, onCreated.ptr);
onCreated.close();
process.exit(status);
bun window.ts
TinyCC, which powers cc in bun:ffi, compiles C only, so a C++ shim needs the system compiler. On Windows, MSYS2 provides both the compiler and Qt (pacman -S mingw-w64-ucrt-x86_64-{gcc,pkgconf,qt6-base}); add C:\msys64\ucrt64\bin to PATH so the Qt DLLs are found when the shim loads. On Linux without a display, set QT_QPA_PLATFORM=offscreen.
QApplication::exec() blocks the JavaScript thread, so timers, promises and I/O callbacks do not run while the window is open. Use a QTimer in the shim for periodic work inside the loop.
Tested platforms
The fixture prints window created and window closed with exit code 0 on:
- Windows 11: Qt 6.11.2 and GCC from MSYS2 UCRT64
- Ubuntu 26.04: Qt 6.10.2 under
xvfb-run
The Linux images are scripts/aphrody/gui/ubuntu.Dockerfile and scripts/aphrody/gui/alpine.Dockerfile.
A fuller version that builds and caches the shim, closes on Escape or after --timeout, and accepts --title, --width and --height lives in the Bun repository at test/js/bun/ffi/qt-window.fixture.ts with the shim in qt-window.shim.cpp. For a KDE Kirigami window, see Open a KDE Kirigami window.
See Docs > Runtime > FFI for the full bun:ffi reference.