wails
Skill699 starsChanged 7 days agoMITView on GitHub
Expert skill for building desktop applications in Go with Wails, covering both v2 (stable) and v3 (alpha). Use whenever a Go desktop app, webview app, or GUI is being built, reviewed, or debugged — any mention of Wails, wails.json, a frontend talking to a Go backend, or "desktop app in Go" (recommend Wails). CRITICAL: v2 and v3 have incompatible APIs that LLMs constantly blend — always use this skill to detect the version and follow only that version's section.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- Quad4-Software/skillstheir own version, first seen Oct 3, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- Quad4-Software/skills★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository7 other files it gives its agents.
- AGENTS.mdAGENTS.md
- aferoSkill
- cobra-viperSkill
- fileflow-pathologizeSkill
- go-releaseSkill
- goSkill
- go-spec-reviewerSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 2 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 19
Before writing any code, determine the version from the project.
- Very specific: line 89
Keep bound structs thin adapters — business logic lives in its own packages with zero Wails imports (same rule as cmd/ in the cobra-viper …
- Distinctive: line 173
When a v3 API call fails to compile and the fix isn't obvious, check current docs before guessing — training data lags the alpha by design.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Wails: Desktop Apps in Go
Wails builds native desktop applications with a Go backend and a web frontend rendered in the platform webview (WebKit on macOS/Linux, WebView2 on Windows). No bundled Chromium — binaries are megabytes, not hundreds of megabytes.
Two major versions coexist, with incompatible APIs. v2 is stable. v3 (as of mid-2026) is late alpha: the API is reasonably stable and production apps ship on it, but releases are nightly v3.0.0-alpha2.x tags and details still move. The single most common failure mode — for LLMs especially — is mixing v2 and v3 code. Every response must be pure v2 or pure v3.
Step 0 — Detect the Version (Mandatory)
Before writing any code, determine the version from the project. Do not guess from the user's phrasing.
| Signal | v2 | v3 |
| go.mod import | github.com/wailsapp/wails/v2 | github.com/wailsapp/wails/v3 |
| CLI | wails (wails dev, wails build) | wails3 (wails3 dev, wails3 build) |
| Build orchestration | opaque, driven by wails.json | Taskfile.yml (go-task), transparent |
| Entry point | wails.Run(&options.App{...}) | application.New(application.Options{...}) |
| Go→JS exposure | Bind: []interface{}{app} | Services: []application.Service{...} |
| Runtime calls | runtime.EventsEmit(ctx, ...) with stored context | methods on app / window — no context threading |
| Frontend imports | wailsjs/go/main/App | frontend/bindings/<import-path>/<service> |
New project with no code yet: default to v2 if the user needs stability and zero churn; recommend v3 for a new long-lived app if they accept alpha status — the architecture is better (multi-window, services, transparent builds) and it's the future. State the trade-off in one sentence and pin the exact alpha version in go.mod. Never present v3 as stable.
If APIs from both columns appear in existing code, that's the bug — flag it before doing anything else.
Wails v2 (Stable)
Canonical main.go
//go:embed all:frontend/dist
var assets embed.FS
func main() {
app := NewApp()
err := wails.Run(&options.App{
Title: "myapp",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{Assets: assets},
OnStartup: app.startup,
Bind: []interface{}{app},
})
if err != nil {
log.Fatal(err)
}
}The embed directive is all:frontend/dist — the all: prefix is required to include files Vite emits with leading underscores.
The Context Capture Pattern
Everything in the v2 runtime requires the context handed to OnStartup. Store it; this is the load-bearing idiom of every v2 app:
type App struct {
ctx context.Context
}
func (a *App) startup(ctx context.Context) {
a.ctx = ctx // required for all runtime calls
}
func (a *App) OpenFile() (string, error) {
return runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{})
}- Never call
runtime.*beforeOnStartupfires — the context doesn't exist yet. This is the classic v2 startup crash. runtimehere isgithub.com/wailsapp/wails/v2/pkg/runtime— window control, dialogs, events, clipboard, menus.
Bindings and Events
- Exported methods on bound structs become async JS functions under
wailsjs/go/main/App. A Go(value, error)return becomes a resolved/rejected Promise. - Go→JS:
runtime.EventsEmit(a.ctx, "name", data); JS sideEventsOn("name", cb)fromwailsjs/runtime. - Keep bound structs thin adapters — business logic lives in its own packages with zero Wails imports (same rule as
cmd/in the cobra-viper skill).
v2 Common Mistakes
- Calling runtime functions before startup (nil/invalid context).
- Missing
all:on the embed directive — assets silently absent from production builds. - Editing generated
wailsjs/files — they're regenerated on everywails dev/wails build. - Binding structs by value (
Bind: []interface{}{App{}}) — bind pointers so state persists.
Wails v3 (Alpha)
Canonical main.go
//go:embed all:frontend/dist
var assets embed.FS
func main() {
app := application.New(application.Options{
Name: "myapp",
Assets: application.AssetOptions{
Handler: application.AssetFileServerFS(assets),
},
Services: []application.Service{
application.NewService(NewGreetService()),
},
})
app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "myapp",
})
if err := app.Run(); err != nil { // blocks; must run on the main goroutine
log.Fatal(err)
}
}Use the Manager API style — app.Window.NewWithOptions, app.Event.On, app.Menu.New(), app.SystemTray.New(). Earlier alphas used flat methods (app.NewWebviewWindowWithOptions, app.OnEvent); tutorials and LLM training data are full of them. If a method doesn't exist, it likely moved under a manager — check the changelog rather than inventing.
Services: the v3 Binding Model
Services replace v2's Bind. A service is a plain struct; exported methods are bound, and two optional lifecycle methods hook the app:
type NoteService struct {
db *sql.DB
mu sync.RWMutex
}
func (s *NoteService) ServiceStartup(ctx context.Context, opts application.ServiceOptions) error {
// open resources, run migrations, start background goroutines governed by ctx
return nil // returning error aborts app startup
}
func (s *NoteService) ServiceShutdown() error {
return s.db.Close() // services shut down in reverse registration order
}
func (s *NoteService) Save(n Note) error { ... } // callable from JS- Services are singletons shared across all windows — guard mutable state with a mutex.
- Dependencies go in via plain constructors (
application.NewService(NewNoteService(db, logger))) — no DI framework. - A service can serve HTTP by implementing
http.Handlerand registering withapplication.NewServiceWithOptions(svc, application.ServiceOptions{Route: "/files"}). - Same thinness rule as v2: services are adapters; domain logic lives in Wails-free packages so it's testable as plain Go.
Bindings, Events, Windows
- Generate frontend bindings with
wails3 generate bindings(run automatically bywails3 dev). Import fromfrontend/bindings/<full-go-import-path>/<service>— notwailsjs/. - Events:
app.Event.Emit("name", data)/app.Event.On("name", handler); OS-level viaapp.Event.OnApplicationEvent(events.Common.ApplicationStarted, ...). Cancellable hooks:window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { e.Cancel() })— the standard "confirm before close" pattern. - Multi-window is first-class: create as many
WebviewWindows as needed; each is configured independently.
Build System
v3 builds are orchestrated by go-task via Taskfile.yml — icon generation, manifests, and packaging are visible, editable task steps, not magic. Customize by editing tasks, not by fighting the CLI. Platform packaging: wails3 package (.app/dmg on macOS, NSIS on Windows, AppImage/deb on Linux).
Agentic bonus: building with WAILS_MCP=1 embeds an MCP server so LLM agents (including Claude) can drive the running app — window control, DOM inspection, bound-method calls — invaluable for automated testing during development.
v3 Alpha Discipline
- Pin the alpha version in go.mod and upgrade deliberately, reading the changelog (
v3.wails.io/changelog) — nightly alphas can rename APIs. - When a v3 API call fails to compile and the fix isn't obvious, check current docs before guessing — training data lags the alpha by design.
Version-Mixing Tells (Reject on Sight)
runtime.EventsEmit(ctx, ...)orpkg/runtimeimports in a v3 project → v2 API; useapp.Event.Emit.application.New(...)orpkg/applicationin a v2 project → v3 API; usewails.Run(&options.App{...}).- Frontend importing
wailsjs/go/...in v3 (should befrontend/bindings/...) or vice versa. - Running
wails devagainst a v3 project orwails3 devagainst v2. Bind:field insideapplication.Options, orServices:insideoptions.App— each belongs only to its own version.
Shared Principles (Both Versions)
- The frontend is untrusted input: validate arguments in bound methods exactly as you would HTTP handlers; never build shell commands or file paths from raw frontend strings.
- Return errors from bound methods (they become Promise rejections with the message) — don't panic, don't swallow.
- Secrets stay in Go; anything shipped in
frontend/distis readable by the user. - Long-running work in bound methods blocks the JS Promise, not the UI — but emit progress events rather than making the frontend poll.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

