Appearance
Bun / TypeScript
The most complete host: a typed SDK, an optional Vue renderer, and single-file executable builds.
Install
bash
bun add godot-ui-sdkThe runtime installs as an optional platform dependency; connect() locates it automatically.
Example
ts
import { GodotClient } from "godot-ui-sdk";
const client = new GodotClient();
await client.connect(); // starts the runtime, opens a window
const root = await client.getRoot();
root.set("title", "Counter");
const box = await client.instantiate("VBoxContainer");
const label = await client.instantiate("Label");
const button = await client.instantiate("Button");
label.set("text", "0");
button.set("text", "Increment");
root.addChild(box);
box.addChild(label).addChild(button);
let n = 0;
await button.on("pressed", () => label.set("text", String(++n)));
client.onWindowCloseRequested = () => client.disconnect();Run with bun run app.ts. Writes (set, addChild) are queued and batched; await is only needed for calls that return values (instantiate, get, on).
Scenes from the Godot editor
Load screens designed in the editor and address their nodes by name (background: Custom scenes & assets):
ts
import type { GodotObject } from "godot-ui-sdk";
import { resolve } from "node:path";
await client.request("loadPack", { path: resolve("app.pck") });
const ui = await client.instantiateScene("res://main.tscn");
(await client.getRoot()).addChild(ui);
const save = await ui.call<GodotObject>("get_node", "%SaveButton");
const status = await ui.call<GodotObject>("get_node", "%Status");
await save.on("pressed", () => status.set("text", "Saved."));During development, instantiateScene("/abs/path/main.tscn") loads a scene file directly, without a pack.
Assets
ts
// Theme (prebuilt Adwaita/Breeze/Fluent/macOS themes are included):
root.set("theme", await client.load(resolve("themes/adwaita_dark.tres")));
// Image loaded at runtime (png/jpg/webp/svg):
const img = await client.instantiate("Image");
await img.call("load", "/home/me/avatar.png");
const tex = await client.instantiate("ImageTexture");
await tex.call("set_image", img);
avatarRect.set("texture", tex);
img.free();
// Font:
const font = await client.instantiate("FontFile");
await font.call("load_dynamic_font", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf");
title.set("theme_override_fonts/font", font);Vue
godot-vue is a Vue 3 renderer over the SDK: components, reactivity and v-model, rendering into widgets.
ts
import { createGodotApp } from "godot-vue";
import App from "./App.vue";
await createGodotApp(App, { client });Single-file executables
bash
cd demo-apps && bun run bundle:inprocProduces one ~32 MB executable containing your app, Bun and the runtime. Details: Shipping your app.
In-process mode
Runs the engine inside the Bun process instead of spawning it; the single-file build uses this mode.
ts
await client.connect({ inproc: { args: ["--no-project"] } });The SDK pumps engine frames on the event loop, so timers and promises continue to run. One engine per process; it cannot be restarted after disconnect.
Notes
await client.sync()is the barrier for read-after-write; see Core patterns. Reads of locally written values are served from the SDK cache.client.instantiateAs(Button, "Button")returns generated typed wrappers.classes/classInforequests report what the connected runtime contains.on(signal, cb, { once, throttleMs }); usethrottleMsfor high-frequency signals such as a slider'svalue_changed. The returned subscription'soff()unsubscribes.- For CI, pass
--headless:connect({ stdio: { args: ["--no-project", "--headless"] } }). - Object handles (
oid) are opaque strings. - Paths in messages are resolved by the runtime; send absolute paths or
res://. - Call
disconnect()at exit so queued writes are flushed.