Frontend API Reference
Everything your web frontend can call on the Go backend: your app's own fields and methods, the built-in window.strux.* system services, and the bidirectional event channel. There is nothing to import or connect — the bindings are injected into the page as global objects before your code runs, and a generated declaration file gives you full TypeScript types for all of them. For a walkthrough, see the Frontend guide; for the Go side of every service listed here, see the Go Runtime reference.
How the bindings reach the page
On a Strux device, your frontend runs inside WPE WebKit under the Cage compositor (see the display stack). A Strux-built WPE extension runs inside the browser process: at page load it connects to the Go runtime's IPC socket (/tmp/strux-ipc.sock), asks the runtime for its bindings, and injects matching JavaScript objects into window. Method calls and field accesses are forwarded over that socket; there is no HTTP API and no WebSocket client to set up.
Bindings exist only on the device
window.go, window.strux, and your app global are injected by the WPE extension. They exist when your page runs on a Strux device or in the QEMU VM that strux dev boots — not when you open the page in a regular desktop browser.
How the typed API is generated
strux types
strux types statically analyzes your Go code and writes frontend/src/strux.d.ts. It also runs automatically during strux init and at the start of every strux build frontend step, so the file tracks your backend. The file uses declare global, so the types apply project-wide with no imports.
What happens under the hood:
- The
strux-introspectbinary (a Go AST analyzer shipped with the CLI) parses yourmain.goand every file in its package. - It finds your app struct by locating the value passed to
runtime.Start(...)orruntime.Init(...); if that fails, it falls back to a struct namedApp. - It extracts the struct's exported fields and methods, follows struct-typed fields into your own packages (resolving them with
go list), and converts Go types to TypeScript. - It merges in the built-in runtime service types (a snapshot of
pkg/runtime/apibaked into the CLI, generated bycmd/gen-runtime-types) and the types of any BSP runtime extensions declared by your active BSP. - The result is written to
frontend/src/strux.d.ts. Don't edit it — rerunstrux typesinstead.
Type mapping
| Go type | TypeScript type |
|---|---|
string | string |
int, int8–int64, uint–uint64, float32, float64 | number |
bool | boolean |
[]T, ...T | T[] |
map[K]V | Record<K, V> |
*T | T (pointers are transparent) |
interface{} | any |
named type over a primitive (e.g. type AudioOutput string) | the underlying type (string) |
| your structs | a generated interface with the same name |
The globals
| Global | What it is |
|---|---|
window.go.<package>.<Struct> | Your app struct's bindings, e.g. window.go.main.App. |
window.<Struct> | A shortcut to the same object, e.g. window.App — what you'll normally use. |
window.strux.<namespace> | The built-in runtime services (boot, capabilities, dev, display, network, project, update, wifi) plus any custom BSP extensions. |
window.strux.ipc | The event API: on, off, send. |
Because they are window properties, all of these are also reachable as bare globals (App, strux), and the generated strux.d.ts declares them that way.
Your app's bindings
Given the template backend from strux init:
type App struct {
Title string
Counter int
}
func (a *App) Greet(name string) string { return "Hello, " + name + "!" }
func (a *App) Add(x, y float64) float64 { return x + y }
strux types generates:
interface App {
Title: string;
Counter: number;
Greet(name: string): Promise<string>;
Add(x: number, y: number): Promise<number>;
}
const App: App;
And the frontend uses it directly:
const msg = await App.Greet("World") // "Hello, World!"
const sum = await App.Add(2, 3) // 5
console.log(App.Title) // read a Go field
App.Counter = 42 // write a Go field
Methods
- Every bound Go method becomes an async function returning a
Promise, regardless of how fast the Go side is. - Parameters are positional and JSON-encoded. Names in the
.d.tscome from your Go parameter names. - If the Go method's last return value is an
errorand it is non-nil, the promise rejects with the error message as a string. The generated return type adds| nullfor these methods (e.g.Promise<string | null>). - A method with no non-error returns resolves to
void; one value resolves to that value; multiple values resolve to an array, typed as a tuple.
Fields
Exported primitive fields are injected as live properties: reading App.Counter performs a synchronous IPC read of the current Go value, and assigning to it writes the Go value. They look like plain values in TypeScript, but every access is a round-trip to the backend — for bulk reads, prefer a method that returns a snapshot struct.
Nested structs
Exported struct-typed fields on your app become nested objects with their own bound fields and methods. From the example project:
// Go: App.Settings.Audio (*settings.Audio) with method SetMasterVolume(volume float64)
await App.Settings.Audio.SetMasterVolume(0.8)
const vol = App.Settings.Audio.MasterVolume
Nil pointer fields in Go are skipped at bind time, so initialize nested structs before starting the runtime.
Naming and JSON encoding
Bindings for your structs keep their Go names exactly: App.SearchYouTube(...), result.Title. Struct values returned by your methods are serialized with Go's encoding/json, while the generated types always use the Go field names — so avoid json:"..." tags that rename fields on structs you return to the frontend, or the runtime values won't match the generated types. The built-in StruxRuntime.* types are the exception: they are declared with their camelCase JSON names (interfaceName, signalStrength, …), which is what the wire actually carries.
Runtime services: window.strux.*
The built-in services mirror the Go runtime services one-to-one — same methods, same semantics, with errors surfacing as promise rejections. This is the full generated interface:
interface Strux {
boot: {
HideSplash(): Promise<void>;
Reboot(): Promise<void>;
Shutdown(): Promise<void>;
};
capabilities: {
List(): Promise<StruxRuntime.CapabilityInfo[]>;
Supports(name: string): Promise<boolean>;
};
dev: {
GetConfig(): Promise<StruxRuntime.DevState | null>;
SetConfig(config: StruxRuntime.DevConfig): Promise<void>;
SetEnabled(enabled: boolean): Promise<void>;
Apply(config: StruxRuntime.DevConfig, enabled: boolean): Promise<void>;
RestartService(): Promise<void>;
ApplyAndRestart(config: StruxRuntime.DevConfig, enabled: boolean): Promise<void>;
};
display: {
List(): Promise<StruxRuntime.DisplayOutput[] | null>;
Get(name: string): Promise<StruxRuntime.DisplayOutput | null>;
Apply(changes: StruxRuntime.DisplayOutputChange[], opts: StruxRuntime.DisplayApplyOptions): Promise<void>;
SetListedMode(output: string, mode: StruxRuntime.ListedModeSelection, opts: StruxRuntime.DisplayApplyOptions): Promise<void>;
SetCustomMode(output: string, mode: StruxRuntime.CustomModeSelection, opts: StruxRuntime.DisplayApplyOptions): Promise<void>;
SetPreferredMode(output: string, opts: StruxRuntime.DisplayApplyOptions): Promise<void>;
SetOutputEnabled(output: string, on: boolean, opts: StruxRuntime.DisplayApplyOptions): Promise<void>;
SetLayout(output: string, x: number, y: number, opts: StruxRuntime.DisplayApplyOptions): Promise<void>;
SetScale(output: string, scale: number, opts: StruxRuntime.DisplayApplyOptions): Promise<void>;
SetTransform(output: string, transform: string, opts: StruxRuntime.DisplayApplyOptions): Promise<void>;
GetBacklight(outputName: string): Promise<number | null>;
SetBacklight(outputName: string, value: number): Promise<void>;
};
network: {
ListInterfaces(): Promise<StruxRuntime.NetworkInterface[] | null>;
GetDefaultInterface(kind: string): Promise<StruxRuntime.NetworkDefaultInterface | null>;
GetStatus(interfaceName: string): Promise<StruxRuntime.NetworkStatus | null>;
ConfigureIP(req: StruxRuntime.NetworkIPConfigRequest): Promise<void>;
SetEnabled(interfaceName: string, enabled: boolean): Promise<void>;
RenewDHCP(interfaceName: string): Promise<void>;
};
project: {
Info(): Promise<StruxRuntime.ProjectInfo | null>;
};
update: {
Progress(): Promise<StruxRuntime.UpdateProgress | null>;
State(): Promise<StruxRuntime.UpdateState | null>;
};
wifi: {
ListInterfaces(): Promise<StruxRuntime.WiFiInterface[] | null>;
GetDefaultInterface(): Promise<StruxRuntime.WiFiDefaultInterface | null>;
GetStatus(interfaceName: string): Promise<StruxRuntime.WiFiStatus | null>;
Scan(interfaceName: string): Promise<StruxRuntime.WiFiNetwork[] | null>;
Connect(req: StruxRuntime.WiFiConnectRequest): Promise<void>;
ConnectKnown(req: StruxRuntime.WiFiKnownNetworkRequest): Promise<void>;
Disconnect(interfaceName: string): Promise<void>;
ListKnownNetworks(): Promise<StruxRuntime.WiFiKnownNetwork[] | null>;
Forget(id: string): Promise<void>;
SetKnownNetworkPriority(id: string, priority: number): Promise<void>;
ConfigureIP(req: StruxRuntime.WiFiIPConfigRequest): Promise<void>;
};
ipc: {
on(event: string, callback: (data: any) => void): () => void;
off(event: string, callback: (data: any) => void): void;
send(event: string, data?: any): void;
};
}
The supporting data shapes (StruxRuntime.DisplayOutput, StruxRuntime.WiFiStatus, …) are declared in the same generated file under the StruxRuntime namespace; their field-by-field documentation lives in the Go Runtime reference.
A few service-specific notes:
display(backlight only),network, andwifiare BSP-dependent. On a BSP without the matching provider, those calls reject withcapability <name> is not supported by the active BSP. Probe first:await strux.capabilities.Supports("wifi").boot.HideSplash()is the call your frontend makes when it has rendered and is ready to replace the boot splash.updatereads the device's update progress and A/B state.
Experimental
A/B (dual-rootfs) updates are experimental; the shapes returned by strux.update.State() may change. See Dual Rootfs.
Example: a Wi-Fi settings screen.
if (await strux.capabilities.Supports("wifi")) {
const def = await strux.wifi.GetDefaultInterface()
if (def?.found) {
const networks = await strux.wifi.Scan(def.interfaceName)
await strux.wifi.Connect({
interfaceName: def.interfaceName,
ssid: "MyNetwork",
password: "hunter22",
bssid: "",
})
}
}
Events: strux.ipc
Events are named messages that flow in both directions, separate from method calls — use them when Go needs to push to the page.
| Function | Description |
|---|---|
strux.ipc.on(event, callback) | Listens for events emitted from Go with rt.Emit(event, data). Returns an unsubscribe function. |
strux.ipc.off(event, callback) | Removes a listener registered with on (same callback reference). |
strux.ipc.send(event, data?) | Sends an event to Go, where rt.On(event, handler) receives it. |
The template project wires up a round trip — Go side:
rt.On("hello", func(data interface{}) {
rt.Emit("hello-reply", map[string]string{"message": "Hello from Go!"})
})
Frontend side:
const unsubscribe = strux.ipc.on("hello-reply", (data) => {
console.log(data.message) // "Hello from Go!"
})
strux.ipc.send("hello", { from: "frontend" })
// later, e.g. in a component unmount hook:
unsubscribe()
Event payloads are JSON-encoded; they are typed as any, so define your own types for them. rt.Emit broadcasts to all connected frontends, which makes events the natural way to sync multiple views of the same device.
BSP extensions
A BSP can expose extra hardware APIs under window.strux.<name> via runtime.RegisterCustomExtension (or runtime.RegisterExtension for other namespaces) — see Runtime Extensions. When your active BSP declares runtime extensions, strux types parses their Go source too and includes their namespaces and data types in frontend/src/strux.d.ts, so they are as fully typed as the built-in services. Call semantics are identical: exported methods, promises, error rejection.