Skip to content

Bun / TypeScript

The most complete host: a typed SDK, an optional Vue renderer, and single-file executable builds.

Install

bash
bun add godot-ui-sdk

The 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:inproc

Produces 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 / classInfo requests report what the connected runtime contains.
  • on(signal, cb, { once, throttleMs }); use throttleMs for high-frequency signals such as a slider's value_changed. The returned subscription's off() 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.