{"id":51716120,"url":"https://github.com/moxcomic/go-webview","last_synced_at":"2026-07-17T03:07:48.127Z","repository":{"id":363130747,"uuid":"1260820910","full_name":"moxcomic/go-webview","owner":"moxcomic","description":"Pure-Go bindings for webview/webview (no cgo) via purego, with prebuilt native libraries for Windows, macOS and Linux.","archived":false,"fork":false,"pushed_at":"2026-06-07T14:45:51.000Z","size":257,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-07T16:21:29.182Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/moxcomic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-05T23:02:29.000Z","updated_at":"2026-06-07T14:45:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/moxcomic/go-webview","commit_stats":null,"previous_names":["moxcomic/go-webview"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/moxcomic/go-webview","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moxcomic%2Fgo-webview","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moxcomic%2Fgo-webview/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moxcomic%2Fgo-webview/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moxcomic%2Fgo-webview/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moxcomic","download_url":"https://codeload.github.com/moxcomic/go-webview/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moxcomic%2Fgo-webview/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35565115,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-17T02:00:06.162Z","response_time":116,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-17T03:07:47.337Z","updated_at":"2026-07-17T03:07:48.120Z","avatar_url":"https://github.com/moxcomic.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# go-webview\n\n\u003e Pure-Go bindings for [webview/webview](https://github.com/webview/webview) — no cgo. The native shared library is loaded at runtime via [purego](https://github.com/ebitengine/purego).\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/moxcomic/go-webview.svg)](https://pkg.go.dev/github.com/moxcomic/go-webview)\n[![Test](https://github.com/moxcomic/go-webview/actions/workflows/test.yml/badge.svg)](https://github.com/moxcomic/go-webview/actions/workflows/test.yml)\n[![Go Report Card](https://goreportcard.com/badge/github.com/moxcomic/go-webview)](https://goreportcard.com/report/github.com/moxcomic/go-webview)\n\n`go-webview` lets you build cross-platform desktop GUIs in Go using the system\nwebview (WebView2 on Windows, WebKitGTK on Linux, WKWebView on macOS). It wraps\nthe C [webview/webview](https://github.com/webview/webview) library (v0.12.0)\n**without cgo**: every native call goes through\n[`github.com/ebitengine/purego`](https://github.com/ebitengine/purego), and the\nshared library is resolved and loaded at runtime (`dlopen` on Unix,\n`LoadLibrary` on Windows). That means a plain `go build` with `CGO_ENABLED=0`,\nno C toolchain, and trivial cross-compilation.\n\n---\n\n## Table of contents\n\n- [Features](#features)\n- [Platform support](#platform-support)\n- [Installation](#installation)\n- [Quick start](#quick-start)\n- [Binding Go functions to JavaScript](#binding-go-functions-to-javascript)\n- [Going the other way: Go calling JS](#going-the-other-way-go-calling-js)\n- [API overview](#api-overview)\n- [Threading model](#threading-model)\n- [Embedded libraries](#embedded-libraries)\n- [Building for Windows](#building-for-windows)\n- [FAQ / troubleshooting](#faq--troubleshooting)\n- [Acknowledgements](#acknowledgements)\n- [License](#license)\n\n---\n\n## Features\n\n- **No cgo.** Builds with `CGO_ENABLED=0`; no C compiler required.\n- **Runtime loading.** The native library is `dlopen`/`LoadLibrary`-loaded the\n  first time you create a webview, so the binary stays portable.\n- **Embeddable native libraries.** A single blank import extracts a prebuilt\n  copy of the correct `.so`/`.dll`/`.dylib` for the host platform at startup —\n  no separate distribution step.\n- **Cross-platform.** macOS, Linux and Windows, on both `amd64` and `arm64`.\n- **Simple, idiomatic API.** A small `WebView` interface mirroring the upstream\n  C API.\n- **Two-way JS bridge.** `Bind` exposes Go functions as global JavaScript\n  functions; arguments and return values are marshalled as JSON.\n- **Safe lifecycle.** `New`/`NewWindow` return an error instead of panicking,\n  and `Destroy` drains in-flight bound-callback goroutines before freeing the\n  native handle.\n\n## Platform support\n\nPrebuilt native libraries (webview/webview **v0.12.0**) are committed under\n[`embedded/\u003cos\u003e_\u003carch\u003e/`](./embedded) and embedded into your binary when you\nimport the [`embedded`](#embedded-libraries) subpackage.\n\n| OS        | amd64 | arm64 | Native backend         | Embedded artifact     |\n| --------- | :---: | :---: | ---------------------- | --------------------- |\n| macOS     |  ✅   |  ✅   | WKWebView (Cocoa)      | `libwebview.dylib`    |\n| Linux     |  ✅   |  ✅   | WebKitGTK (GTK)        | `libwebview.so`       |\n| Windows   |  ✅   |  ✅   | WebView2 (Win32)       | `webview.dll`         |\n\n**Linux runtime requirements:** GTK 3 and WebKitGTK 4.1 must be present on the\ntarget machine. On Debian/Ubuntu:\n\n```sh\nsudo apt-get install -y libgtk-3-0 libwebkit2gtk-4.1-0\n```\n\n**Windows runtime requirements:** the\n[Microsoft Edge WebView2 Runtime](https://developer.microsoft.com/microsoft-edge/webview2/),\nwhich ships with current versions of Windows 10/11.\n\n**macOS** uses the system WebKit and needs no extra runtime dependency.\n\n## Installation\n\n```sh\ngo get github.com/moxcomic/go-webview\n```\n\nRequires Go 1.21 or newer.\n\n## Quick start\n\nThe native library has to be available at runtime. The simplest way is to blank\nimport the `embedded` subpackage, which carries a prebuilt copy for the host\nplatform and extracts it on startup (see [Embedded libraries](#embedded-libraries)).\n\n```go\npackage main\n\nimport (\n\t\"github.com/moxcomic/go-webview\"\n\t_ \"github.com/moxcomic/go-webview/embedded\"\n)\n\nfunc main() {\n\tw, err := webview.New(true) // debug = true enables dev tools where supported\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tw.SetTitle(\"Basic Example\")\n\tw.SetSize(480, 320, webview.HintNone)\n\tw.SetHtml(\"Thanks for using webview!\")\n\tw.Run()     // blocks the main goroutine until the window is closed\n\tw.Destroy() // free the native window\n}\n```\n\n\u003e **Note:** `New` and `NewWindow` return an `error` — always check it. A nil\n\u003e error means the native library loaded and the window was created.\n\nA few things worth knowing about the flow above:\n\n- **`Run` blocks.** It drives the native event loop on the current (main)\n  goroutine and only returns once the window closes (or you call `Terminate`).\n- **Always `Destroy`.** It frees the native window. It also blocks until any\n  in-flight bound callbacks have finished, so it's safe to free the handle.\n- **Threading matters.** The native UI toolkit is single-threaded, so `New`\n  must run on the main goroutine — the package pins it for you with\n  `runtime.LockOSThread` in `init`.\n\nYou can load a remote URL or a data URI instead of inline HTML:\n\n```go\nw.Navigate(\"https://github.com/webview/webview\")\nw.Navigate(\"data:text/html,%3Ch1%3EHello%3C%2Fh1%3E\")\n```\n\nThe minimal window above lives in [`./examples/simple`](./examples/simple).\n\n## Binding Go functions to JavaScript\n\n`Bind` exposes a Go function as a global JavaScript function. The JS call\nreturns a `Promise` that resolves with the JSON-encoded result. Arguments from\nJavaScript are decoded into the Go function's parameters.\n\n```go\npackage main\n\nimport (\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/moxcomic/go-webview\"\n\t_ \"github.com/moxcomic/go-webview/embedded\"\n)\n\nconst html = `\n\u003cbutton id=\"inc\"\u003e+\u003c/button\u003e\n\u003cspan id=\"out\"\u003e0\u003c/span\u003e\n\u003cscript type=\"module\"\u003e\n  document.getElementById(\"inc\").addEventListener(\"click\", async () =\u003e {\n    document.getElementById(\"out\").textContent = await window.count(1);\n  });\n\u003c/script\u003e\n`\n\nfunc main() {\n\t// Bound callbacks run on independent goroutines and may execute\n\t// concurrently, so shared state must be synchronized — here via atomic.\n\tvar count atomic.Int64\n\n\tw, err := webview.New(true)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer w.Destroy()\n\tw.SetTitle(\"Bind Example\")\n\tw.SetSize(480, 320, webview.HintNone)\n\tw.SetHtml(html)\n\n\t// Returns immediately with the new counter value.\n\tif err := w.Bind(\"count\", func(delta int64) int64 {\n\t\treturn count.Add(delta)\n\t}); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// A slow callback does not block the UI thread.\n\tif err := w.Bind(\"compute\", func(a, b int) int {\n\t\ttime.Sleep(time.Second)\n\t\treturn a * b\n\t}); err != nil {\n\t\tpanic(err)\n\t}\n\n\tw.Run()\n}\n```\n\nFrom the page, `await window.count(1)` resolves to the Go return value, and\n`await window.compute(6, 7)` resolves to `42` a second later — without ever\nblocking the UI thread. See the full runnable version (increment/decrement plus\nan async compute button) in [`./examples/bind`](./examples/bind).\n\n### Accepted callback signatures\n\nThe function passed to `Bind` must be a function and must return one of:\n\n| Return signature        | Sent to JavaScript                                            |\n| ----------------------- | ------------------------------------------------------------ |\n| `func(...) T`           | `T` marshalled as JSON (resolves the promise)                |\n| `func(...) (T, error)`  | `T` as JSON, or the error message rejects the promise        |\n| `func(...) error`       | resolves with `null`, or rejects with the error message      |\n| `func(...)`             | resolves with `null`                                          |\n\nIf the second return value is present it **must** implement `error`. A function\nthat returns more than two values, or a non-`error` second value, is rejected by\n`Bind` with an error. A bound function that panics is recovered and surfaced to\nJavaScript as a rejected promise rather than crashing the process. Use\n`Unbind(name)` to remove a binding (and note that binding the same name twice on\none window returns an error).\n\n## Going the other way: Go calling JS\n\n`Eval` runs arbitrary JavaScript asynchronously (the result is ignored — use\n`Bind` if you need a value back), and `Init` injects code that runs on every\npage load, before `window.onload`:\n\n```go\nw.Init(`console.log(\"runs on every navigation\")`)\nw.Eval(`document.body.style.background = \"rebeccapurple\"`)\n```\n\nTo touch the UI from another goroutine, post work back onto the UI thread with\n`Dispatch`:\n\n```go\ngo func() {\n\tw.Dispatch(func() {\n\t\tw.Eval(`alert(\"hello from a background goroutine\")`)\n\t})\n}()\n```\n\n## API overview\n\n```go\nfunc New(debug bool) (WebView, error)\nfunc NewWindow(debug bool, window unsafe.Pointer) (WebView, error)\n\ntype Hint int\n\nconst (\n\tHintNone  Hint = iota // default size\n\tHintMin               // minimum bounds\n\tHintMax               // maximum bounds\n\tHintFixed             // window size cannot be changed by the user\n)\n\ntype WebView interface {\n\tRun()                          // run the event loop (blocks)\n\tTerminate()                    // stop the event loop (safe from any goroutine)\n\tDispatch(f func())             // run f on the main/UI thread\n\tDestroy()                      // close the window and free the handle\n\tWindow() unsafe.Pointer        // native window handle (NSWindow/GtkWindow/HWND)\n\tSetTitle(title string)\n\tSetSize(w, h int, hint Hint)\n\tNavigate(url string)\n\tSetHtml(html string)\n\tInit(js string)                // JS run before window.onload on every page\n\tEval(js string)                // evaluate JS asynchronously, result ignored\n\tBind(name string, f any) error\n\tUnbind(name string) error\n}\n```\n\n`NewWindow` accepts an optional native window handle; pass `nil` to create a new\ntop-level window (which is exactly what `New` does), or a\n`GtkWindow`/`NSWindow`/`HWND` pointer to embed the webview into an existing\nwindow. Full docs are on\n[pkg.go.dev](https://pkg.go.dev/github.com/moxcomic/go-webview).\n\n## Threading model\n\nThe native UI toolkit (Cocoa, GTK, Win32) is **single-threaded**, and that\nconstraint shapes how you use this library.\n\n- **`New`/`NewWindow` must run on the main goroutine.** The package calls\n  `runtime.LockOSThread()` in its `init`, pinning `main` to the main OS thread\n  so the native toolkit is created on the thread it requires. In practice:\n  create the webview from `main`, and don't move that work onto another\n  goroutine.\n- **`Run` blocks.** It runs the event loop on the calling (main) goroutine and\n  returns only when the loop is terminated. After it returns, call `Destroy`.\n- **UI mutators run on the UI thread.** Methods such as `SetTitle`, `SetSize`,\n  `Navigate`, `SetHtml`, `Init` and `Eval` are intended to be called from the\n  UI thread. To touch the window from another goroutine, post work with\n  `Dispatch(func(){ ... })`, which runs `f` on the main thread.\n- **`Terminate` is goroutine-safe.** You can call it from any goroutine to stop\n  the loop.\n- **Bound callbacks are concurrent.** Functions registered with `Bind` are\n  invoked on **independent goroutines** and may run **concurrently** with one\n  another, so a slow callback never blocks the UI. Any shared state your\n  callbacks touch must be synchronized by your code (the example above uses\n  `sync/atomic`; a `sync.Mutex` works too).\n- **`Destroy` is safe.** It marks the instance dead, waits for in-flight\n  bound-callback goroutines to finish (so none calls into a freed handle), then\n  destroys the native window. Calling `Destroy` more than once is a no-op.\n\n## Embedded libraries\n\nThe native library must be resolvable at runtime. There are two supported ways\nto ship it.\n\n### Option 1 — embed and auto-extract (recommended)\n\nBlank-import the `embedded` subpackage. It bundles a prebuilt copy of the\ncorrect native library for the host `GOOS`/`GOARCH` and, during package `init`,\nextracts it into a **per-user cache directory** (from `os.UserCacheDir()`, under\n`go-webview/\u003cversion\u003e/`) using restrictive `0600` permissions, then points the\nloader at that directory via the `WEBVIEW_PATH` environment variable.\n\n```go\nimport (\n\t\"github.com/moxcomic/go-webview\"\n\t_ \"github.com/moxcomic/go-webview/embedded\"\n)\n```\n\nExtraction is atomic and idempotent: a previously extracted, current-user-owned\ncopy of the right size is reused; otherwise it is rewritten via a temp file +\nrename so concurrent processes never observe a partially written library.\n\nIf extraction fails, the error is recorded in `embedded.Err` rather than\npanicking. `webview.New` will then fail to load the library, so you can inspect\nthe root cause:\n\n```go\nimport (\n\t\"log\"\n\n\t\"github.com/moxcomic/go-webview\"\n\tembedded \"github.com/moxcomic/go-webview/embedded\"\n)\n\nfunc main() {\n\tif embedded.Err != nil {\n\t\tlog.Fatalf(\"embedded native library not available: %v\", embedded.Err)\n\t}\n\n\tw, err := webview.New(true)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer w.Destroy()\n\t// ...\n}\n```\n\n### Option 2 — ship the library next to your executable\n\nIf you'd rather not embed the binary, place the appropriate native library in\nthe same directory as your executable:\n\n| Platform | File               |\n| -------- | ------------------ |\n| Linux    | `libwebview.so`    |\n| macOS    | `libwebview.dylib` |\n| Windows  | `webview.dll`      |\n\nYou can copy these from the [`embedded/\u003cos\u003e_\u003carch\u003e/`](./embedded) directories of\nthis repository, or build them from upstream\n[webview/webview](https://github.com/webview/webview).\n\n**macOS `.app` bundles:** place `libwebview.dylib` in the bundle's\n`Contents/Frameworks` folder (e.g. `YourApp.app/Contents/Frameworks/`) — the\nloader looks in `../Frameworks` relative to the executable in addition to the\nexecutable's own directory.\n\n### How the loader finds the library\n\nThe first time you create a webview, the loader searches, in order:\n\n1. the directory named by the `WEBVIEW_PATH` environment variable (if set and\n   non-empty — the `embedded` package sets this for you);\n2. the directory containing the running executable (plus `../Frameworks` on\n   macOS);\n3. the platform's standard library search path (so a system-installed `webview`\n   library is found via `dlopen`/`LoadLibrary`).\n\nThe current working directory is deliberately **not** part of the search path.\nOn Windows the loader opts into the safe DLL search order and, when given an\nabsolute path, loads with an altered search path so the library's own\ndependencies resolve from its directory.\n\n## Building for Windows\n\nBy default a Windows Go binary opens a console window. For a GUI application,\nbuild with the `windowsgui` subsystem flag:\n\n```sh\ngo build -ldflags=\"-H windowsgui\" .\n```\n\nBecause there is no cgo, you can cross-compile from any host:\n\n```sh\nGOOS=windows GOARCH=amd64 go build -ldflags=\"-H windowsgui\" .\n```\n\nThe end-user machine needs the\n[WebView2 Runtime](https://developer.microsoft.com/microsoft-edge/webview2/)\ninstalled (bundled with current Windows). Ship `webview.dll` alongside your\n`.exe`, or use the `embedded` subpackage.\n\n## FAQ / troubleshooting\n\n**`webview: failed to load native library` on startup.**\nThe loader could not find or open the native library. Either blank-import\n`github.com/moxcomic/go-webview/embedded`, or place the right\n`.so`/`.dll`/`.dylib` next to your executable. See\n[Embedded libraries](#embedded-libraries). If you embed and still see this,\ncheck `embedded.Err` for the underlying extraction failure.\n\n**What does `WEBVIEW_PATH` do?**\nIt names the directory the loader checks **first** for the native library. The\n`embedded` package sets it automatically to the per-user cache directory it\nextracts into. You can also set it yourself to point at a directory that\ncontains `libwebview.so` / `libwebview.dylib` / `webview.dll` — for example to\ntest a locally built native library. An empty `WEBVIEW_PATH` is ignored (it does\n**not** fall back to the current working directory).\n\n**Linux: error loading shared libraries / `webkit2gtk` not found.**\nThe GTK 3 and WebKitGTK 4.1 runtime libraries are missing. Install them:\n`sudo apt-get install -y libgtk-3-0 libwebkit2gtk-4.1-0`.\n\n**Windows: the window never appears / errors mentioning WebView2.**\nInstall the [Microsoft Edge WebView2 Runtime](https://developer.microsoft.com/microsoft-edge/webview2/).\n\n**The program panics or the UI hangs when I call from a goroutine.**\nCreate the webview from `main` and don't move UI calls onto other goroutines —\nuse `Dispatch` to run code on the UI thread, and remember bound callbacks run\nconcurrently (synchronize shared state). See [Threading model](#threading-model).\n\n**My `Bind` call returns an error.**\nThe bound value must be a function returning a value, value+error, just an\nerror, or nothing; a second return value must implement `error`; and binding the\nsame name twice on one window fails. See\n[Accepted callback signatures](#accepted-callback-signatures).\n\n**A console window pops up on Windows.**\nBuild with `-ldflags=\"-H windowsgui\"`. See [Building for Windows](#building-for-windows).\n\n## Acknowledgements\n\nThis project stands on the work of others:\n\n- [**webview/webview**](https://github.com/webview/webview) — the C library these\n  bindings wrap (v0.12.0).\n- [**ebitengine/purego**](https://github.com/ebitengine/purego) — calling C from\n  Go without cgo, which makes the no-cgo runtime loading possible.\n- [**abemedia/go-webview**](https://github.com/abemedia/go-webview) — the\n  upstream Go bindings (MIT) that `go-webview` is forked from.\n\n## License\n\nReleased under the [MIT License](./LICENSE).\n\nCopyright (c) 2025 Adam Bouqdib (upstream author of\n[abemedia/go-webview](https://github.com/abemedia/go-webview)). This fork retains\nthe original copyright and license.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoxcomic%2Fgo-webview","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoxcomic%2Fgo-webview","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoxcomic%2Fgo-webview/lists"}