Skip to content

Hello World (Go)

This guide creates a minimal Electrobun app with a Go main process and a normal project-owned Go module. For the default TypeScript runtime see Hello World. The faster path is hutch electrobun init go-app --template=go-maze-wgpu, which scaffolds a complete WGPU-rendering example.

1. Create the project

Terminal window
mkdir hello-go
cd hello-go

Create hutch.config.ts. Hutch resolves scripts only from this file; this project does not need a package.json:

export default {
electrobun: { version: "2.0.1-beta.0" },
scripts: {
dev: ["hutch", "electrobun", "dev", "--watch"],
build: ["hutch", "electrobun", "build", "--env=stable"],
},
};

Create tsconfig.json so editors and TypeScript resolve the config types and view SDK projected during sync:

{
"extends": "./.hutch/devkit/tsconfig.json"
}

Create go.mod:

module hellogo.example.dev
go 1.26.0
require electrobun v0.0.0
replace electrobun => ./.hutch/devkit/go-sdk

The replacement is the Go SDK copied from the exact Electrobun devkit. Hutch does not generate this module or resolve other Go dependencies; add them normally and let Go own go.mod and go.sum.

2. Add a webview

Create src/mainview/index.html:

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>Hello Electrobun</title>
</head>
<body>
<h1>Hello from Electrobun</h1>
<script type="module" src="index.js"></script>
</body>
</html>

Create src/mainview/index.ts — the view’s script entrypoint, bundled to views://mainview/index.js:

console.log("Hello from the view");

3. Add the main process

Create src/go/main.go:

package main
import (
"fmt"
"os"
"time"
"electrobun"
)
func main() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "[hello-go] %s\n", err)
os.Exit(1)
}
}
func run() error {
core, err := electrobun.LoadCore()
if err != nil {
return err
}
bundlePaths, err := electrobun.ResolveBundlePaths()
if err != nil {
return err
}
appInfo, err := electrobun.ResolveAppInfoFromBundle(bundlePaths)
if err != nil {
return err
}
go createUI(core, bundlePaths)
// Blocks on the native event loop; must run on the main goroutine.
return core.RunMainThread(appInfo)
}
func createUI(core *electrobun.Core, bundlePaths electrobun.BundlePaths) {
time.Sleep(150 * time.Millisecond)
if err := core.ConfigureWebviewRuntimeFromExecutableDir(bundlePaths, 0); err != nil {
fmt.Fprintf(os.Stderr, "[hello-go] failed to configure webview runtime: %s\n", err)
return
}
windowOptions := electrobun.NewWindowOptions(
"Hello Electrobun",
electrobun.NewRect(140, 100, 800, 600),
)
windowOptions.Callbacks = electrobun.WindowCallbacks{
Close: func(uint32) { _ = core.StopEventLoop() },
}
windowID, err := core.CreateWindow(windowOptions)
if err != nil {
fmt.Fprintf(os.Stderr, "[hello-go] failed to create window: %s\n", err)
return
}
webviewOptions := electrobun.NewWebviewOptions(
windowID,
"views://mainview/index.html",
electrobun.NewRect(0, 0, 800, 600),
)
if _, err := core.CreateWebview(webviewOptions); err != nil {
fmt.Fprintf(os.Stderr, "[hello-go] failed to create webview: %s\n", err)
_ = core.CloseWindow(windowID)
}
}

A few things to notice: RunMainThread owns the main goroutine, so all UI creation happens on a separate goroutine; the window’s Close callback stops the event loop so the process exits when the window closes; and nil webview callbacks are fine — a nil DecideNavigation allows all navigation.

4. Configure the build

Create electrobun.config.ts:

import type { ElectrobunConfig } from "electrobun";
export default {
app: {
name: "hello-go",
identifier: "hellogo.example.dev",
version: "0.0.1",
},
build: {
mainProcess: "go",
go: {
package: "./src/go",
},
views: {
mainview: {
entrypoint: "src/mainview/index.ts",
},
},
copy: {
"src/mainview/index.html": "views/mainview/index.html",
},
},
} satisfies ElectrobunConfig;

mainProcess: "go" selects the Go runtime, and go.package selects the main package within the project module. Per-platform bundleCEF / bundleWGPU flags are only needed if you use CEF rendering or WGPU views.

5. Run the app

Terminal window
hutch electrobun sync
hutch run dev

The explicit sync projects the Go SDK and resolves the selected Go toolchain when no exact match is installed. On Windows it also prepares the Zig C compiler required by cgo. hutch run dev then builds and launches the app and rebuilds it when watched source files change. Press Ctrl+C to stop it.

How this differs from TypeScript

  • Normal module ownership. Hutch runs go build from the project module with -mod=readonly. It does not copy source or synthesize a GOPATH. The project owns go.mod, go.sum, module replacements, and third-party dependencies.
  • Version-matched SDK. hutch electrobun sync projects the selected release’s module into .hutch/devkit/go-sdk; the explicit replace directive makes the SDK import deterministic.
  • cgo is mandatory. The SDK is a cgo wrapper, so the build runs with CGO_ENABLED=1 and needs a host C toolchain.
  • Dynamic core loading. LoadCore() dlopens libElectrobunCore from the executable’s directory at runtime and resolves its symbols. Nothing links at build time, and the binary only runs from inside a hutch-built bundle — not via go run.
  • Bundle discovery. ResolveBundlePaths() derives the executable and Resources/ directories from os.Executable(), and ResolveAppInfoFromBundle() reads the app identifier, name, and channel from Resources/version.json.
  • Webview runtime setup. Call ConfigureWebviewRuntimeFromExecutableDir once before creating any webview; it registers the preload scripts that power the views:// scheme and the bridge.
  • You own the event loop. RunMainThread must be called from the main goroutine and blocks until the loop stops. Create windows and webviews from another goroutine after a short delay (~150 ms) so the loop is running first, and call StopEventLoop() from the window close callback or the process keeps running.
  • Errors are plain error values. Every core call returns an error; core.LastError() surfaces the core’s last error string (electrobun_core_last_error).
  • Template. hutch electrobun init go-app --template=go-maze-wgpu scaffolds a full example with RPC messaging and WGPU rendering.

Toolchain

Electrobun 2.0.0 defaults to Go 1.26.4. Override it exactly with build.go.version; Hutch uses an exact matching system compiler when supported or downloads it under ~/.hutch/toolchains/go/<version>/<platform>. Builds target the host OS and architecture only — cross-compilation is not supported yet, so use native runners per platform. Because of cgo you need a host C toolchain: the Xcode Command Line Tools on macOS, the platform compiler and linker on Linux, and on Windows the build uses the vendored zig cc as the C compiler.

For the full picture of native main processes, see Native main process and the main process APIs.