Glaze is a pure Go desktop WebView binding built on top of webview/webview and purego.
This repository started from the original go-webview project, but it has diverged significantly and is maintained as a separate codebase with its own goals and APIs.
- Keep a CGo-free desktop integration layer for Go applications.
- Provide pragmatic helpers for desktop app workflows.
- Keep behavior explicit and testable.
- Stay friendly to multi-module
go.workdevelopment.
- No CGo
- Windows, macOS, and Linux support
- Native library loading with embedded runtime assets
- JavaScript to Go binding support
- Utility helpers for desktop app patterns:
BindMethods,RenderHTML,AppWindow
| Desktop | Game of Life | Starfield |
|---|---|---|
![]() |
![]() |
![]() |
| Doom Fire | Mandelbrot | Falling Sand |
|---|---|---|
![]() |
![]() |
![]() |
| Raycasting | Filo REPL | |
|---|---|---|
![]() |
![]() |
go get github.com/crgimenes/glaze@latestTo use embedded native libraries:
import _ "github.com/crgimenes/glaze/embedded"package main
import (
"log"
"github.com/crgimenes/glaze"
_ "github.com/crgimenes/glaze/embedded"
)
func main() {
w, err := glaze.New(true)
if err != nil {
log.Fatal(err)
}
defer w.Destroy()
w.SetTitle("Glaze")
w.SetSize(800, 600, glaze.HintNone)
w.SetHtml("<h1>Hello from Glaze</h1>")
w.Run()
}BindMethods is a convenience layer over Bind that exposes all exported
methods of a Go value as JavaScript-callable functions.
What it does:
- Reflects over exported methods on a struct or pointer receiver.
- Builds JavaScript names using a prefix and snake_case conversion.
- Example:
GetUserByIDwith prefixapibecomesapi_get_user_by_id.
- Example:
- Applies the same function signature rules as
Bind:- no return
- value
- error
- value and error
- Returns the list of bound names so you can log or verify registration.
This is useful when you have a service object and want to expose a consistent
JavaScript API without writing one Bind call per method.
type Store struct{}
func (s *Store) GetItems() []string { return []string{"a", "b"} }
bound, err := glaze.BindMethods(w, "store", &Store{})RenderHTML renders a named Go html/template into a string for SetHtml.
What it does:
- Executes a specific template by name (including nested template calls).
- Returns the final HTML string.
- Wraps template execution errors with template context.
This is useful when you want server-style template rendering in a local desktop app without running an HTTP server for that page.
html, err := glaze.RenderHTML(tpl, "page", data)
if err != nil {
return err
}
w.SetHtml(html)AppWindow wraps an http.Handler inside a native desktop window backed by a
local loopback HTTP server.
What it does:
- Supports selectable transport with platform-aware default:
auto(default):unixon macOS/Linux,tcpon Windowstcp: direct loopback HTTP (127.0.0.1)unix: handler served on Unix socket with a lightweight loopback HTTP gateway for browser navigation
- Starts listeners using random free ports/paths by default (or custom
Addr/UnixSocketPath). - Creates a native window and navigates it to that local URL.
- Runs the UI loop and closes the HTTP server when the window exits.
- Supports window sizing, title, debug mode, and optional readiness callback.
OnReady: receives browser URL (alwayshttp://127.0.0.1:...).OnReadyInfo: receives resolved backend details (Transport,Backend,Gateway) so you can verify unix vs tcp in logs.
This is the simplest way to reuse an existing net/http application as a
desktop app with minimal changes to your routing, templates, and assets.
err := glaze.AppWindow(glaze.AppOptions{
Title: "My App",
Width: 1280,
Height: 800,
Transport: glaze.AppTransportAuto,
Handler: mux,
OnReadyInfo: func(info glaze.AppReadyInfo) {
log.Printf("transport=%s backend=%s gateway=%s", info.Transport, info.Backend, info.Gateway)
},
})From the repository root:
go run ./examples/simple
go run ./examples/bind
go run ./examples/zero_tcpFrom each example directory:
cd examples/appwindow && go run .
cd examples/desktop && go run .
cd examples/filorepl && go run .examples/zero_tcp demonstrates a local-first UI with SetHtml + BindMethods
only. It does not start an HTTP server, so there is no loopback TCP gateway.
Default tests (headless safe):
go test ./...GUI integration test:
go test -tags=integration -run TestWebview ./...Use windowsgui to hide the console window:
go build -ldflags="-H windowsgui" .webview.go- core API and binding internalsappwindow.go- desktop window plus local HTTP server helperhelpers.go- utility helpers (BindMethods,RenderHTML)embedded/- embedded native library assets per platformexamples/- runnable sample applications
Glaze embeds native libraries and extracts them to disk before loading. By default, the extraction target is a temporary directory that may be writable by other processes, which could allow an attacker to replace the library file.
To mitigate this, Glaze computes a BLAKE2b-256 hash of the embedded library bytes at runtime and verifies every extracted (or pre-existing) file against that hash before loading. If the hash does not match, extraction fails with an integrity error and the library is not loaded.
Additionally, extracted files are created with restricted permissions (0500
owner read+execute) inside a directory with 0700 permissions.
For production deployments, use ExtractTo to place the library in a secure,
application-controlled directory instead of the system temp directory:
package main
import (
"log"
"github.com/crgimenes/glaze"
"github.com/crgimenes/glaze/embedded"
)
func main() {
// Extract to a directory with restricted access.
if err := embedded.ExtractTo("/opt/myapp/lib"); err != nil {
log.Fatal(err)
}
w, err := glaze.New(true)
if err != nil {
log.Fatal(err)
}
defer w.Destroy()
w.SetTitle("Secure App")
w.SetSize(800, 600, glaze.HintNone)
w.SetHtml("<h1>Hello</h1>")
w.Run()
}When using ExtractTo, do not use import _ "github.com/crgimenes/glaze/embedded" — that
blank import triggers init() which extracts to the default temp directory.
Call ExtractTo explicitly instead.
- abemedia/go-webview for the original Go binding base
- webview/webview for the native WebView implementation
- purego for dynamic linking without CGo







