Compare commits

...

5 commits

Author SHA1 Message Date
Lea Anthony
ff87592631 docs: third-pass final stragglers - logger, dialog method, FAQ fixes
- concepts/bridge.mdx: Fixed logger to use slog (not fabricated
  application.NewDefaultLogger/logger.DEBUG)
- concepts/manager-api.mdx: Fixed SetDefaultFilename -> SetFilename
- faq.mdx: Fixed build output dir, removed fabricated -platform flag,
  corrected binary size reduction advice

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 21:44:27 +11:00
Lea Anthony
ce95358854 docs: third-pass deep audit continued - late agent fixes
Additional fixes from deep-audit agents that completed after initial staging:

- reference/application.mdx: Added complete Options struct table (30+ fields),
  AssetOptions, platform options (MacOptions, WindowsOptions, LinuxOptions),
  missing methods (SetIcon, GetPID, Capabilities)
- features/events/system.mdx: Fixed JS event callbacks - handler receives event
  object, data is at event.data not passed directly
- features/bindings/services.mdx: Added ServiceName interface, ServiceOptions
  table with Name/Route/MarshalError fields
- reference/dialogs.mdx: Removed non-existent SetTitle() from SaveFileDialogStruct,
  documented SaveFileWithOptions as correct approach
- guides/events-reference.mdx: Fixed theme change pattern - system events don't
  carry theme data, show proper Go->JS forwarding
- guides/build/windows.mdx: Removed fabricated -platform flag, use GOARCH instead
- migration/v2-to-v3.mdx: Fixed OnClick callbacks to take *application.Context
- getting-started/installation.mdx: wails doctor -> wails3 doctor
- concepts/lifecycle.mdx: Fixed leftover OnStartup reference

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 21:40:25 +11:00
Lea Anthony
0cac85cc6b docs: third-pass deep audit verifying struct fields, options, CLI flags, and examples
Deep audit of 23 documentation files against v3 source code:

- reference/window.mdx: Added 20+ missing methods (ToggleFullscreen, ToggleMaximise,
  ForceReload, Width/Height, Bounds, PhysicalBounds, SnapAssist, ToggleFrameless,
  ShowMenuBar/HideMenuBar/ToggleMenuBar), fixed EmitEvent return docs
- reference/dialogs.mdx: Added missing dialog methods (ResolvesAliases,
  AllowsOtherFileTypes, HideExtension, SetMessage, SetButtonText, AddButtons,
  OpenFileWithOptions, SaveFileWithOptions, full SaveFileDialogStruct methods)
- reference/menu.mdx: Added missing MenuItem methods and role constants
- reference/events.mdx: Fixed event constant names and struct fields
- guides/cli.mdx: Added missing CLI flags (-s, -mod, -tags, -noevents), fixed
  -clean default to true, added missing commands (tool lipo, tool capabilities,
  tool sign, setup signing, setup entitlements, doctor-ng, generate template)
- guides/building.mdx: Fixed fabricated build flags, corrected Taskfile structure
- concepts/lifecycle.mdx: Replaced hallucinated OnStartup/OnBeforeClose hooks with
  actual ServiceStartup interface, ShouldQuit option, RegisterHook pattern
- concepts/build-system.mdx: Fixed Vite port (5173->9245), corrected Taskfile.yml
  to match actual project structure with platform-specific includes
- features/screens/info.mdx: Fixed app.Screens->app.Screen, replaced fabricated
  Screen struct with real one (Bounds, PhysicalBounds, WorkArea, Rotation fields),
  removed non-existent GetCurrent/GetByID methods
- features/windows/options.mdx: Added missing platform option fields
- tutorials/03-notes-vanilla.mdx: Fixed package-level dialog functions to manager
  API, fixed service registration pattern with app reference passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 21:39:21 +11:00
Lea Anthony
89d006bf18 docs: second-pass audit with specialist agents verifying against source
Deep second-pass audit using 6 specialist agents, each reading v3 source
code first then verifying every code example in their domain.

Window specialist (6 files):
- Remove fabricated convenience methods (OnClose, OnFocus, OnBlur, etc.)
- Fix platform struct names: MacOptions→MacWindow, WindowsOptions→WindowsWindow
- Fix field names: Resizable→DisableResize, WindowState→StartState
- Remove non-existent fields: Parent, Assets, OnClose on options
- Fix SetBackgroundColour to take RGBA struct
- Full rewrite of windows/events.mdx to use OnWindowEvent pattern

Event specialist (3 files):
- Fix wrong constants: WindowBlur→WindowLostFocus, WindowClosed→WindowClosing
- Fix wrong method: OpenedFile()→Filename()
- Fix JS import patterns and Emit namespace
- Split mixed Go/JS code blocks

Menu specialist (3 files):
- Fix context menu CSS property name
- Fix menu guide patterns

Dialog specialist (5 files):
- All correct, no fixes needed

Bindings specialist (5 files):
- Fix generated JS/TS binding examples to use $Call.ByID()
- Fix sync.Mutex→sync.RWMutex compile error
- Fix //wails:internal on unexported methods

General specialist (6 files):
- Fix remaining *application.Application refs
- Fix old dialog patterns in FAQ and migration guide
- Fix changelog method names: Register→Add, Unregister→Remove
- Fix Screen.GetAll return type, Env.GetAll→Env.Info

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 18:04:44 +11:00
Lea Anthony
182eeee8b9 docs: fix hallucinated API patterns across v3 documentation
Audit all v3 documentation pages against actual source code on v3-alpha
branch. Fix incorrect API patterns that were hallucinated during docs
generation.

Key fixes across 38 files:
- app.NewWebviewWindow() → app.Window.New()
- app.NewWebviewWindowWithOptions() → app.Window.NewWithOptions()
- app.NewSystemTray() → app.SystemTray.New()
- app.EmitEvent() → app.Event.Emit()
- app.OnEvent() → app.Event.On()
- app.CurrentWindow() → app.Window.Current()
- app.GetWindowByName() → app.Window.GetByName()
- app.OnWindowCreation() → app.Window.OnCreate()
- app.NewMenu() → application.NewMenu()
- *application.Application → *application.App
- *application.WailsEvent → *application.CustomEvent
- window.SetFocus() → window.Focus()
- window.SetFullscreen() → window.ToggleFullscreen()
- SetWindowOffset() → WindowOffset()
- SetWindowDebounce() → WindowDebounce()
- KeyBinding callback: func(*WebviewWindow) → func(Window)
- SetBackgroundColour(r,g,b,a) → SetBackgroundColour(RGBA{})
- app.Events.On() → app.Event.On()/app.Event.OnApplicationEvent()

Sections audited: reference/, features/, concepts/, guides/,
tutorials/, migration/, contributing/, getting-started/, quick-start/,
faq, whats-new, DEVELOPER_GUIDE

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 12:43:00 +11:00
140 changed files with 42700 additions and 0 deletions

View file

@ -0,0 +1,542 @@
---
title: Wails v3 Developer Guide
description: Comprehensive onboarding for engineering and debugging the Wails v3 codebase
slug: developer-guide
sidebar:
label: Developer Guide
order: 50
---
## How to Use This Guide
- **Scope**: Everything documented here applies to the `v3/` branch of Wails, tracking the current workspace state (commit you have checked out) and intentionally ignores the legacy `v2/` runtime.
- **Audience**: Senior Go + desktop developers who need to become productive at fixing bugs or extending the v3 runtime, CLI, or build tooling.
- **Format**: Top-down description → component drill-down → pseudocode for critical flows → teardown guidance → reference tables.
- **Navigation**: Skim the diagrams first, then expand sections relevant to the subsystem you are editing.
```d2
direction: down
Developer: "Developer"
CLI: "CLI Layer\n(cmd/wails3)"
Commands: "Command Handlers\n(internal/commands)"
TaskRunner: "Task Runner\n(internal/commands/task.go)"
Taskfile: "Taskfile\n(v3/Taskfile.yaml)"
BuildPipeline: "Build/Packaging\n(internal/*)"
Templates: "Templates\n(internal/templates)"
RuntimeGen: "Runtime Assets\n(internal/runtime)"
Application: "Application Core\n(pkg/application)"
AssetServer: "Asset Server\n(internal/assetserver)"
MessageBridge: "Message Processor\n(pkg/application/messageprocessor*.go)"
Webview: "WebView Implementations\n(pkg/application/webview_window_*.go)"
Services: "Services & Bindings\n(pkg/services/*)"
Platform: "Platform Layers\n(pkg/mac, pkg/w32, pkg/events, pkg/ui)"
Developer -> CLI: "runs wails3"
CLI -> Commands: "registers subcommands"
Commands -> TaskRunner: "wraps build/package/dev"
TaskRunner -> Taskfile: "executes tasks"
Commands -> BuildPipeline: "invokes packagers & generators"
BuildPipeline -> Templates: "renders scaffolds"
Commands -> RuntimeGen: "builds runtime JS"
RuntimeGen -> Application: "embedded assets"
Application -> AssetServer: "serves HTTP"
Application -> MessageBridge: "routes runtime calls"
MessageBridge -> Webview: "bridge over WebView"
Application -> Services: "binds Go services"
Application -> Platform: "dispatches to OS"
AssetServer -> Webview: "feeds frontend"
Services -> MessageBridge: "expose methods"
```
---
## Repository Layout (v3 only)
| Path | Purpose | Highlights |
| --- | --- | --- |
| `v3/cmd/wails3` | CLI entrypoint | Command registration, build info capture (`main.go`). |
| `v3/internal/commands` | Implementation for each CLI subcommand | Includes build/package/dev tooling, template generators, diagnostics. |
| `v3/internal/flags` | Typed flag definitions for CLI commands | Shared flag structs (e.g. `Build`, `Package`, `ServiceInit`). |
| `v3/internal/assetserver` | Embedded HTTP server powering the runtime | Request routing, middleware, webview handoff. |
| `v3/internal/runtime` | Generates runtime JS payload | Platform-specific flag injection, dev/prod toggles. |
| `v3/internal/templates` | Project/frontend scaffolding | Multiple frontend stacks with shared `_common` assets. |
| `v3/internal/service` | Service template generator | Creates Go service skeletons. |
| `v3/internal/packager` | Wrapper around nfpm for Linux packaging | Generates `.deb`, `.rpm`, `.apk`, etc. |
| `v3/internal/term`, `term2` | Console styling helpers | Shared across commands. |
| `v3/pkg/application` | Core runtime used by user apps | Windowing, events, bindings, asset hosting, shutdown. |
| `v3/pkg/events` | Enumerates runtime event IDs | Shared constants used on Go and JS sides. |
| `v3/pkg/services` | Built-in services (badge, notifications, sqlite, etc.) | Reference implementations for custom services. |
| `v3/pkg/icons`, `v3/pkg/ui` | Icon tooling & UI examples | Support packages used by CLI utilities. |
| `v3/pkg/mac`, `v3/pkg/w32`, `v3/pkg/mcp` | Platform bindings | cgo / pure Go glue for OS integrations. |
| `v3/scripts`, `v3/tasks`, `v3/test`, `v3/tests` | Automation helpers | Task runners, Docker harnesses, regression suites. |
| `v3/Taskfile.yaml` | Root Taskfile consumed by CLI wrappers | Defines build/test workflows referenced by commands. |
---
## CLI and Tooling Layer
### Entry Point: `v3/cmd/wails3/main.go`
The CLI uses `github.com/leaanthony/clir` to declaratively register subcommands. Build metadata is captured in `init()` by reading `debug.ReadBuildInfo` and storing it into `internal/commands.BuildSettings` for later introspection (`wails3 tool buildinfo`).
```pseudo
func main():
app := clir.NewCli("wails", "The Wails3 CLI", "v3")
register simple actions (docs, sponsor) -> open browser
register functional subcommands that delegate to internal/commands
init -> commands.Init
build -> commands.Build (wraps task)
dev -> commands.Dev (starts watcher)
package -> commands.Package
doctor -> commands.Doctor
releasenotes -> commands.ReleaseNotes
task -> commands.RunTask (Taskfile wrapper)
generate subtree -> {build-assets, icons, syso, runtime, template, ...}
update subtree -> {build-assets, cli}
service -> {init}
tool subtree -> {checkport, watcher, cp, buildinfo, package, version}
version -> commands.Version
defer printFooter() (prints docs/sponsor hints unless disabled)
on error -> log with pterm and exit 1
```
Key takeaways:
- All heavy lifting happens inside `internal/commands`. The CLI just constructs flag structs and hands them off.
- Build/package commands are aliases for `wails3 task <name>`; real work is defined in `v3/Taskfile.yaml`.
- `commands.DisableFooter` prevents duplicate footer output when commands open browsers or produce their own footers.
### Flag Definitions: `v3/internal/flags`
- Each subcommand gets a struct tagged with descriptions/defaults used by `clir`.
- Example: `flags.Build` holds options for `wails3 build`, while `flags.GenerateBindingsOptions` describes binding generator inputs.
- Flag structs double as configuration objects passed into command implementations, so the same struct layout must be respected by tests.
### Command Implementations: Highlights
| File | Responsibility |
| --- | --- |
| `internal/commands/init.go` | Scaffolds new projects from templates, resolves template metadata, writes config (`wails.json`). |
| `internal/commands/dev.go` | Sets up the dev server port, populates `FRONTEND_DEVSERVER_URL`, and forwards to the file watcher. |
| `internal/commands/watcher.go` | Loads `taskfile`-compatible YAML (`dev_mode`), instantiates `refresh/engine`, registers signal handlers, and keeps the engine alive until interrupted. |
| `internal/commands/build-assets.go` | Materializes packaging assets from embedded templates (`gosod` extractor). Handles defaults, path normalization, and YAML config ingestion. |
| `internal/commands/generate_template.go` | Creates template stubs by reading `internal/templates`. |
| `internal/commands/tool_*` | Misc utilities: port checks, file copy, semantic version bump (`tool_version.go`), etc. |
| `internal/commands/task_wrapper.go` | Implements `wails3 build/package` alias behavior by re-invoking the `task` subcommand with rewired `os.Args`. |
```pseudo
func wrapTask(command string, otherArgs []string):
warn("alias for task")
newArgs := ["wails3", "task", command] + otherArgs
os.Args = newArgs
return RunTask(&RunTaskOptions{Name: command}, otherArgs)
```
### Task Runner Integration: `internal/commands/task.go`
- Wraps `github.com/wailsapp/task/v3` for cross-platform task execution.
- Accepts both positional task names (`wails3 task dev -- <extra>`) and `--name` flags.
- Validates mutually exclusive options (e.g., `--dir` vs `--taskfile`).
- Supports list/status/JSON output inherited from upstream Task library.
- Uses `BuildSettings` to print the bundled Task version when `--version` is passed.
### Dev Mode Flow (`wails3 dev`)
1. Resolve Vite port preference (`--port`, `WAILS_VITE_PORT`, default `9245`).
2. Check that the port is free by attempting to `net.Listen` and immediately closing.
3. Export `WAILS_VITE_PORT` and set `FRONTEND_DEVSERVER_URL` (`http` vs `https` based on `--secure`).
4. Invoke `Watcher` with the configured Taskfile path (defaults to `./build/config.yml`).
### Packaging & Distribution Commands
- `GenerateSyso` (Windows resource) writes `.syso` files for icon/resource embedding.
- `GenerateIcons` converts SVG/PNGs into platform icon bundles.
- `ToolPackage` uses `internal/packager` to drive `nfpm` for Linux packages.
- `GenerateAppImage`, `generate_webview2`, and `package/msix.go` provide OS-specific installers.
---
## Template & Asset Generation
### Project Templates: `v3/internal/templates`
- Each frontend stack lives under its own directory (React, Vue, Svelte, Solid, Qwik, Vanilla, Lit, Preact) with `-ts` variants.
- `_common` contains shared scaffolding (Go module layout, `wails.json`, default assets).
- `generate template` extracts template files via `gosod` into the target project directory, honoring parameters collected during `wails3 init`.
### Build Assets: `internal/commands/build-assets.go`
Embedded assets define packaging metadata such as installers, file associations, protocol handlers, and Windows installer scripts.
```pseudo
func GenerateBuildAssets(opts):
opts.Dir = abs(opts.Dir); mkdir if missing
fill empty fields (ProductComments, Identifier, BinaryName, etc.) with sensible defaults
load `build_assets` FS subtree and render via gosod using opts (includes file associations + protocols)
render `updatable_build_assets` on top so user edits stay intact
```
### Runtime Bundles: `internal/runtime`
- `Core()` concatenates `runtimeInit + flags + invoke + environment` to produce the JS bootstrap injected into every window.
- Platform-specific files (`runtime_windows.go`, `runtime_linux.go`, etc.) define how Go exposes `window._wails.invoke` and system flags (resize handle sizes on Windows via `pkg/w32`).
- `GenerateRuntime` command serializes pre-built runtime assets for embedding.
---
## Runtime Boot Sequence (pkg/application)
### Application Construction: `pkg/application/application.go`
A singleton `App` is created via `application.New(options)` and stored in `globalApplication`.
```pseudo
func New(options):
if globalApplication exists -> return it (enforces singleton)
mergeApplicationDefaults(options)
app := newApplication(options) // debug vs production build tags
globalApplication = app
fatalHandler(app.handleFatalError)
configure Logger (debug -> DefaultLogger, prod -> discard)
install default signal handler unless disabled (ctrl+c, SIGTERM -> App.Quit)
log startup + platform info
customEventProcessor := NewWailsEventProcessor(app.Event.dispatch)
messageProc := NewMessageProcessor(app.Logger)
assetOpts := assetserver.Options {
Handler: options.Assets.Handler (default BundledAssetFileServer)
Middleware: Chain(user middleware, internal middleware for /wails endpoints)
Logger: app.Logger (or discard when DisableLogging)
}
asset server intercepts:
/wails/runtime.js -> bundled runtime asset
/wails/runtime -> messageProc.ServeHTTP
/wails/capabilities -> emits capabilities JSON
/wails/flags -> marshals platform flags via impl.GetFlags
app.assets = AssetServer(assetOpts)
app.bindings = NewBindings(options.MarshalError, options.BindAliases)
app.options.Services = clone(options.Services)
process key bindings if provided
register OnShutdown hook if provided
if SingleInstance configured -> newSingleInstanceManager
return app
```
Key supporting structures:
- `Options` (`application_options.go`) configures assets, services, platform-specific knobs, signal handling, keybindings, custom marshaling, and single-instance behavior.
- `signal.NewSignalHandler` (`internal/signal`) watches OS signals, invoking `App.Quit` while printing customizable exit messages.
### Run Loop & Lifecycle
`App.Run()` orchestrates startup, service lifecycle, channel fan-out, and the platform event loop.
```pseudo
func (a *App) Run():
lock runLock; guard against double runs
defer cancel app.Context (ensures goroutines exit on failure)
execute a.preRun() (noop in production, debug logging otherwise)
a.impl = newPlatformApp(a) // windowsApp, darwinApp, linuxApp depending on GOOS
defer a.shutdownServices()
services := clone(options.Services); options.Services = nil
for service in services:
startupService(service) // binds methods, registers HTTP routes, calls ServiceStartup
append to options.Services so shutdown order is reversed
spawn goroutines reading buffered channels:
applicationEvents -> EventManager.handleApplicationEvent
windowEvents -> handleWindowEvent
webviewRequests -> assets.ServeWebViewRequest
windowMessageBuffer -> handleWindowMessage (custom vs wails: prefix)
windowKeyEvents -> handleWindowKeyEvent
windowDragAndDropBuffer -> handleDragAndDropMessage (debug logs)
menuItemClicked -> Menu.handleMenuItemClicked
mark running=true; flush pendingRun queue by invoking runnable.Run() asynchronously
if GOOS == darwin -> set application menu immediately
if Icon provided -> impl.setIcon
return a.impl.run() (enters platform main loop)
```
```d2
direction: down
root: "App.Run dispatch hub"
root -> applicationEvents: "goroutine"
applicationEvents -> EventManager: "Event.handleApplicationEvent"
root -> windowEvents: "goroutine"
windowEvents -> Windows: "handleWindowEvent"
root -> webviewRequests
webviewRequests -> AssetServer: "ServeWebViewRequest"
root -> windowMessages
windowMessages -> MessageHandlers: "HandleMessage / RawMessageHandler"
root -> windowKeyEvents
windowKeyEvents -> KeyBinding: "HandleKeyEvent"
root -> dragDropBuffer
dragDropBuffer -> Windows: "HandleDragAndDrop"
root -> menuItemClicked
menuItemClicked -> MenuManager
```
### Shutdown Path
- `shutdownServices()` iterates bound services in reverse start order, invoking `ServiceShutdown` when implemented and cancelling the app context.
- `OnShutdown` hooks run synchronously on the main thread; `PostShutdown` runs last (useful on macOS where `Run` may block indefinitely).
- `cleanup()` (triggered via `impl.destroy()` and `App.Quit`) sets `performingShutdown`, cancels the context, runs queued shutdown tasks, releases the single-instance manager, and closes windows/system trays.
---
## Message Bridge & Asset Server
### Message Processor (`messageprocessor.go` et al.)
- Handles `/wails/runtime` POST requests from the frontend.
- Dispatch keyed by `object` query parameter:
- `callRequest` (0) → service bindings.
- `clipboardRequest`, `applicationRequest`, `eventsRequest`, `contextMenuRequest`, `dialogRequest`, `windowRequest`, `screensRequest`, `systemRequest`, `browserRequest`, `cancelCallRequest`.
- Uses HTTP headers `x-wails-window-id` / `name` to resolve the target `Window` (`getTargetWindow`).
- Maintains `runningCalls` map to support cancellation by call ID.
```pseudo
func processCallMethod(method, rw, req, window, params):
args := params.Args()
callID := args.String("call-id")
if method == CallBinding:
options := params.ToStruct(CallOptions)
ctx, cancel := context.WithCancel(request.Context)
register cancel in runningCalls[callID]
respond 200 OK immediately
go func():
boundMethod := lookup by name or ID (honour aliases)
if not found -> CallError(kind=ReferenceError)
if window != nil -> ctx = context.WithValue(ctx, WindowKey, window)
result, err := boundMethod.Call(ctx, options.Args)
on CallError -> window.CallError(callID, json, knownError=true)
marshal result -> jsonResult
window.CallResponse(callID, jsonResult)
cleanup runningCalls entry and cancel context
```
```
- Errors bubble to the frontend via `CallError` JSON with `ReferenceError`, `TypeError`, or `RuntimeError` kinds.
- Cancellation (`cancelCallRequest`) removes the call ID from `runningCalls` and cancels the context.
### Asset Server (`internal/assetserver`)
- `AssetServer.ServeHTTP` wraps responses with MIME sniffing, logs duration, window metadata, and status codes.
- `serveHTTP` intercepts root/index requests to optionally serve a localized index fallback (`defaultIndexHTML` uses `accept-language`).
- `/wails/*` special routes and user-defined services share the same middleware chain, allowing injection of auth, logging, or routing.
- Dev vs production logic uses environment variable `FRONTEND_DEVSERVER_URL`; when present, requests proxy to the external dev server instead of the embedded assets (`asset_fileserver.go`).
- `AttachServiceHandler` mounts service-provided HTTP handlers under custom routes (`ServiceOptions.Route`).
- WebView-specific request/response types are implemented in `internal/assetserver/webview`, providing platform-native bridges to feed asset bytes directly into the webview without round-tripping through TCP when possible.
### Runtime JS Exposure
- `/wails/runtime.js` serves the concatenated runtime string produced by `internal/runtime`, ensuring the frontend has access to `window.wails` helpers.
- `/wails/flags` serializes `Options.Flags` extended by the platform implementation (`windowsApp.GetFlags` injects resize handles, etc.), allowing frontend startup logic to adjust to platform constraints.
- `/wails/capabilities` exposes a JSON describing features like native drag support (populated by `internal/capabilities`).
---
## Windowing & UI Layer (`pkg/application`)
### Window Manager (`window_manager.go`)
- Maintains the authoritative map of `Window` instances keyed by numeric ID.
- Defers actual `Run` execution if the app has not started (`runOrDeferToAppRun`).
- Provides lookup by name/ID, iteration (`GetAll`), and lifecycle hooks (`OnCreate`).
- Works in tandem with `App.pendingRun` to ensure windows created before `App.Run` are executed after the platform loop is ready.
### Webview Windows (`webview_window.go` + `webview_window_<platform>.go`)
- `WebviewWindow` wraps a platform-specific `webviewWindowImpl` with synchronized maps for event listeners, key bindings, menus, and asynchronous cancellers.
- Supports full window control API: sizing, positioning, zoom, devtools, menu bar toggles, context menu injection, border size queries, drag/resize operations.
- `HandleMessage`, `HandleKeyEvent`, and drag-and-drop handlers are invoked by the central channels in `App.Run`.
- On runtime readiness, windows emit `events.Common.WindowRuntimeReady` to allow frontends to hydrate state once the JS bridge is loaded.
### Event System (`events.go`, `context_*`)
- `ApplicationEvent` and `WindowEvent` objects carry strongly-typed contexts (`ApplicationEventContext`, `WindowEventContext`) to provide structured data (files dropped, URLs, screen info, etc.).
- Channels `applicationEvents`, `windowEvents`, and `menuItemClicked` are buffered to avoid blocking the OS event loop.
- `EventProcessor` manages custom user events, offering `On`, `Once`, `Emit`, and hook registration for pre-dispatch inspection.
- Platform-specific files (`events_common_windows.go`, `events_common_darwin.go`, etc.) translate native callbacks into the common channel structure.
### Managers & Subsystems
| Manager | File | Responsibility |
| --- | --- | --- |
| `ContextMenuManager` | `context_menu_manager.go` | Creates native context menus, wires handlers. |
| `DialogManager` | `dialog_manager.go` + OS-specific files | Wraps native file/message dialogs. |
| `ClipboardManager` | `clipboard_manager.go` | Provides cross-platform clipboard access, proxies to OS-specific implementations. |
| `ScreenManager` | `screenmanager.go` | Exposes multi-monitor info, resolution, scaling. |
| `SystemTrayManager` | `system_tray_manager.go` | Manages tray icons, menu interactions. |
| `BrowserManager` | `browser_manager.go` | Handles window navigation and devtools. |
| `KeyBindingManager` | `key_binding_manager.go` | Registers accelerators and callbacks. |
| `EnvironmentManager` | `environment_manager.go` | Tracks env state (dark mode, accent color) exposed to the frontend. |
---
## Services & Binding Engine
### Service Definition (`service.go`, `bindings.go`)
- Services are user-provided structs implementing optional interfaces:
- `ServiceStartup(ctx context.Context, opts ServiceOptions) error`
- `ServiceShutdown() error`
- `ServeHTTP` (when exposing HTTP routes)
- Methods to be bound must be exported, live on pointer receivers of named types, and cannot be generic.
```pseudo
func startupService(service):
bindings.Add(service) // reflect, hash method signatures, register alias map
if service.Route != "":
if instance implements http.Handler -> attach to asset server
else -> attach fallback handler returning 503
if instance implements ServiceStartup -> call with app context
```
- `Bindings.Add` enumerates exported methods via reflection, hashes fully qualified names using `internal/hash`, and stores them in `boundMethods` (by name) and `boundByID` (by numeric ID). Hash collisions are guarded with explicit error messages instructing developers to rename methods.
- `CallOptions` submitted from the frontend include `MethodID`, `MethodName`, and JSON-encoded args; the binding engine supports both to allow smaller payloads in production (IDs) while keeping dev ergonomics (names).
### Error Marshalling & Aliases
- Custom error marshaling can be provided per service (`ServiceOptions.MarshalError`) or globally (`Options.MarshalError`), allowing user-defined JSON payloads.
- `BindAliases` maps alternative IDs to primary method IDs, helpful when generated bindings are versioned and need to stay stable across refactors.
### Context Propagation
- During calls, the bridge injects `context.Context` with the window (`context.WithValue(ctx, WindowKey, window)`) so services can inspect which window invoked them (e.g., to push events back via `window.Emit`).
- Cancellation is propagated when the frontend aborts a promise or the app shuts down (`App.cancel()`).
---
## Configuration Options & Their Effects
### `application.Options` Highlights (`application_options.go`)
| Field | Effect on Runtime |
| --- | --- |
| `Assets.Handler` | Base HTTP handler serving static assets. Overrides default embedded bundle, but `/wails/*` middleware still executes. |
| `Assets.Middleware` | Injects custom middleware before internal routes; can short-circuit requests to implement routing or authentication. |
| `Assets.DisableLogging` | Swaps the asset server logger to a discard handler to avoid noisy logs. |
| `Flags` | Merged with platform flags and exposed to the frontend at `/wails/flags`. Changing affects frontend boot configuration. |
| `Services` | List of services auto-registered during `Run`. Order matters for startup/shutdown. |
| `BindAliases` | Remaps method IDs used by the runtime. Critical when regenerating bindings without breaking existing frontend code. |
| `KeyBindings` | Global accelerator map executed per window. Processed during `New`, stored per window at runtime. |
| `OnShutdown` / `PostShutdown` | Lifecycle hooks executed during teardown. `OnShutdown` runs before services shut down; `PostShutdown` runs after platform loop returns (if ever). |
| `ShouldQuit` | Gatekeeper invoked when the user attempts to quit (e.g., Cmd+Q). Returning `false` keeps the app alive. |
| `RawMessageHandler` | Receives messages that do not start with the `wails:` prefix, enabling custom bridge protocols aside from service calls. |
| `WarningHandler` / `ErrorHandler` | Overrides default slog warnings/errors for system-level diagnostics. |
| `FileAssociations` | Used during packaging and when launch arguments are parsed on Windows/macOS to emit `ApplicationOpenedWithFile`. |
| `SingleInstance` | Triggers single-instance manager setup; options include encryption key, exit code, and `OnSecondInstanceLaunch` callback. |
### Single Instance Workflow (`single_instance.go`)
```pseudo
func newSingleInstanceManager(app, opts):
if opts == nil -> return nil
start goroutine that reads secondInstanceBuffer and dispatches OnSecondInstanceLaunch
lock := newPlatformLock(opts.UniqueID) // OS-specific
if lock.acquire fails -> already running
return manager
func manager.notifyFirstInstance():
data := {Args, WorkingDir, AdditionalData}
payload := json.Marshal(data) or encrypt(AES-256-GCM)
lock.notify(payload)
```
- On startup, if another instance is detected, the CLI prints a warning, invokes `notifyFirstInstance`, and exits with the configured code.
- Platform locks live in `single_instance_<os>.go`, using named pipes, mutexes, or DBus depending on OS.
---
## Platform Implementations
| Platform | Entry Files | Notes |
| --- | --- | --- |
| Windows | `application_windows.go`, `webview_window_windows*.go`, `pkg/w32` | Integrates with Win32 APIs, WebView2 via `go-webview2`. Handles taskbar recreation, dark mode, accent color, custom title bars, drag/resize, `WndProcInterceptor`. |
| macOS | `application_darwin.go`, `webview_window_darwin*.go`, `pkg/mac` | Objective-C bridges (via cgo) for NSApplication, implements activation policy, app/URL events, and main thread run loops. |
| Linux | `application_linux.go`, `webview_window_linux*.go`, `pkg/application/linux_*` | GTK/WebKit2 integration, theme detection, DBus single-instance hooks. Pure Go fallback provided (`linux_purego`). |
| Runtime JS | `runtime_<os>.go` | Defines `window._wails.invoke` binding for each platform (e.g., `webkit.messageHandlers.external.postMessage` on Linux/macOS, `chrome.webview.postMessage` on Windows). |
Key responsibilities of each platform struct (e.g., `windowsApp`):
- Manage native window/class registration, system tray IDs, focus management.
- Emit platform-specific events into `applicationEvents` when OS notifications occur (power events, taskbar resets, theme changes).
- Provide platform-specific implementations of `setApplicationMenu`, `dispatchOnMainThread`, `GetFlags`, etc.
---
## Packaging & Distribution Pipeline
- **Linux NFPM**: `internal/packager/packager.go` wraps `nfpm` to parse YAML recipes and build `.deb`, `.rpm`, `.apk`, `.ipk`, or Arch packages. CLI `wails3 tool package` selects the format and output path.
- **Windows**: `msix.go`, `generate_syso.go`, and build assets templates generate installers, embedding certificate details and executable paths from `BuildAssetsOptions`.
- **macOS**: `dmg` command scaffolds DMG packaging scripts.
- **AppImage**: `GenerateAppImage` compiles a portable AppImage by invoking the bundled `linuxdeploy` scripts (`appimage_testfiles` contains test fixtures).
- **Build Info**: `tool_buildinfo.go` prints deduced module versions using the captured `BuildSettings` map.
During release automation (see `v3/tasks/release`), these commands are orchestrated by Taskfile recipes to build cross-platform artifacts.
---
## Diagnostics, Logging & Error Handling
- Logging defaults differ: debug builds use structured logging (`DefaultLogger`), production builds discard framework logs unless a logger is provided.
- `fatalHandler` sets a package-level panic handler that exits the process on unrecoverable errors (panic + formatted message).
- `App.handleError`/`handleWarning` route errors to user-provided handlers or `Logger.Error/Warn`.
- Signal handling (SIGINT/SIGTERM) ensures the watcher and the app cleanly shut down, releasing OS resources and closing windows.
- `internal/term` and `term2` provide colored console output; used heavily in CLI warnings/errors to standardize UX (`term.Warningf`, `term.Hyperlink`).
---
## Developer Workflow Checklist
1. **Familiarize with Taskfile**: Run `task -l` or `wails3 task --list` to see available workflows (`install`, `precommit`, example builds).
2. **Local CLI Install**: `task v3:install` compiles the CLI (`go install` inside `cmd/wails3`).
3. **Run Dev Mode**: `wails3 dev` (or `task dev`) spawns the watcher, sets `FRONTEND_DEVSERVER_URL`, and tails the Go backend.
4. **Build Release**: `wails3 build` → alias for `task build` (see `Taskfile.yaml` for pipeline details, including `go build`, asset bundling, templating, packaging).
5. **Packaging Tests**: Use `wails3 tool package --type deb --config ...` or `GenerateBuildAssets` to refresh installer scaffolding.
6. **Pre-commit routine**: `task v3:precommit` runs `go test ./...` and repository formatters before opening PRs.
---
## Lifecycles at a Glance
### Application Startup Timeline
1. CLI entry (user application) calls `wails.Run()` (inside user code) → constructs `application.Options`.
2. `application.New` merges defaults, sets up asset server, bindings, single-instance manager, event processor.
3. User code configures windows/services (often via `app.Window.New()` or services via `application.NewService()`).
4. `App.Run`:
- Validates no concurrent runs.
- Instantiates platform app (calls Objective-C/Win32/GTK setup).
- Starts services, mounts HTTP routes.
- Spins up channel goroutines and flushes deferred runnables.
- Enters platform main loop (`impl.run()`), bridging native events into Go.
5. Frontend loads `runtime.js` → initializes `window.wails`, fetches `/wails/flags` & `/wails/capabilities`, and begins invoking bound methods via JSON payloads.
### Shutdown Timeline
1. `App.Quit` (called from user code, signal handler, or platform event) sets `performingShutdown`.
2. Cancels `context.Context` to abort long-running service calls.
3. Executes `OnShutdown` tasks in registration order.
4. Invokes `shutdownServices` in reverse startup order; each service can release resources.
5. Releases single-instance lock, destroys windows/system trays, and calls platform `destroy()`.
6. Executes `PostShutdown` hook (if provided) then exits the process.
---
## Key Channels & Buffers
| Channel | Producer | Consumer | Purpose |
| --- | --- | --- | --- |
| `applicationEvents` (`events.go`) | Platform apps (`application_<os>.go`) and system signals | `App.Event.handleApplicationEvent` | Broadcast application-level events (startup, theme changes, power events). |
| `windowEvents` | Platform window callbacks | `App.handleWindowEvent` | Window lifecycle events (move, resize, focus). |
| `webviewRequests` | Asset server (`webview/request.go`) | `App.handleWebViewRequest` | Direct webview resource streaming. |
| `windowMessageBuffer` | Platform message pump | `App.handleWindowMessage` | Messages from WebView (prefixed `wails:`) -> service invocations or raw handlers. |
| `windowDragAndDropBuffer` | Drag/drop handlers | `App.handleDragAndDropMessage` | File drop notifications with DOM metadata. |
| `windowKeyEvents` | Accelerator handlers | `App.handleWindowKeyEvent` | Dispatch registered key bindings to the appropriate window. |
| `menuItemClicked` | Native menu callbacks | `Menu.handleMenuItemClicked` | Execute Go handlers for menu events. |
All buffers default to size 5 to absorb bursty events without blocking UI threads.
---
## Environment Variables & Configuration Hooks
| Variable | Used By | Effect |
| --- | --- | --- |
| `FRONTEND_DEVSERVER_URL` | Asset server (`GetDevServerURL`) | Redirects asset requests to external dev server instead of embedded bundle. |
| `WAILS_VITE_PORT` | `commands.Dev` | Controls Vite dev server port advertised to frontend tasks. |
| `NO_COLOR` | Task runner | Disables colored CLI output. |
| `WAILSENV` | User applications (common pattern) | Choose between dev/prod logic when bootstrapping `Options`. |
---
## Debugging Tips
- **Inspect Call Flow**: Enable verbose logging (`Options.LogLevel = slog.LevelDebug`) to trace binding calls and HTTP requests.
- **Capture Runtime JS**: Hit `http://127.0.0.1:<port>/wails/runtime.js` while running in dev to verify injected flags and capabilities.
- **Watch Service Registration**: Look for `Registering bound method` debug lines (`bindings.go`) to confirm service methods were detected.
- **Single Instance Issues**: Check platform lock files (e.g., `%AppData%\Wails\Locks` on Windows, `$XDG_RUNTIME_DIR` on Linux) and ensure the encryption key is consistent.
- **Asset Server Paths**: Use `assetserver.ServeFile` error output (logged via slog) to diagnose missing assets or MIME type issues.
---
## Extending the Codebase
1. **Adding a CLI Command**: Implement function in `internal/commands`, define flags under `internal/flags`, register in `cmd/wails3/main.go`. Document the Taskfile hook if it wraps tasks.
2. **New Service Template**: Update `internal/service/template`, ensure `gosod` placeholders map to new options, and adjust `flags.ServiceInit` accordingly.
3. **Runtime Feature Flags**: Modify `internal/runtime/runtime_<os>.go` to expose additional data via `/wails/flags`; update frontend expectations.
4. **Custom Middleware**: Provide `Options.Assets.Middleware` to inject auth/logging; remember middleware runs before internal routes, so call `next` for default behavior.
5. **Platform-Specific Fixes**: Locate corresponding `application_<os>.go` and `webview_window_<os>*.go` files. Keep cross-platform interfaces (`platformApp`, `webviewWindowImpl`) stable.
---
## Reference: Critical Files by Responsibility
| Responsibility | File(s) | Notes |
| --- | --- | --- |
| CLI bootstrap | `v3/cmd/wails3/main.go` | Command registration, browser helpers. |
| Build info capture | `v3/cmd/wails3/main.go:init` | Populates `commands.BuildSettings`. |
| Task aliasing | `v3/internal/commands/task_wrapper.go` | Rewrites `os.Args` and invokes `RunTask`. |
| Asset server core | `v3/internal/assetserver/assetserver.go` | Middleware, logging, fallback handling. |
| Application singleton | `v3/pkg/application/application.go:56` | Global `App` creation. |
| Service binding | `v3/pkg/application/bindings.go` | Reflection, aliasing, error marshaling. |
| Message bridge | `v3/pkg/application/messageprocessor*.go` | Runtime call routing. |
| Event channels | `v3/pkg/application/events.go` | Buffered channels + processors. |
| Window API | `v3/pkg/application/webview_window.go` | Platform interface and user-facing API. |
| Single-instance control | `v3/pkg/application/single_instance*.go` | Locking and IPC. |
| Platform adapters | `v3/pkg/application/application_<os>.go` | Native message loops, start/stop. |
| Packaging | `v3/internal/packager/packager.go`, `internal/commands/tool_package.go` | Linux packaging wrappers. |
---
Armed with these maps, pseudocode, and lifecycle notes, you can confidently trace any bug from CLI invocation through runtime dispatch, into platform-specific glue, and back. Use the pseudocode as a mental model, verify concrete behavior by jumping into the referenced files, and lean on the diagrams to understand how data flows between layers.

View file

@ -0,0 +1,206 @@
---
slug: blog/wails-v2-beta-for-windows
title: Wails v2 Beta for Windows
authors: [leaanthony]
tags: [wails, v2]
date: 2021-09-27
---
![wails screenshot](../../../assets/blog-images/wails.webp)
When I first announced Wails on Reddit, just over 2 years ago from a train in
Sydney, I did not expect it to get much attention. A few days later, a prolific
tech vlogger released a tutorial video, gave it a positive review and from that
point on, interest in the project has skyrocketed.
It was clear that people were excited about adding web frontends to their Go
projects, and almost immediately pushed the project beyond the proof of concept
that I had created. At the time, Wails used the
[webview](https://github.com/webview/webview) project to handle the frontend,
and the only option for Windows was the IE11 renderer. Many bug reports were
rooted in this limitation: poor JavaScript/CSS support and no dev tools to debug
it. This was a frustrating development experience but there wasn't much that
could have been done to rectify it.
For a long time, I'd firmly believed that Microsoft would eventually have to
sort out their browser situation. The world was moving on, frontend development
was booming and IE wasn't cutting it. When Microsoft announced the move to using
Chromium as the basis for their new browser direction, I knew it was only a
matter of time until Wails could use it, and move the Windows developer
experience to the next level.
Today, I am pleased to announce: **Wails v2 Beta for Windows**! There's a huge
amount to unpack in this release, so grab a drink, take a seat and we'll
begin...
### No CGO Dependency!
No, I'm not joking: _No_ _CGO_ _dependency_ 🤯! The thing about Windows is that,
unlike MacOS and Linux, it doesn't come with a default compiler. In addition,
CGO requires a mingw compiler and there's a ton of different installation
options. Removing the CGO requirement has massively simplified setup, as well as
making debugging an awful lot easier. Whilst I have put a fair bit of effort in
getting this working, the majority of the credit should go to
[John Chadwick](https://github.com/jchv) for not only starting a couple of
projects to make this possible, but also being open to someone taking those
projects and building on them. Credit also to
[Tad Vizbaras](https://github.com/tadvi) whose
[winc](https://github.com/tadvi/winc) project started me down this path.
### WebView2 Chromium Renderer
![devtools screenshot](../../../assets/blog-images/devtools.png)
Finally, Windows developers get a first class rendering engine for their
applications! Gone are the days of contorting your frontend code to work on
Windows. On top of that, you get a first-class developer tools experience!
The WebView2 component does, however, have a requirement to have the
`WebView2Loader.dll` sitting alongside the binary. This makes distribution just
that little bit more painful than we gophers are used to. All solutions and
libraries (that I know of) that use WebView2 have this dependency.
However, I'm really excited to announce that Wails applications _have no such
requirement_! Thanks to the wizardry of
[John Chadwick](https://github.com/jchv), we are able to bundle this dll inside
the binary and get Windows to load it as if it were present on disk.
Gophers rejoice! The single binary dream lives on!
### New Features
![wails-menus screenshot](../../../assets/blog-images/wails-menus.webp)
There were a lot of requests for native menu support. Wails has finally got you
covered. Application menus are now available and include support for most native
menu features. This includes standard menu items, checkboxes, radio groups,
submenus and separators.
There were a huge number of requests in v1 for the ability to have greater
control of the window itself. I'm happy to announce that there's new runtime
APIs specifically for this. It's feature-rich and supports multi-monitor
configurations. There is also an improved dialogs API: Now, you can have modern,
native dialogs with rich configuration to cater for all your dialog needs.
There is now the option to generate IDE configuration along with your project.
This means that if you open your project in a supported IDE, it will already be
configured for building and debugging your application. Currently VSCode is
supported but we hope to support other IDEs such as Goland soon.
![vscode screenshot](../../../assets/blog-images/vscode.webp)
### No requirement to bundle assets
A huge pain-point of v1 was the need to condense your entire application down to
single JS & CSS files. I'm happy to announce that for v2, there is no
requirement to bundle assets, in any way, shape or form. Want to load a local
image? Use an `<img>` tag with a local src path. Want to use a cool font? Copy
it in and add the path to it in your CSS.
> Wow, that sounds like a webserver...
Yes, it works just like a webserver, except it isn't.
> So how do I include my assets?
You just pass a single `embed.FS` that contains all your assets into your
application configuration. They don't even need to be in the top directory -
Wails will just work it out for you.
### New Development Experience
![browser screenshot](../../../assets/blog-images/browser.webp)
Now that assets don't need to be bundled, it's enabled a whole new development
experience. The new `wails dev` command will build and run your application, but
instead of using the assets in the `embed.FS`, it loads them directly from disk.
It also provides the additional features:
- Hot reload - Any changes to frontend assets will trigger and auto reload of
the application frontend
- Auto rebuild - Any changes to your Go code will rebuild and relaunch your
application
In addition to this, a webserver will start on port 34115. This will serve your
application to any browser that connects to it. All connected web browsers will
respond to system events like hot reload on asset change.
In Go, we are used to dealing with structs in our applications. It's often
useful to send structs to our frontend and use them as state in our application.
In v1, this was a very manual process and a bit of a burden on the developer.
I'm happy to announce that in v2, any application run in dev mode will
automatically generate TypeScript models for all structs that are input or
output parameters to bound methods. This enables seamless interchange of data
models between the two worlds.
In addition to this, another JS module is dynamically generated wrapping all
your bound methods. This provides JSDoc for your methods, providing code
completion and hinting in your IDE. It's really cool when you get data models
auto-imported when hitting tab in an auto-generated module wrapping your Go
code!
### Remote Templates
![remote screenshot](../../../assets/blog-images/remote.webp)
Getting an application up and running quickly was always a key goal for the
Wails project. When we launched, we tried to cover a lot of the modern
frameworks at the time: react, vue and angular. The world of frontend
development is very opinionated, fast moving and hard to keep on top of! As a
result, we found our base templates getting out of date pretty quickly and this
caused a maintenance headache. It also meant that we didn't have cool modern
templates for the latest and greatest tech stacks.
With v2, I wanted to empower the community by giving you the ability to create
and host templates yourselves, rather than rely on the Wails project. So now you
can create projects using community supported templates! I hope this will
inspire developers to create a vibrant ecosystem of project templates. I'm
really quite excited about what our developer community can create!
### In Conclusion
Wails v2 represents a new foundation for the project. The aim of this release is
to get feedback on the new approach, and to iron out any bugs before a full
release. Your input would be most welcome. Please direct any feedback to the
[v2 Beta](https://github.com/wailsapp/wails/discussions/828) discussion board.
There were many twists and turns, pivots and u-turns to get to this point. This
was due partly to early technical decisions that needed changing, and partly
because some core problems we had spent time building workarounds for were fixed
upstream: Gos embed feature is a good example. Fortunately, everything came
together at the right time, and today we have the very best solution that we can
have. I believe the wait has been worth it - this would not have been possible
even 2 months ago.
I also need to give a huge thank you :pray: to the following people because
without them, this release just wouldn't exist:
- [Misite Bao](https://github.com/misitebao) - An absolute workhorse on the
Chinese translations and an incredible bug finder.
- [John Chadwick](https://github.com/jchv) - His amazing work on
[go-webview2](https://github.com/jchv/go-webview2) and
[go-winloader](https://github.com/jchv/go-winloader) have made the Windows
version we have today possible.
- [Tad Vizbaras](https://github.com/tadvi) - Experimenting with his
[winc](https://github.com/tadvi/winc) project was the first step down the path
to a pure Go Wails.
- [Mat Ryer](https://github.com/matryer) - His support, encouragement and
feedback has really helped drive the project forward.
And finally, I'd like to give a special thank you to all the
[project sponsors](/credits#sponsors), including
[JetBrains](https://www.jetbrains.com?from=Wails), whose support drives the
project in many ways behind the scenes.
I look forward to seeing what people build with Wails in this next exciting
phase of the project!
Lea.
PS: MacOS and Linux users need not feel left out - porting to this new
foundation is actively under way and most of the hard work has already been
done. Hang in there!
PPS: If you or your company find Wails useful, please consider
[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks!

View file

@ -0,0 +1,166 @@
---
slug: blog/wails-v2-beta-for-mac
title: Wails v2 Beta for MacOS
authors: [leaanthony]
tags: [wails, v2]
date: 2021-11-08
---
![wails-mac screenshot](../../../assets/blog-images/wails-mac.webp)
Today marks the first beta release of Wails v2 for Mac! It's taken quite a while
to get to this point and I'm hoping that today's release will give you something
that's reasonably useful. There have been a number of twists and turns to get to
this point and I'm hoping, with your help, to iron out the crinkles and get the
Mac port polished for the final v2 release.
You mean this isn't ready for production? For your use case, it may well be
ready, but there are still a number of known issues so keep your eye on
[this project board](https://github.com/wailsapp/wails/projects/7) and if you
would like to contribute, you'd be very welcome!
So what's new for Wails v2 for Mac vs v1? Hint: It's pretty similar to the
Windows Beta :wink:
### New Features
![wails-menus-mac screenshot](../../../assets/blog-images/wails-menus-mac.webp)
There were a lot of requests for native menu support. Wails has finally got you
covered. Application menus are now available and include support for most native
menu features. This includes standard menu items, checkboxes, radio groups,
submenus and separators.
There were a huge number of requests in v1 for the ability to have greater
control of the window itself. I'm happy to announce that there's new runtime
APIs specifically for this. It's feature-rich and supports multi-monitor
configurations. There is also an improved dialogs API: Now, you can have modern,
native dialogs with rich configuration to cater for all your dialog needs.
### Mac Specific Options
In addition to the normal application options, Wails v2 for Mac also brings some
Mac extras:
- Make your window all funky and translucent, like all the pretty swift apps!
- Highly customisable titlebar
- We support the NSAppearance options for the application
- Simple config to auto-create an "About" menu
### No requirement to bundle assets
A huge pain-point of v1 was the need to condense your entire application down to
single JS & CSS files. I'm happy to announce that for v2, there is no
requirement to bundle assets, in any way, shape or form. Want to load a local
image? Use an `<img>` tag with a local src path. Want to use a cool font? Copy
it in and add the path to it in your CSS.
> Wow, that sounds like a webserver...
Yes, it works just like a webserver, except it isn't.
> So how do I include my assets?
You just pass a single `embed.FS` that contains all your assets into your
application configuration. They don't even need to be in the top directory -
Wails will just work it out for you.
### New Development Experience
Now that assets don't need to be bundled, it's enabled a whole new development
experience. The new `wails dev` command will build and run your application, but
instead of using the assets in the `embed.FS`, it loads them directly from disk.
It also provides the additional features:
- Hot reload - Any changes to frontend assets will trigger and auto reload of
the application frontend
- Auto rebuild - Any changes to your Go code will rebuild and relaunch your
application
In addition to this, a webserver will start on port 34115. This will serve your
application to any browser that connects to it. All connected web browsers will
respond to system events like hot reload on asset change.
In Go, we are used to dealing with structs in our applications. It's often
useful to send structs to our frontend and use them as state in our application.
In v1, this was a very manual process and a bit of a burden on the developer.
I'm happy to announce that in v2, any application run in dev mode will
automatically generate TypeScript models for all structs that are input or
output parameters to bound methods. This enables seamless interchange of data
models between the two worlds.
In addition to this, another JS module is dynamically generated wrapping all
your bound methods. This provides JSDoc for your methods, providing code
completion and hinting in your IDE. It's really cool when you get data models
auto-imported when hitting tab in an auto-generated module wrapping your Go
code!
### Remote Templates
![remote-mac screenshot](../../../assets/blog-images/remote-mac.webp)
Getting an application up and running quickly was always a key goal for the
Wails project. When we launched, we tried to cover a lot of the modern
frameworks at the time: react, vue and angular. The world of frontend
development is very opinionated, fast moving and hard to keep on top of! As a
result, we found our base templates getting out of date pretty quickly and this
caused a maintenance headache. It also meant that we didn't have cool modern
templates for the latest and greatest tech stacks.
With v2, I wanted to empower the community by giving you the ability to create
and host templates yourselves, rather than rely on the Wails project. So now you
can create projects using community supported templates! I hope this will
inspire developers to create a vibrant ecosystem of project templates. I'm
really quite excited about what our developer community can create!
### Native M1 Support
Thanks to the amazing support of [Mat Ryer](https://github.com/matryer/), the
Wails project now supports M1 native builds:
![build-darwin-arm screenshot](../../../assets/blog-images/build-darwin-arm.webp)
You can also specify `darwin/amd64` as a target too:
![build-darwin-amd screenshot](../../../assets/blog-images/build-darwin-amd.webp)
Oh, I almost forgot.... you can also do `darwin/universal`.... :wink:
![build-darwin-universal screenshot](../../../assets/blog-images/build-darwin-universal.webp)
### Cross Compilation to Windows
Because Wails v2 for Windows is pure Go, you can target Windows builds without
docker.
![build-cross-windows screenshot](../../../assets/blog-images/build-cross-windows.webp)
bu
### WKWebView Renderer
V1 relied on a (now deprecated) WebView component. V2 uses the most recent
WKWebKit component so expect the latest and greatest from Apple.
### In Conclusion
As I'd said in the Windows release notes, Wails v2 represents a new foundation
for the project. The aim of this release is to get feedback on the new approach,
and to iron out any bugs before a full release. Your input would be most
welcome! Please direct any feedback to the
[v2 Beta](https://github.com/wailsapp/wails/discussions/828) discussion board.
And finally, I'd like to give a special thank you to all the
[project sponsors](/credits#sponsors), including
[JetBrains](https://www.jetbrains.com?from=Wails), whose support drive the
project in many ways behind the scenes.
I look forward to seeing what people build with Wails in this next exciting
phase of the project!
Lea.
PS: Linux users, you're next!
PPS: If you or your company find Wails useful, please consider
[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks!

View file

@ -0,0 +1,129 @@
---
slug: blog/wails-v2-beta-for-linux
title: Wails v2 Beta for Linux
authors: [leaanthony]
tags: [wails, v2]
date: 2022-02-22
---
![wails-linux screenshot](../../../assets/blog-images/wails-linux.webp)
I'm pleased to finally announce that Wails v2 is now in beta for Linux! It is
somewhat ironic that the very first experiments with v2 was on Linux and yet it
has ended up as the last release. That being said, the v2 we have today is very
different from those first experiments. So without further ado, let's go over
the new features:
### New Features
![wails-menus-linux screenshot](../../../assets/blog-images/wails-menus-linux.webp)
There were a lot of requests for native menu support. Wails has finally got you
covered. Application menus are now available and include support for most native
menu features. This includes standard menu items, checkboxes, radio groups,
submenus and separators.
There were a huge number of requests in v1 for the ability to have greater
control of the window itself. I'm happy to announce that there's new runtime
APIs specifically for this. It's feature-rich and supports multi-monitor
configurations. There is also an improved dialogs API: Now, you can have modern,
native dialogs with rich configuration to cater for all your dialog needs.
### No requirement to bundle assets
A huge pain-point of v1 was the need to condense your entire application down to
single JS & CSS files. I'm happy to announce that for v2, there is no
requirement to bundle assets, in any way, shape or form. Want to load a local
image? Use an `<../../../assets/blog-images>` tag with a local src path. Want to
use a cool font? Copy it in and add the path to it in your CSS.
> Wow, that sounds like a webserver...
Yes, it works just like a webserver, except it isn't.
> So how do I include my assets?
You just pass a single `embed.FS` that contains all your assets into your
application configuration. They don't even need to be in the top directory -
Wails will just work it out for you.
### New Development Experience
Now that assets don't need to be bundled, it's enabled a whole new development
experience. The new `wails dev` command will build and run your application, but
instead of using the assets in the `embed.FS`, it loads them directly from disk.
It also provides the additional features:
- Hot reload - Any changes to frontend assets will trigger an auto reload of the
application frontend
- Auto rebuild - Any changes to your Go code will rebuild and relaunch your
application
In addition to this, a webserver will start on port 34115. This will serve your
application to any browser that connects to it. All connected web browsers will
respond to system events like hot reload on asset change.
In Go, we are used to dealing with structs in our applications. It's often
useful to send structs to our frontend and use them as state in our application.
In v1, this was a very manual process and a bit of a burden on the developer.
I'm happy to announce that in v2, any application run in dev mode will
automatically generate TypeScript models for all structs that are input or
output parameters to bound methods. This enables seamless interchange of data
models between the two worlds.
In addition to this, another JS module is dynamically generated wrapping all
your bound methods. This provides JSDoc for your methods, providing code
completion and hinting in your IDE. It's really cool when you get data models
auto-imported when hitting tab in an auto-generated module wrapping your Go
code!
### Remote Templates
![remote-linux screenshot](../../../assets/blog-images/remote-linux.webp)
Getting an application up and running quickly was always a key goal for the
Wails project. When we launched, we tried to cover a lot of the modern
frameworks at the time: react, vue and angular. The world of frontend
development is very opinionated, fast moving and hard to keep on top of! As a
result, we found our base templates getting out of date pretty quickly and this
caused a maintenance headache. It also meant that we didn't have cool modern
templates for the latest and greatest tech stacks.
With v2, I wanted to empower the community by giving you the ability to create
and host templates yourselves, rather than rely on the Wails project. So now you
can create projects using community supported templates! I hope this will
inspire developers to create a vibrant ecosystem of project templates. I'm
really quite excited about what our developer community can create!
### Cross Compilation to Windows
Because Wails v2 for Windows is pure Go, you can target Windows builds without
docker.
![build-cross-windows screenshot](../../../assets/blog-images/linux-build-cross-windows.webp)
### In Conclusion
As I'd said in the Windows release notes, Wails v2 represents a new foundation
for the project. The aim of this release is to get feedback on the new approach,
and to iron out any bugs before a full release. Your input would be most
welcome! Please direct any feedback to the
[v2 Beta](https://github.com/wailsapp/wails/discussions/828) discussion board.
Linux is **hard** to support. We expect there to be a number of quirks with the
beta. Please help us to help you by filing detailed bug reports!
Finally, I'd like to give a special thank you to all the
[project sponsors](/credits#sponsors) whose support drives the project in many
ways behind the scenes.
I look forward to seeing what people build with Wails in this next exciting
phase of the project!
Lea.
PS: The v2 release isn't far off now!
PPS: If you or your company find Wails useful, please consider
[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks!

View file

@ -0,0 +1,198 @@
---
slug: blog/wails-v2-released
title: Wails v2 Released
authors: [leaanthony]
tags: [wails, v2]
date: 2022-09-22
---
![montage screenshot](../../../assets/blog-images/montage.png)
# It's here!
Today marks the release of [Wails](https://wails.io) v2. It's been about 18
months since the first v2 alpha and about a year from the first beta release.
I'm truly grateful to everyone involved in the evolution of the project.
Part of the reason it took that long was due to wanting to get to some
definition of completeness before officially calling it v2. The truth is,
there's never a perfect time to tag a release - there's always outstanding
issues or "just one more" feature to squeeze in. What tagging an imperfect major
release does do, however, is to provide a bit of stability for users of the
project, as well as a bit of a reset for the developers.
This release is more than I'd ever expected it to be. I hope it gives you as
much pleasure as it has given us to develop it.
# What _is_ Wails?
If you are unfamiliar with Wails, it is a project that enables Go programmers to
provide rich frontends for their Go programs using familiar web technologies.
It's a lightweight, Go alternative to Electron. Much more information can be
found on the [official site](https://wails.io/docs/introduction).
# What's new?
The v2 release is a huge leap forward for the project, addressing many of the
pain points of v1. If you have not read any of the blog posts on the Beta
releases for [macOS](/blog/wails-v2-beta-for-mac),
[Windows](/blog/wails-v2-beta-for-windows) or
[Linux](/blog/wails-v2-beta-for-linux), then I encourage you to do so as it
covers all the major changes in more detail. In summary:
- Webview2 component for Windows that supports modern web standards and
debugging capabilities.
- [Dark / Light theme](https://wails.io/docs/reference/options#theme) +
[custom theming](https://wails.io/docs/reference/options#customtheme) on Windows.
- Windows now has no CGO requirements.
- Out-of-the-box support for Svelte, Vue, React, Preact, Lit & Vanilla project
templates.
- [Vite](https://vitejs.dev/) integration providing a hot-reload development
environment for your application.
- Native application
[menus](https://wails.io/docs/guides/application-development#application-menu) and
[dialogs](https://wails.io/docs/reference/runtime/dialog).
- Native window translucency effects for
[Windows](https://wails.io/docs/reference/options#windowistranslucent) and
[macOS](https://wails.io/docs/reference/options#windowistranslucent-1). Support for Mica &
Acrylic backdrops.
- Easily generate an [NSIS installer](https://wails.io/docs/guides/windows-installer) for
Windows deployments.
- A rich [runtime library](https://wails.io/docs/reference/runtime/intro) providing utility
methods for window manipulation, eventing, dialogs, menus and logging.
- Support for [obfuscating](https://wails.io/docs/guides/obfuscated) your application using
[garble](https://github.com/burrowers/garble).
- Support for compressing your application using [UPX](https://upx.github.io/).
- Automatic TypeScript generation of Go structs. More info
[here](https://wails.io/docs/howdoesitwork#calling-bound-go-methods).
- No extra libraries or DLLs are required to be shipped with your application.
For any platform.
- No requirement to bundle frontend assets. Just develop your application like
any other web application.
# Credit & Thanks
Getting to v2 has been a huge effort. There have been ~2.2K commits by 89
contributors between the initial alpha and the release today, and many, many
more that have provided translations, testing, feedback and help on the
discussion forums as well as the issue tracker. I'm so unbelievably grateful to
each one of you. I'd also like to give an extra special thank you to all the
project sponsors who have provided guidance, advice and feedback. Everything you
do is hugely appreciated.
There are a few people I'd like to give special mention to:
Firstly, a **huge** thank you to [@stffabi](https://github.com/stffabi) who has
provided so many contributions which we all benefit from, as well as providing a
lot of support on many issues. He has provided some key features such as the
external dev server support which transformed our dev mode offering by allowing
us to hook into [Vite](https://vitejs.dev/)'s superpowers. It's fair to say that
Wails v2 would be a far less exciting release without his
[incredible contributions](https://github.com/wailsapp/wails/commits?author=stffabi&since=2020-01-04).
Thank you so much @stffabi!
I'd also like to give a huge shout-out to
[@misitebao](https://github.com/misitebao) who has tirelessly been maintaining
the website, as well as providing Chinese translations, managing Crowdin and
helping new translators get up to speed. This is a hugely important task, and
I'm extremely grateful for all the time and effort put into this! You rock!
Last, but not least, a huge thank you to Mat Ryer who has provided advice and
support during the development of v2. Writing xBar together using an early Alpha
of v2 was helpful in shaping the direction of v2, as well as give me an
understanding of some design flaws in the early releases. I'm happy to announce
that as of today, we will start to port xBar to Wails v2, and it will become the
flagship application for the project. Cheers Mat!
# Lessons Learnt
There are a number of lessons learnt in getting to v2 that will shape
development moving forward.
## Smaller, Quicker, Focused Releases
In the course of developing v2, there were many features and bug fixes that were
developed on an ad-hoc basis. This led to longer release cycles and were harder
to debug. Moving forward, we are going to create releases more often that will
include a reduced number of features. A release will involve updates to
documentation as well as thorough testing. Hopefully, these smaller, quicker,
focussed releases will lead to fewer regressions and better quality
documentation.
## Encourage Engagement
When starting this project, I wanted to immediately help everyone who had a
problem. Issues were "personal" and I wanted them resolved as quickly as
possible. This is unsustainable and ultimately works against the longevity of
the project. Moving forward, I will be giving more space for people to get
involved in answering questions and triaging issues. It would be good to get
some tooling to help with this so if you have any suggestions, please join in
the discussion [here](https://github.com/wailsapp/wails/discussions/1855).
## Learning to say No
The more people that engage with an Open Source project, the more requests there
will be for additional features that may or may not be useful to the majority of
people. These features will take an initial amount of time to develop and debug,
and incur an ongoing maintenance cost from that point on. I myself am the most
guilty of this, often wanting to "boil the sea" rather than provide the minimum
viable feature. Moving forward, we will need to say "No" a bit more to adding
core features and focus our energies on a way to empower developers to provide
that functionality themselves. We are looking seriously into plugins for this
scenario. This will allow anyone to extend the project as they see fit, as well
as providing an easy way to contribute towards the project.
# Looking to the Future
There are so many core features we are looking at to add to Wails in the next
major development cycle already. The
[roadmap](https://github.com/wailsapp/wails/discussions/1484) is full of
interesting ideas, and I'm keen to start work on them. One of the big asks has
been for multiple window support. It's a tricky one and to do it right, and we
may need to look at providing an alternative API, as the current one was not
designed with this in mind. Based on some preliminary ideas and feedback, I
think you'll like where we're looking to go with it.
I'm personally very excited at the prospect of getting Wails apps running on
mobile. We already have a demo project showing that it is possible to run a
Wails app on Android, so I'm really keen to explore where we can go with this!
A final point I'd like to raise is that of feature parity. It has long been a
core principle that we wouldn't add anything to the project without there being
full cross-platform support for it. Whilst this has proven to be (mainly)
achievable so far, it has really held the project back in releasing new
features. Moving forward, we will be adopting a slightly different approach: any
new feature that cannot be immediately released for all platforms will be
released under an experimental configuration or API. This allows early adopters
on certain platforms to try the feature and provide feedback that will feed into
the final design of the feature. This, of course, means that there are no
guarantees of API stability until it is fully supported by all the platforms it
can be supported on, but at least it will unblock development.
# Final Words
I'm really proud of what we've been able to achieve with the V2 release. It's
amazing to see what people have already been able to build using the beta
releases so far. Quality applications like [Varly](https://varly.app/),
[Surge](https://getsurge.io/) and [October](https://october.utf9k.net/). I
encourage you to check them out.
This release was achieved through the hard work of many contributors. Whilst it
is free to download and use, it has not come about through zero cost. Make no
mistakes, this project has come at considerable cost. It has not only been my
time and the time of each and every contributor, but also the cost of absence
from friends and families of each of those people too. That's why I'm extremely
grateful for every second that has been dedicated to making this project happen.
The more contributors we have, the more this effort can be spread out and the
more we can achieve together. I'd like to encourage you all to pick one thing
that you can contribute, whether it is confirming someone's bug, suggesting a
fix, making a documentation change or helping out someone who needs it. All of
these small things have such a huge impact! It would be so awesome if you too
were part of the story in getting to v3.
Enjoy!
&dash; Lea
PS: If you or your company find Wails useful, please consider
[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks!

View file

@ -0,0 +1,256 @@
---
slug: blog/the-road-to-wails-v3
title: The Road to Wails v3
authors: [leaanthony]
tags: [wails, v3]
date: 2023-01-17
---
![multiwindow screenshot](../../../assets/blog-images/multiwindow.webp)
# Introduction
Wails is a project that simplifies the ability to write cross-platform desktop
applications using Go. It uses native webview components for the frontend (not
embedded browsers), bringing the power of the world's most popular UI system to
Go, whilst remaining lightweight.
Version 2 was released on the 22nd of September 2022 and brought with it a lot
of enhancements including:
- Live development, leveraging the popular Vite project
- Rich functionality for managing windows and creating menus
- Microsoft's WebView2 component
- Generation of Typescript models that mirror your Go structs
- Creating of NSIS Installer
- Obfuscated builds
Right now, Wails v2 provides powerful tooling for creating rich, cross-platform
desktop applications.
This blog post aims to look at where the project is at right now and what we can
improve on moving forward.
# Where are we now?
It's been incredible to see the popularity of Wails rising since the v2 release.
I'm constantly amazed by the creativity of the community and the wonderful
things that are being built with it. With more popularity, comes more eyes on
the project. And with that, more feature requests and bug reports.
Over time, I've been able to identify some of the most pressing issues facing
the project. I've also been able to identify some of the things that are holding
the project back.
## Current issues
I've identified the following areas that I feel are holding the project back:
- The API
- Bindings generation
- The Build System
### The API
The API to build a Wails application currently consists of 2 parts:
- The Application API
- The Runtime API
The Application API famously has only 1 function: `Run()` which takes a heap of
options which govern how the application will work. Whilst this is very simple
to use, it is also very limiting. It is a "declarative" approach which hides a
lot of the underlying complexity. For instance, there is no handle to the main
window, so you can't interact with it directly. For that, you need to use the
Runtime API. This is a problem when you start to want to do more complex things
like create multiple windows.
The Runtime API provides a lot of utility functions for the developer. This
includes:
- Window management
- Dialogs
- Menus
- Events
- Logs
There are a number of things I am not happy with the Runtime API. The first is
that it requires a "context" to be passed around. This is both frustrating and
confusing for new developers who pass in a context and then get a runtime error.
The biggest issue with the Runtime API is that it was designed for applications
that only use a single window. Over time, the demand for multiple windows has
grown and the API is not well suited to this.
### Thoughts on the v3 API
Wouldn't it be great if we could do something like this?
```go
func main() {
app := wails.NewApplication(options.App{})
myWindow := app.NewWindow(options.Window{})
myWindow.SetTitle("My Window")
myWindow.On(events.Window.Close, func() {
app.Quit()
})
app.Run()
}
```
This programmatic approach is far more intuitive and allows the developer to
interact with the application elements directly. All current runtime methods for
windows would simply be methods on the window object. For the other runtime
methods, we could move them to the application object like so:
```go
app := wails.NewApplication(options.App{})
app.NewInfoDialog(options.InfoDialog{})
app.Log.Info("Hello World")
```
This is a much more powerful API which will allow for more complex applications
to be built. It also allows for the creation of multiple windows,
[the most up-voted feature on GitHub](https://github.com/wailsapp/wails/issues/1480):
```go
func main() {
app := wails.NewApplication(options.App{})
myWindow := app.NewWindow(options.Window{})
myWindow.SetTitle("My Window")
myWindow.On(events.Window.Close, func() {
app.Quit()
})
myWindow2 := app.NewWindow(options.Window{})
myWindow2.SetTitle("My Window 2")
myWindow2.On(events.Window.Close, func() {
app.Quit()
})
app.Run()
}
```
### Bindings generation
One of the key features of Wails is generating bindings for your Go methods so
they may be called from Javascript. The current method for doing this is a bit
of a hack. It involves building the application with a special flag and then
running the resultant binary which uses reflection to determine what has been
bound. This leads to a bit of a chicken and egg situation: You can't build the
application without the bindings and you can't generate the bindings without
building the application. There are many ways around this but the best one would
be not to use this approach at all.
There were a number of attempts at writing a static analyser for Wails projects
but they didn't get very far. In more recent times, it has become slightly
easier to do this with more material available on the subject.
Compared to reflection, the AST approach is much faster however it is
significantly more complicated. To start with, we may need to impose certain
constraints on how to specify bindings in the code. The goal is to support the
most common use cases and then expand it later on.
### The Build System
Like the declarative approach to the API, the build system was created to hide
the complexities of building a desktop application. When you run `wails build`,
it does a lot of things behind the scenes:
- Builds the backend binary for bindings and generates the bindings
- Installs the frontend dependencies
- Builds the frontend assets
- Determines if the application icon is present and if so, embeds it
- Builds the final binary
- If the build is for `darwin/universal` it builds 2 binaries, one for
`darwin/amd64` and one for `darwin/arm64` and then creates a fat binary using
`lipo`
- If compression is required, it compresses the binary with UPX
- Determines if this binary is to be packaged and if so:
- Ensures the icon and application manifest are compiled into the binary
(Windows)
- Builds out the application bundle, generates the icon bundle and copies it,
the binary and Info.plist to the application bundle (Mac)
- If an NSIS installer is required, it builds it
This entire process, whilst very powerful, is also very opaque. It is very
difficult to customise it and it is very difficult to debug.
To address this in v3, I would like to move to a build system that exists
outside of Wails. After using [Task](https://taskfile.dev/) for a while, I am a
big fan of it. It is a great tool for configuring build systems and should be
reasonably familiar to anyone who has used Makefiles.
The build system would be configured using a `Taskfile.yml` file which would be
generated by default with any of the supported templates. This would have all of
the steps required to do all the current tasks, such as building or packaging
the application, allowing for easy customisation.
There will be no external requirement for this tooling as it would form part of
the Wails CLI. This means that you can still use `wails build` and it will do
all the things it does today. However, if you want to customise the build
process, you can do so by editing the `Taskfile.yml` file. It also means you can
easily understand the build steps and use your own build system if you wish.
The missing piece in the build puzzle is the atomic operations in the build
process, such as icon generation, compression and packaging. To require a bunch
of external tooling would not be a great experience for the developer. To
address this, the Wails CLI will provide all these capabilities as part of the
CLI. This means that the builds still work as expected, with no extra external
tooling, however you can replace any step of the build with any tool you like.
This will be a much more transparent build system which will allow for easier
customisation and address a lot of the issues that have been raised around it.
## The Payoff
These positive changes will be a huge benefit to the project:
- The new API will be much more intuitive and will allow for more complex
applications to be built.
- Using static analysis for bindings generation will be much faster and reduce a
lot of the complexity around the current process.
- Using an established, external build system will make the build process
completely transparent, allowing for powerful customisation.
Benefits to the project maintainers are:
- The new API will be much easier to maintain and adapt to new features and
platforms.
- The new build system will be much easier to maintain and extend. I hope this
will lead to a new ecosystem of community driven build pipelines.
- Better separation of concerns within the project. This will make it easier to
add new features and platforms.
## The Plan
A lot of the experimentation for this has already been done and it's looking
good. There is no current timeline for this work but I'm hoping by the end of Q1
2023, there will be an alpha release for Mac to allow the community to test,
experiment with and provide feedback.
## Summary
- The v2 API is declarative, hides a lot from the developer and not suitable for
features such as multiple windows. A new API will be created which will be
simpler, intuitive and more powerful.
- The build system is opaque and difficult to customise so we will move to an
external build system which will open it all up.
- The bindings generation is slow and complex so we will move to static analysis
which will remove a lot of the complexity the current method has.
There has been a lot of work put into the guts of v2 and it's solid. It's now
time to address the layer on top of it and make it a much better experience for
the developer.
I hope you are as excited about this as I am. I'm looking forward to hearing
your thoughts and feedback.
Regards,
&dash; Lea
PS: If you or your company find Wails useful, please consider
[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks!
PPS: Yes, that's a genuine screenshot of a multi-window application built with
Wails. It's not a mockup. It's real. It's awesome. It's coming soon.

View file

@ -0,0 +1,52 @@
---
title: Alpha 10 Released - A New Chapter for Wails
description: Announcing Wails v3 Alpha 10 and our new daily release strategy
date: 2024-12-03
author: The Wails Team
---
# Alpha 10 Released - A New Chapter for Wails
We're thrilled to announce the release of **Wails v3 Alpha 10** - and it's big! While our release cadence may have seemed slow recently, there's been an incredible amount of work happening behind the scenes. Today marks not just another release, but a shift in how we approach the development of Wails.
## Why the Wait?
Like many development teams, we fell into the trap of trying to achieve perfection before each release. We wanted to squash every bug, polish every feature, and ensure everything was just right. But here's the thing - software is never truly bug-free, and waiting for that mythical state only delays getting improvements into your hands.
## Our New Release Strategy
Starting today, we're adopting a **daily release strategy**. Any new changes merged during the day will be released at the end of that day. This approach will:
- Get bug fixes and features to you faster
- Provide more frequent feedback loops
- Speed up our journey to Beta
- Make the development process more transparent
This means you might see more frequent, smaller releases rather than occasional large ones. We believe this will benefit everyone in the Wails community.
## How You Can Help
We've been overwhelmed by the number of people wanting to contribute to Wails! To make it easier for contributors to get involved, we're implementing a new system:
- Firstly, we are opening up bug reporting for alpha releases. The frequent releases allow us to do this.
- Issues marked with **"Ready for Work"** are open for community contributions
- These issues have clear requirements and are ready to be tackled
- Most importantly, **we need people testing PRs** - this is one of the most valuable contributions you can make
Testing doesn't require deep knowledge of the codebase, but it provides immense value by ensuring changes work across different environments and use cases.
## What's Next?
With our new daily release strategy, expect to see rapid progress toward Beta. We're committed to maintaining momentum and getting Wails v3 to a stable release as quickly as possible without sacrificing quality.
And who knows? There might be a few surprises along the way... 😉
## Thank You
To everyone who has contributed code, tested releases, reported bugs, or simply used Wails - thank you. Your support and feedback drive this project forward.
Here's to more frequent releases, faster iteration, and an exciting journey ahead!
---
*Want to get involved? Check out our [GitHub repository](https://github.com/wailsapp/wails) for issues marked "ready for work", or join our community to help test the latest changes.*

View file

@ -0,0 +1,802 @@
---
title: Changelog
---
Legend:
-  - macOS
- ⊞ - Windows
- 🐧 - Linux
/*--
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `Added` for new features.
- `Changed` for changes in existing functionality.
- `Deprecated` for soon-to-be removed features.
- `Removed` for now removed features.
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.
*/
/*
** PLEASE DO NOT UPDATE THIS FILE **
Updates should be added to `v3/UNRELEASED_CHANGELOG.md`
Thank you!
*/
## [Unreleased]
## v3.0.0-alpha.31 - 2025-09-27
## Fixed
- Windows: Flicker of window at start and hidden windows being shown incorrectly in [PR](https://github.com/wailsapp/wails/pull/4600) by @leaanthony.
- Fixed Wayland window size maximising issues (https://github.com/wailsapp/wails/issues/4429) by [@samstanier](https://github.com/samstanier)
## v3.0.0-alpha.30 - 2025-09-26
## Fixed
- Fixed Wayland window size maximising issues (https://github.com/wailsapp/wails/issues/4429) by [@samstanier](https://github.com/samstanier)
## v3.0.0-alpha.29 - 2025-09-25
## Added
- macOS: Shows native window controls in the menu bar in [#4588](https://github.com/wailsapp/wails/pull/4588) by @nidib
- Add macOS Dock service to hide/show app icon in the dock @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4451)
## Changed
- macOS: Use `visibleFrame` instead of `frame` for window centering to exclude menu bar and dock areas
## Fixed
- Fixed redefinition error for liquid glass demo in [#4542](https://github.com/wailsapp/wails/pull/4542) by @Etesam913
- Fixed issue where AssetServer can crash on MacOS in [#4576](https://github.com/wailsapp/wails/pull/4576) by @jghiloni
- Fixed compilation issue when building with NextJs. Fixed in [#4585](https://github.com/wailsapp/wails/pull/4585) by @rev42
- Fixed pipelines for nightly release in [#4597](https://github.com/wailsapp/wails/pull/4597) by @riadafridishibly
## v3.0.0-alpha.29 - 2025-09-25
## Added
- Add macOS Dock service to hide/show app icon in the dock @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4451)
## Changed
- macOS: Use `visibleFrame` instead of `frame` for window centering to exclude menu bar and dock areas
## Fixed
- Fixed redefinition error for liquid glass demo in [#4542](https://github.com/wailsapp/wails/pull/4542) by @Etesam913
- Fixed issue where AssetServer can crash on MacOS in [#4576](https://github.com/wailsapp/wails/pull/4576) by @jghiloni
- Fixed compilation issue when building with NextJs. Fixed in [#4585](https://github.com/wailsapp/wails/pull/4585) by @rev42
- Fixed pipelines for nightly release in [#4597](https://github.com/wailsapp/wails/pull/4597) by @riadafridishibly
## v3.0.0-alpha.27 - 2025-09-07
## Fixed
- Fixed redefinition error for liquid glass demo in [#4542](https://github.com/wailsapp/wails/pull/4542) by @Etesam913
## v3.0.0-alpha.26 - 2025-08-24
## Added
- Add native Liquid Glass effect support for macOS with NSGlassEffectView (macOS 15.0+) and NSVisualEffectView fallback, including comprehensive material customization options by @leaanthony in [#4534](https://github.com/wailsapp/wails/pull/4534)
## v3.0.0-alpha.25 - 2025-08-16
## Changed
- When running `wails3 update build-assets` with the `-config` parameter, values set via the `-product*` parameters are
no longer ignored, and override the config value.
## v3.0.0-alpha.24 - 2025-08-13
## Added
- Browser URL Sanitisation by @leaanthony in [#4500](https://github.dev/wailsapp/wails/pull/4500). Based on [#4484](https://github.com/wailsapp/wails/pull/4484) by @APShenkin.
## v3.0.0-alpha.23 - 2025-08-11
## Fixed
- Fix SetBackgroundColour on Windows by @PPTGamer in [PR](https://github.com/wailsapp/wails/pull/4492)
## v3.0.0-alpha.22 - 2025-08-10
## Added
- Add Content Protection on Windows/Mac by [@leaanthony](https://github.com/leaanthony) based on the original work of [@Taiterbase](https://github.com/Taiterbase) in this [PR](https://github.com/wailsapp/wails/pull/4241)
- Add support for passing CLI variables to Task commands through `wails3 build` and `wails3 package` aliases (#4422) by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/4488)
## Changed
- `window.NativeWindowHandle()` -> `window.NativeWindow()` by @leaanthony in [#4471](https://github.com/wailsapp/wails/pull/4471)
- Refactor internal window handling by @leaanthony in [#4471](https://github.com/wailsapp/wails/pull/4471)
+ Fix extra-broad Linux package dependencies, fix outdated RPM dependencies.
## v3.0.0-alpha.21 - 2025-08-07
## Fixed
- Update docs to reflect changes from Manager API Refactoring by @yulesxoxo in [PR #4476](https://github.com/wailsapp/wails/pull/4476)
- Fix Linux .desktop file appicon variable in Linux taskfile [PR #4477](https://github.com/wailsapp/wails/pull/4477)
## v3.0.0-alpha.20 - 2025-08-06
## Fixed
- Update docs to reflect changes from Manager API Refactoring by @yulesxoxo in [PR #4476](https://github.com/wailsapp/wails/pull/4476)
## v3.0.0-alpha.19 - 2025-08-05
## Added
- Support for dropzones with event sourcing dropped element data [@atterpac](https://github.com/atterpac) in [#4318](https://github.com/wailsapp/wails/pull/4318)
- Added `AdditionalLaunchArgs` to `WindowsWindow` options to allow for additional command line arguments to be passed to the WebView2 browser. in [PR](https://github.com/wailsapp/wails/pull/4467)
- Added Run go mod tidy automatically after wails init [@triadmoko](https://github.com/triadmoko) in [PR](https://github.com/wailsapp/wails/pull/4286)
- Windows Snapassist feature by @leaanthony in [PR](https://github.dev/wailsapp/wails/pull/4463)
## Fixed
- Fix Windows nil pointer dereference bug reported in [#4456](https://github.com/wailsapp/wails/issues/4456) by @leaanthony in [#4460](https://github.com/wailsapp/wails/pull/4460)
- Add support for `allowsBackForwardNavigationGestures` in macOS WKWebView to enable two-finger swipe navigation gestures (#1857)
- Fixes issue where onClick didn't work for menu items initially set as disabled by @leaanthony in [PR #4469](https://github.com/wailsapp/wails/pull/4469). Thanks to @IanVS for the initial investigation.
- Fix Vite server not being cleaned up when build fails (#4403)
- Fixed panic when closing or cancelling a `SaveFileDialog` on windows. Fixed in [PR](https://github.com/wailsapp/wails/pull/4284) by @hkhere
- Fixed HTML level drag and drop on Windows by [@mbaklor](https://github.com/mbaklor) in [#4259](https://github.com/wailsapp/wails/pull/4259)
## v3.0.0-alpha.18 - 2025-08-03
## Added
- Added `AdditionalLaunchArgs` to `WindowsWindow` options to allow for additional command line arguments to be passed to the WebView2 browser. in [PR](https://github.com/wailsapp/wails/pull/4467)
- Added Run go mod tidy automatically after wails init [@triadmoko](https://github.com/triadmoko) in [PR](https://github.com/wailsapp/wails/pull/4286)
- Windows Snapassist feature by @leaanthony in [PR](https://github.dev/wailsapp/wails/pull/4463)
## Fixed
- Add support for `allowsBackForwardNavigationGestures` in macOS WKWebView to enable two-finger swipe navigation gestures (#1857)
- Fixes issue where onClick didn't work for menu items initially set as disabled by @leaanthony in [PR #4469](https://github.com/wailsapp/wails/pull/4469). Thanks to @IanVS for the initial investigation.
- Fix Vite server not being cleaned up when build fails (#4403)
## v3.0.0-alpha.17 - 2025-07-31
## Fixed
- Fixed notification parsing on Windows @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4450)
## v3.0.0-alpha.16 - 2025-07-25
## Added
- Add Windows `getAccentColor` implementation by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/4427)
## v3.0.0-alpha.15 - 2025-07-25
## Added
- Add Windows `getAccentColor` implementation by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/4427)
## v3.0.0-alpha.14 - 2025-07-25
## Added
- Windows dark theme menus + menubar. By @leaanthony in [a29b4f0861b1d0a700e9eb213c6f1076ec40efd5](https://github.com/wailsapp/wails/commit/a29b4f0861b1d0a700e9eb213c6f1076ec40efd5)
- Rename built-in services for clearer JS/TS bindings by @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4405)
## v3.0.0-alpha.12 - 2025-07-15
### Added
- `app.Env.GetAccentColor` to get the accent color of a user's system. Works on MacOS. by [@etesam913](https://github.com/etesam913)
- Add `window.ToggleFrameless()` api by [@atterpac](https://github.com/atterpac) in [#4137](https://github.com/wailsapp/wails/pull/4137)
### Fixed
- Fixed doctor command to check for Windows SDK dependencies by [@kodumulo](https://github.com/kodumulo) in [#4390](https://github.com/wailsapp/wails/issues/4390)
## v3.0.0-alpha.11 - 2025-07-12
## Added
- Add distribution-specific build dependencies for Linux by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/4345)
- Added bindings guide by @atterpac in [PR](https://github.com/wailsapp/wails/pull/4404)
## v3.0.0-alpha.10 - 2025-07-06
### Breaking Changes
- **Manager API Refactoring**: Reorganized application API from flat structure to organized managers for better code organization and discoverability by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359)
- `app.NewWebviewWindow()` → `app.Window.New()`
- `app.CurrentWindow()` → `app.Window.Current()`
- `app.GetAllWindows()` → `app.Window.GetAll()`
- `app.WindowByName()` → `app.Window.GetByName()`
- `app.EmitEvent()` → `app.Event.Emit()`
- `app.OnApplicationEvent()` → `app.Event.OnApplicationEvent()`
- `app.OnWindowEvent()` → `app.Event.OnWindowEvent()`
- `app.SetApplicationMenu()` → `app.Menu.SetApplicationMenu()`
- `app.OpenFileDialog()` → `app.Dialog.OpenFile()`
- `app.SaveFileDialog()` → `app.Dialog.SaveFile()`
- `app.MessageDialog()` → `app.Dialog.Message()`
- `app.InfoDialog()` → `app.Dialog.Info()`
- `app.WarningDialog()` → `app.Dialog.Warning()`
- `app.ErrorDialog()` → `app.Dialog.Error()`
- `app.QuestionDialog()` → `app.Dialog.Question()`
- `app.NewSystemTray()` → `app.SystemTray.New()`
- `app.GetSystemTray()` → `app.SystemTray.Get()`
- `app.ShowContextMenu()` → `app.ContextMenu.Show()`
- `app.RegisterKeybinding()` → `app.KeyBinding.Add()`
- `app.UnregisterKeybinding()` → `app.KeyBinding.Remove()`
- `app.GetPrimaryScreen()` → `app.Screen.GetPrimary()`
- `app.GetAllScreens()` → `app.Screen.GetAll()`
- `app.BrowserOpenURL()` → `app.Browser.OpenURL()`
- `app.Environment()` → `app.Env.Info()`
- `app.ClipboardGetText()` → `app.Clipboard.Text()`
- `app.ClipboardSetText()` → `app.Clipboard.SetText()`
- Renamed Service methods: `Name` -> `ServiceName`, `OnStartup` -> `ServiceStartup`, `OnShutdown` -> `ServiceShutdown` by [@leaanthony](https://github.com/leaanthony)
- Moved `Path` and `Paths` methods to `application` package by [@leaanthony](https://github.com/leaanthony)
- The application menu is now macOS only by [@leaanthony](https://github.com/leaanthony)
### Added
- **Organized Testing Infrastructure**: Moved Docker test files to dedicated `test/docker/` directory with optimized images and enhanced build reliability by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359)
- **Improved Resource Management Patterns**: Added proper event handler cleanup and context-aware goroutine management in examples by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359)
- Support aarch64 AppImage builds by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981)
- Add diagnostics section to `wails doctor` by [@leaanthony](https://github.com/leaanthony)
- Add window to context when calling a service method by [@leaanthony](https://github.com/leaanthony)
- Add `window-call` example to demonstrate how to know which window is calling a service by [@leaanthony](https://github.com/leaanthony)
- New Menu guide by [@leaanthony](https://github.com/leaanthony)
- Better panic handling by [@leaanthony](https://github.com/leaanthony)
- New Menu guide by [@leaanthony](https://github.com/leaanthony)
- Add doc comments for Service API by [@fbbdev](https://github.com/fbbdev) in [#4024](https://github.com/wailsapp/wails/pull/4024)
- Add function `application.NewServiceWithOptions` to initialise services with additional configuration by [@leaanthony](https://github.com/leaanthony) in [#4024](https://github.com/wailsapp/wails/pull/4024)
- Improved menu control by [@FalcoG](https://github.com/FalcoG) and [@leaanthony](https://github.com/leaanthony) in [#4031](https://github.com/wailsapp/wails/pull/4031)
- More documentation by [@leaanthony](https://github.com/leaanthony)
- Support cancellation of events in standard event listeners by [@leaanthony](https://github.com/leaanthony)
- Systray `Hide`, `Show` and `Destroy` support by [@leaanthony](https://github.com/leaanthony)
- Systray `SetTooltip` support by [@leaanthony](https://github.com/leaanthony). Original idea by [@lujihong](https://github.com/wailsapp/wails/issues/3487#issuecomment-2633242304)
- Report package path in binding generator warnings about unsupported types by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Add binding generator support for generic aliases by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Add binding generator support for `omitzero` JSON flag by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Add `//wails:ignore` directive to prevent binding generation for chosen service methods by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Add `//wails:internal` directive on services and models to allow for types that are exported in Go but not in JS/TS by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Add binding generator support for constants of alias type to allow for weakly typed enums by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Add binding generator tests for Go 1.24 features by [@fbbdev](https://github.com/fbbdev) in [#4068](https://github.com/wailsapp/wails/pull/4068)
- Add support for macOS 15 "Sequoia" to `OSInfo.Branding` for improved OS version detection in [#4065](https://github.com/wailsapp/wails/pull/4065)
- Add `PostShutdown` hook for running custom code after the shutdown process completes by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- Add `FatalError` struct to support detection of fatal errors in custom error handlers by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- Standardise and document service startup and shutdown order by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- Add test harness for application startup/shutdown sequence and service startup/shutdown tests by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- Add `RegisterService` method for registering services after the application has been created by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- Add `MarshalError` field in application and service options for custom error handling in binding calls by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- Add cancellable promise wrapper that propagates cancellation requests through promise chains by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- Add the ability to tie binding call cancellation to an `AbortSignal` by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- Support `data-wml-*` attributes for WML alongside the usual `wml-*` attributes by [@leaanthony](https://github.com/leaanthony)
- Add `Configure` method on all services for late configuration/dynamic reconfiguration by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- `fileserver` service sends a 503 Service Unavailable response when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- `kvstore` service provides an in-memory key-value store by default when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Add `Load` method on `kvstore` service to reload data from file after config changes by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Add `Clear` method on `kvstore` service to delete all keys by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Add type `Level` in `log` service to provide JS-side log-level constants by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Add `Log` method on `log` service to specify log-level dynamically by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- `sqlite` service provides an in-memory DB by default when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Add method `Close` on `sqlite` service to close the DB manually by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Add cancellation support for query methods on `sqlite` service by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Add prepared statement support to `sqlite` service with JS bindings by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Gin support by [Lea Anthony](https://github.com/leaanthony) in [PR](https://github.com/wailsapp/wails/pull/3537) based on the original work of [@AnalogJ](https://github.com/AnalogJ) in this [PR](https://github.com/wailsapp/wails/pull/3537)
- Fix auto save and password auto save always enabled by [@oSethoum](https://github.com/osethoum) in [#4134](https://github.com/wailsapp/wails/pull/4134)
- Add `SetMenu()` on window to allow for setting a menu on a window by [@leaanthony](https://github.com/leaanthony)
- Add Notification support by [@popaprozac](https://github.com/popaprozac) in [#4098](https://github.com/wailsapp/wails/pull/4098)
-  Add File Association support for mac by [@wimaha](https://github.com/wimaha) in [#4177](https://github.com/wailsapp/wails/pull/4177)
- Add `wails3 tool version` for semantic version bumping by [@leaanthony](https://github.com/leaanthony)
- Add badging support for macOS and Windows by [@popaprozac](https://github.com/popaprozac) in [#](https://github.com/wailsapp/wails/pull/4234)
### Fixed
- Fixed nil pointer dereference in processURLRequest for Mac by [@etesam913](https://github.com/etesam913) in [#4366](https://github.com/wailsapp/wails/pull/4366)
- Fixed a linux bug preventing filtered dialogs by [@bh90210](https://github.com/bh90210) in [#4287](https://github.com/wailsapp/wails/pull/4287)
- Fixed Windows+Linux Edit Menu issues by [@leaanthony](https://github.com/leaanthony) in [#3f78a3a](https://github.com/wailsapp/wails/commit/3f78a3a8ce7837e8b32242c8edbbed431c68c062)
- Updated the minimum system version in macOS .plist files from 10.13.0 to 10.15.0 by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981)
- Window ID skip issue by [@leaanthony](https://github.com/leaanthony)
- Fix nil menu issue when calling RegisterContextMenu by [@leaanthony](https://github.com/leaanthony)
- Fixed dependency cycles in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001)
- Fixed use-before-define errors in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001)
- Pass build flags to binding generator by [@fbbdev](https://github.com/fbbdev) in [#4023](https://github.com/wailsapp/wails/pull/4023)
- Change paths in windows Taskfile to forward slashes to ensure it works on non-Windows platforms by [@leaanthony](https://github.com/leaanthony)
- Mac + Mac JS events now fixed by [@leaanthony](https://github.com/leaanthony)
- Fixed event deadlock for macOS by [@leaanthony](https://github.com/leaanthony)
- Fixed a `Parameter incorrect` error in Window initialisation on Windows when HTML provided but no JS by [@leaanthony](https://github.com/leaanthony)
- Fixed size of response prefix used for content type sniffing in asset server by [@fbbdev](https://github.com/fbbdev) in [#4049](https://github.com/wailsapp/wails/pull/4049)
- Fixed handling of non-404 responses on root index path in asset server by [@fbbdev](https://github.com/fbbdev) in [#4049](https://github.com/wailsapp/wails/pull/4049)
- Fixed undefined behaviour in binding generator when testing properties of generic types by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Fixed binding generator output for models when underlying type has not the same properties as named wrapper by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Fixed binding generator output for map key types and preprocessing by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Fixed binding generator output for structs that implement marshaler interfaces by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Fixed detection of type cycles involving generic types in binding generator by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Fixed invalid references to unexported models in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Moved injected code to the end of service files by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Fixed handling of errors from file close operations in binding generator by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Suppressed warnings for services that define lifecycle or http methods but no other bound methods by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Fixed non-React templates failing to display Hello World footer when using light system colour scheme by [@marcus-crane](https://github.com/marcus-crane) in [#4056](https://github.com/wailsapp/wails/pull/4056)
- Fixed hidden menu items on macOS by [@leaanthony](https://github.com/leaanthony)
- Fixed handling and formatting of errors in message processors by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
-  Fixed skipped service shutdown when quitting application by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
-  Ensure menu updates occur on the main thread by [@leaanthony](https://github.com/leaanthony)
- The dragging and resizing mechanism is now more robust and matches expected platform behaviour more closely by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- Fixed [#4097](https://github.com/wailsapp/wails/issues/4097) Webpack/angular discards runtime init code by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- Fixed initially-hidden menu items by [@IanVS](https://github.com/IanVS) in [#4116](https://github.com/wailsapp/wails/pull/4116)
- Fixed assetFileServer not serving `.html` files when non-extension request when `[request]` doesn't exist but `[request].html` does
- Fixed icon generation paths by [@robin-samuel](https://github.com/robin-samuel) in [#4125](https://github.com/wailsapp/wails/pull/4125)
- Fixed `fullscreen`, `unfullscreen`, `unminimise` and `unmaximise` events not being emitted by [@oSethoum](https://github.com/osethoum) in [#4130](https://github.com/wailsapp/wails/pull/4130)
- Fixed NSIS Error because of incorrect prefix on default version in config by [@robin-samuel](https://github.com/robin-samuel) in [#4126](https://github.com/wailsapp/wails/pull/4126)
- Fixed Dialogs runtime function returning escaped paths on Windows by [TheGB0077](https://github.com/TheGB0077) in [#4188](https://github.com/wailsapp/wails/pull/4188)
- Fixed Webview2 detection path in HKCU by [@leaanthony](https://github.com/leaanthony).
- Fixed input issue with macOS by [@leaanthony](https://github.com/leaanthony).
- Fixed Windows icon generation task file name by [@yulesxoxo](https://github.com/yulesxoxo) in [#4219](https://github.com/wailsapp/wails/pull/4219).
- Fixed transparency issue for frameless windows by [@leaanthony](https://github.com/leaanthony) based on work by @kron.
- Fixed focus calls when window is disabled or minimised by [@leaanthony](https://github.com/leaanthony) based on work by @kron.
- Fixed system trays not showing after taskbar restarts by [@leaanthony](https://github.com/leaanthony) based on work by @kron.
- Fixed fallbackResponseWriter not implementing Flush() in [#4245](https://github.com/wailsapp/wails/pull/4245)
- Fixed fallbackResponseWriter not implementing Flush() by [@superDingda] in [#4236](https://github.com/wailsapp/wails/issues/4236)
- Fixed macOS window close with pending async Go-bound function call crashes by [@joshhardy](https://github.com/joshhardy) in [#4354](https://github.com/wailsapp/wails/pull/4354)
- Fixed Windows Efficiency mode startup race condition by [@leaanthony](https://github.com/leaanthony)
- Fixed Windows icon handle cleanup by [@leaanthony](https://github.com/leaanthony).
- Fixed `OpenFileManager` on Windows by [@PPTGamer](https://github.com/PPTGamer) in [#4375](https://github.com/wailsapp/wails/pull/4375).
### Changed
- Removed `application.WindowIDKey` and `application.WindowNameKey` (replaced by `application.WindowKey`) by [@leaanthony](https://github.com/leaanthony)
- ContextMenuData now returns a string instead of any by [@leaanthony](https://github.com/leaanthony)
- In JS/TS bindings, class fields of fixed-length array types are now initialized with their expected length instead of being empty by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001)
- ContextMenuData now returns a string instead of any by [@leaanthony](https://github.com/leaanthony)
- `application.NewService` does not accept options as an optional parameter anymore (use `application.NewServiceWithOptions` instead) by [@leaanthony](https://github.com/leaanthony) in [#4024](https://github.com/wailsapp/wails/pull/4024)
- Removed `nanoid` dependency by [@leaanthony](https://github.com/leaanthony)
- Updated Window example for mica/acrylic/tabbed window styles by [@leaanthony](https://github.com/leaanthony)
- In JS/TS bindings, `internal.js/ts` model files have been removed; all models can now be found in `models.js/ts` by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- In JS/TS bindings, named types are never rendered as aliases for other named types; the old behaviour is now restricted to aliases by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- In JS/TS bindings, in class mode, struct fields whose type is a type parameter are marked optional and never initialised automatically by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Remove ESLint from templates by by [@IanVS](https://github.com/IanVS) in [#4059](https://github.com/wailsapp/wails/pull/4059)
- Update copyright date to 2025 by [@IanVS](https://github.com/IanVS) in [#4037](https://github.com/wailsapp/wails/pull/4037)
- Add docs for event.Sender by [@IanVS](https://github.com/IanVS) in [#4075](https://github.com/wailsapp/wails/pull/4075)
- Go 1.24 support by [@leaanthony](https://github.com/leaanthony)
- `ServiceStartup` hooks are now invoked when `App.Run` is called, not in `application.New` by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- `ServiceStartup` errors are now returned from `App.Run` instead of terminating the process by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- Binding and dialog calls from JS now reject with error objects instead of strings by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- Improved systray menu positioning on Windows by [@leaanthony](https://github.com/leaanthony)
- The JS runtime has been ported to TypeScript by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- The runtime initialises as soon as it is imported, no need to wait for the window to load by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- The runtime does not export an init method anymore. A side effects import can be used to initialise it by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- Bound methods now return a `CancellablePromise` that rejects with a `CancelError` if cancelled. The actual result of the call is discarded by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- Built-in service types are now consistently called `Service` by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Built-in service creation functions with options are now consistently called `NewWithConfig` by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- `Select` method on `sqlite` service is now named `Query` for consistency with Go APIs by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Templates: moved runtime to "dependencies", organized package.json files by [@IanVS](https://github.com/IanVS) in [#4133](https://github.com/wailsapp/wails/pull/4133)
- Creates and ad-hoc signs app bundles in dev to enable certain macOS APIs by [@popaprozac](https://github.com/popaprozac) in [#4171](https://github.com/wailsapp/wails/pull/4171)
## v3.0.0-alpha.9 - 2025-01-13
### Added
- `app.OpenFileManager(path string, selectFile bool)` to open the system file manager to the path `path` with optional highlighting via `selectFile` by [@Krzysztofz01](https://github.com/Krzysztofz01) [@rcalixte](https://github.com/rcalixte)
- New `-git` flag for `wails3 init` command by [@leaanthony](https://github.com/leaanthony)
- New `wails3 generate webview2bootstrapper` command by [@leaanthony](https://github.com/leaanthony)
- Added `init()` method in runtime to allow manual initialisation of the runtime by [@leaanthony](https://github.com/leaanthony)
- Added `WindowDidMoveDebounceMS` option to Window's WindowOptions by [@leaanthony](https://github.com/leaanthony)
- Added Single Instance feature by [@leaanthony](https://github.com/leaanthony). Based on the [v2 PR](https://github.com/wailsapp/wails/pull/2951) by @APshenkin.
- `wails3 generate template` command by [@leaanthony](https://github.com/leaanthony)
- `wails3 releasenotes` command by [@leaanthony](https://github.com/leaanthony)
- `wails3 update cli` command by [@leaanthony](https://github.com/leaanthony)
- `-clean` option for `wails3 generate bindings` command by [@leaanthony](https://github.com/leaanthony)
- Allow for aarch64 (arm64) AppImage Linux builds by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981)
### Fixed
- Fixed min/max width options for linux by @atterpac in [#3979](https://github.com/wailsapp/wails/pull/3979)
- Typescript templates types definitions via npm version bump by @atterpac in [#3966](https://github.com/wailsapp/wails/pull/3966)
- Fix Sveltekit template CSS referance by @atterpac in [#3945](https://github.com/wailsapp/wails/pull/3945)
- Ensure key callbacks in window run() are called on the main thread by [@leaanthony](https://github.com/leaanthony)
- Fix dialog directory chooser examples by [@leaanthony](https://github.com/leaanthony)
- Created new Chinese error page when index.html is missing by [@leaanthony](https://github.com/leaanthony)
-  Ensure `windowDidBecomeKey` callback is running on main thread by [@leaanthony](https://github.com/leaanthony)
-  Support fullscreen for frameless windows by [@leaanthony](https://github.com/leaanthony)
-  Improved window destroying logic by [@leaanthony](https://github.com/leaanthony)
-  Fix window position logic when attached to system trays by [@leaanthony](https://github.com/leaanthony)
-  Support fullscreen for frameless windows by [@leaanthony](https://github.com/leaanthony)
- Fix event handling by [@leaanthony](https://github.com/leaanthony)
- Fixed window shutdown logic by [@leaanthony](https://github.com/leaanthony)
- Common taskfile now defaults to generating Typescript bindings for Typescript templates by [@leaanthony](https://github.com/leaanthony)
- Fix Close application on WM_CLOSE message when no windows are open/systray only by [@mmalcek](https://github.com/mmalcek) in [#3990](https://github.com/wailsapp/wails/pull/3990)
- Fixed garble build by @5aaee9 in [#3192](https://github.com/wailsapp/wails/pull/3192)
- Fixed windows nsis builds by [@leaanthony](https://github.com/leaanthony)
### Changed
- Moved build assets to platform specific directories by [@leaanthony](https://github.com/leaanthony)
- Moved and renamed Taskfiles to platform specific directories by [@leaanthony](https://github.com/leaanthony)
- Created a much better experience when `index.html` is missing by [@leaanthony](https://github.com/leaanthony)
- [Windows] Improved performance of minimise and restore by [@leaanthony](https://github.com/leaanthony). Based on original [PR](https://github.com/wailsapp/wails/pull/3955) by [562589540](https://github.com/562589540)
- Removed `ShouldClose` option (Register a hook for events.Common.WindowClosing instead) by [@leaanthony](https://github.com/leaanthony)
- [Windows] Reduced flicker when opening a window by [@leaanthony](https://github.com/leaanthony)
- Removed `Window.Destroy` as this was intended to be an internal function by [@leaanthony](https://github.com/leaanthony)
- Renamed `WindowClose` events to `WindowClosing` by [@leaanthony](https://github.com/leaanthony)
- Frontend builds now use vite environment "development" or "production" depending on build type by [@leaanthony](https://github.com/leaanthony)
- Update to go-webview2 v1.19 by [@leaanthony](https://github.com/leaanthony)
## v3.0.0-alpha.8.3 - 2024-12-07
### Changed
- Ensure fork of taskfile is used by @leaanthony
## v3.0.0-alpha.8.2 - 2024-12-07
### Changed
- Update fork of Taskfile to fix version issues when installing using
`go install` by @leaanthony
## v3.0.0-alpha.8.1 - 2024-12-07
### Changed
- Using fork of Taskfile to fix version issues when installing using
`go install` by @leaanthony
## v3.0.0-alpha.8 - 2024-12-06
### Added
- Added hyperlink for sponsor by @ansxuman in [#3958](https://github.com/wailsapp/wails/pull/3958)
- Support of linux packaging of deb,rpm, and arch linux packager builds by
@atterpac in [#3909](https://github.com/wailsapp/wails/3909)
- Added Support for darwin universal builds and packages by
[ansxuman](https://github.com/ansxuman) in
[#3902](https://github.com/wailsapp/wails/pull/3902)
- Events documentation to the website by
[atterpac](https://github.com/atterpac) in
[#3867](https://github.com/wailsapp/wails/pull/3867)
- Templates for sveltekit and sveltekit-ts that are set for non-SSR development
by [atterpac](https://github.com/atterpac) in
[#3829](https://github.com/wailsapp/wails/pull/3829)
- Update build assets using new `wails3 update build-assets` command by
[leaanthony](https://github.com/leaanthony)
- Example to test the HTML Drag and Drop API by
[FerroO2000](https://github.com/FerroO2000) in
[#3856](https://github.com/wailsapp/wails/pull/3856)
- File Association support by [leaanthony](https://github.com/leaanthony) in
[#3873](https://github.com/wailsapp/wails/pull/3873)
- New `wails3 generate runtime` command by
[leaanthony](https://github.com/leaanthony)
- New `InitialPosition` option to specify if the window should be centered or
positioned at the given X/Y location by
[leaanthony](https://github.com/leaanthony) in
[#3885](https://github.com/wailsapp/wails/pull/3885)
- Add `Path` & `Paths` methods to `application` package by
[ansxuman](https://github.com/ansxuman) and
[leaanthony](https://github.com/leaanthony) in
[#3823](https://github.com/wailsapp/wails/pull/3823)
- Added `GeneralAutofillEnabled` and `PasswordAutosaveEnabled` Windows options
by [leaanthony](https://github.com/leaanthony) in
[#3766](https://github.com/wailsapp/wails/pull/3766)
- Added the ability to retrieve the window calling a service method by
[leaanthony](https://github.com/leaanthony) in
[#3888](https://github.com/wailsapp/wails/pull/3888)
- Added `EnabledFeatures` and `DisabledFeatures` options for Webview2 by
[leaanthony](https://github.com/leaanthony).
-
### Changed
- `service.OnStartup` now shutdowns the application on error and runs
`service.OnShutdown`for any prior services that started by @atterpac in
[#3920](https://github.com/wailsapp/wails/pull/3920)
- Refactored systray click messaging to better align with user interactions by
@atterpac in [#3907](https://github.com/wailsapp/wails/pull/3907)
- Asset embed to include `all:frontend/dist` to support frameworks that generate
subfolders by @atterpac in
[#3887](https://github.com/wailsapp/wails/pull/3887)
- Taskfile refactor by [leaanthony](https://github.com/leaanthony) in
[#3748](https://github.com/wailsapp/wails/pull/3748)
- Upgrade to `go-webview2` v1.0.16 by
[leaanthony](https://github.com/leaanthony)
- Fixed `Screen` type to include `ID` not `Id` by
[etesam913](https://github.com/etesam913) in
[#3778](https://github.com/wailsapp/wails/pull/3778)
- Update `go.mod.tmpl` wails version to support `application.ServiceOptions` by
[northes](https://github.com/northes) in
[#3836](https://github.com/wailsapp/wails/pull/3836)
- Fixed service name determination by [windom](https://github.com/windom/) in
[#3827](https://github.com/wailsapp/wails/pull/3827)
- mkdocs serve now uses docker by [leaanthony](https://github.com/leaanthony)
- Consolidated dev config into `config.yml` by
[leaanthony](https://github.com/leaanthony)
- Systray dialog now defaults to the application icon if available (Windows) by
[@leaanthony](https://github.com/leaanthony)
- Better reporting of GPU + Memory for macOS by
[@leaanthony](https://github.com/leaanthony)
- Removed `WebviewGpuIsDisabled` and `EnableFraudulentWebsiteWarnings`
(superseded by `EnabledFeatures` and `DisabledFeatures` options) by
[leaanthony](https://github.com/leaanthony)
### Fixed
- Fixed deadlock in Linux dialog for multiple selections caused by unclosed
channel variable by @michael-freling in
[#3925](https://github.com/wailsapp/wails/pull/3925)
- Fixed cross-platform cleanup for .syso files during Windows build by
[ansxuman](https://github.com/ansxuman) in
[#3924](https://github.com/wailsapp/wails/pull/3924)
- Fixed amd64 appimage compile by @atterpac in
[#3898](https://github.com/wailsapp/wails/pull/3898)
- Fixed build assets update by @ansxuman in
[#3901](https://github.com/wailsapp/wails/pull/3901)
- Fixed Linux systray `OnClick` and `OnRightClick` implementation by @atterpac
in [#3886](https://github.com/wailsapp/wails/pull/3886)
- Fixed `AlwaysOnTop` not working on Mac by
[leaanthony](https://github.com/leaanthony) in
[#3841](https://github.com/wailsapp/wails/pull/3841)
-  Fixed `application.NewEditMenu` including a duplicate
`PasteAndMatchStyle` role in the edit menu on Darwin by
[johnmccabe](https://github.com/johnmccabe) in
[#3839](https://github.com/wailsapp/wails/pull/3839)
- 🐧 Fixed aarch64 compilation
[#3840](https://github.com/wailsapp/wails/issues/3840) in
[#3854](https://github.com/wailsapp/wails/pull/3854) by
[kodflow](https://github.com/kodflow)
- ⊞ Fixed radio group menu items by
[@leaanthony](https://github.com/leaanthony)
- Fix error on building runnable .app on MacOS when 'name' and 'outputfilename'
are different. by @nickisworking in
[#3789](https://github.com/wailsapp/wails/pull/3789)
## v3.0.0-alpha.7 - 2024-09-18
### Added
- ⊞ New DIP system for Enhanced High DPI Monitor Support by
[mmghv](https://github.com/mmghv) in
[#3665](https://github.com/wailsapp/wails/pull/3665)
- ⊞ Window class name option by [windom](https://github.com/windom/) in
[#3682](https://github.com/wailsapp/wails/pull/3682)
- Services have been expanded to provide plugin functionality. By
[atterpac](https://github.com/atterpac) and
[leaanthony](https://github.com/leaanthony) in
[#3570](https://github.com/wailsapp/wails/pull/3570)
### Changed
- Events API change: `On`/`Emit` -> user events, `OnApplicationEvent` ->
Application Events `OnWindowEvent` -> Window Events, by
[leaanthony](https://github.com/leaanthony)
- Fix for Events API on Linux by [TheGB0077](https://github.com/TheGB0077) in
[#3734](https://github.com/wailsapp/wails/pull/3734)
- [CI] improvements to actions & enable to run actions also in forks and
branches prefixed with `v3/` or `v3-` by
[stendler](https://github.com/stendler) in
[#3747](https://github.com/wailsapp/wails/pull/3747)
### Fixed
- Fixed bug with usage of customEventProcessor in drag-n-drop example by
[etesam913](https://github.com/etesam913) in
[#3742](https://github.com/wailsapp/wails/pull/3742)
- 🐧 Fixed linux compile error introduced by IgnoreMouseEvents addition by
[atterpac](https://github.com/atterpac) in
[#3721](https://github.com/wailsapp/wails/pull/3721)
- ⊞ Fixed syso icon file generation bug by
[atterpac](https://github.com/atterpac) in
[#3675](https://github.com/wailsapp/wails/pull/3675)
- 🐧 Fix to run natively in wayland incorporated from
[#1811](https://github.com/wailsapp/wails/pull/1811) in
[#3614](https://github.com/wailsapp/wails/pull/3614) by
[@stendler](https://github.com/stendler)
- Do not bind internal service methods in
[#3720](https://github.com/wailsapp/wails/pull/3720) by
[leaanthony](https://github.com/leaanthony)
- ⊞ Fixed system tray startup panic in
[#3693](https://github.com/wailsapp/wails/issues/3693) by
[@DeltaLaboratory](https://github.com/DeltaLaboratory)
- Do not bind internal service methods in
[#3720](https://github.com/wailsapp/wails/pull/3720) by
[leaanthony](https://github.com/leaanthony)
- ⊞ Fixed system tray startup panic in
[#3693](https://github.com/wailsapp/wails/issues/3693) by
[@DeltaLaboratory](https://github.com/DeltaLaboratory)
- Major menu item refactor and event handling. Mainly improves macOS for now. By
[leaanthony](https://github.com/leaanthony)
- Fix tests after plugins and event refactor in
[#3746](https://github.com/wailsapp/wails/pull/3746) by
[@stendler](https://github.com/stendler)
- ⊞ Fixed `Failed to unregister class Chrome_WidgetWin_0` warning. By
[leaanthony](https://github.com/leaanthony)
## v3.0.0-alpha.6 - 2024-07-30
### Fixed
- Module issues
## v3.0.0-alpha.5 - 2024-07-30
### Added
- 🐧 WindowDidMove / WindowDidResize events in
[#3580](https://github.com/wailsapp/wails/pull/3580)
- ⊞ WindowDidResize event in
[#3580](https://github.com/wailsapp/wails/pull/3580)
-  add Event ApplicationShouldHandleReopen to be able to handle dock
icon click by @5aaee9 in [#2991](https://github.com/wailsapp/wails/pull/2991)
-  add getPrimaryScreen/getScreens to impl by @tmclane in
[#2618](https://github.com/wailsapp/wails/pull/2618)
-  add option for showing the toolbar in fullscreen mode on macOS by
[@fbbdev](https://github.com/fbbdev) in
[#3282](https://github.com/wailsapp/wails/pull/3282)
- 🐧 add onKeyPress logic to convert linux keypress into an accelerator
@[Atterpac](https://github.com/Atterpac)
in[#3022](https://github.com/wailsapp/wails/pull/3022])
- 🐧 add task `run:linux` by
[@marcus-crane](https://github.com/marcus-crane) in
[#3146](https://github.com/wailsapp/wails/pull/3146)
- Export `SetIcon` method by [@almas-x](https://github.com/almas-x) in
[PR](https://github.com/wailsapp/wails/pull/3147)
- Improve `OnShutdown` by [@almas-x](https://github.com/almas-x) in
[PR](https://github.com/wailsapp/wails/pull/3189)
- Restore `ToggleMaximise` method in `Window` interface by
[@fbbdev](https://github.com/fbbdev) in
[#3281](https://github.com/wailsapp/wails/pull/3281)
- Added more information to `Environment()`. By @leaanthony in
[aba82cc](https://github.com/wailsapp/wails/commit/aba82cc52787c97fb99afa58b8b63a0004b7ff6c)
based on [PR](https://github.com/wailsapp/wails/pull/2044) by @Mai-Lapyst
- Expose the `WebviewWindow.IsFocused` method on the `Window` interface by
[@fbbdev](https://github.com/fbbdev) in
[#3295](https://github.com/wailsapp/wails/pull/3295)
- Support multiple space-separated trigger events in the WML system by
[@fbbdev](https://github.com/fbbdev) in
[#3295](https://github.com/wailsapp/wails/pull/3295)
- Add ESM exports from the bundled JS runtime script by
[@fbbdev](https://github.com/fbbdev) in
[#3295](https://github.com/wailsapp/wails/pull/3295)
- Add binding generator flag for using the bundled JS runtime script instead of
the npm package by [@fbbdev](https://github.com/fbbdev) in
[#3334](https://github.com/wailsapp/wails/pull/3334)
- Implement `setIcon` on linux by [@abichinger](https://github.com/abichinger)
in [#3354](https://github.com/wailsapp/wails/pull/3354)
- Add flag `-port` to dev command and support environment variable
`WAILS_VITE_PORT` by [@abichinger](https://github.com/abichinger) in
[#3429](https://github.com/wailsapp/wails/pull/3429)
- Add tests for bound method calls by
[@abichinger](https://github.com/abichinger) in
[#3431](https://github.com/wailsapp/wails/pull/3431)
- ⊞ add `SetIgnoreMouseEvents` for already created window by
[@bruxaodev](https://github.com/bruxaodev) in
[#3667](https://github.com/wailsapp/wails/pull/3667)
-  Add ability to set a window's stacking level (order) by
[@OlegGulevskyy](https://github.com/OlegGulevskyy) in
[#3674](https://github.com/wailsapp/wails/pull/3674)
### Fixed
- Fixed resize event messaging by [atterpac](https://github.com/atterpac) in
[#3606](https://github.com/wailsapp/wails/pull/3606)
- 🐧Fixed theme handling error on NixOS by
[tmclane](https://github.com/tmclane) in
[#3515](https://github.com/wailsapp/wails/pull/3515)
- Fixed cross volume project install for windows by
[atterpac](https://github.com/atterac) in
[#3512](https://github.com/wailsapp/wails/pull/3512)
- Fixed react template css to show footer by
[atterpac](https://github.com/atterpac) in
[#3477](https://github.com/wailsapp/wails/pull/3477)
- Fixed zombie processes when working in devmode by updating to latest refresh
by [Atterpac](https://github.com/atterpac) in
[#3320](https://github.com/wailsapp/wails/pull/3320).
- Fixed appimage webkit file sourcing by [Atterpac](https://github.com/atterpac)
in [#3306](https://github.com/wailsapp/wails/pull/3306).
- Fixed Doctor apt package verify by [Atterpac](https://github.com/Atterpac) in
[#2972](https://github.com/wailsapp/wails/pull/2972).
- Fixed application frozen when quit (Darwin) by @5aaee9 in
[#2982](https://github.com/wailsapp/wails/pull/2982)
- Fixed background colours of examples on Windows by
[mmghv](https://github.com/mmghv) in
[#2750](https://github.com/wailsapp/wails/pull/2750).
- Fixed default context menus by [mmghv](https://github.com/mmghv) in
[#2753](https://github.com/wailsapp/wails/pull/2753).
- Fixed hex values for arrow keys on Darwin by
[jaybeecave](https://github.com/jaybeecave) in
[#3052](https://github.com/wailsapp/wails/pull/3052).
- Set drag-n-drop for windows to working. Added by
[@pylotlight](https://github.com/pylotlight) in
[PR](https://github.com/wailsapp/wails/pull/3039)
- Fixed bug for linux in doctor in the event user doesn't have proper drivers
installed. Added by [@pylotlight](https://github.com/pylotlight) in
[PR](https://github.com/wailsapp/wails/pull/3032)
- Fix dpi scaling on start up (windows). Changed by [@almas-x](https://github.com/almas-x) in
[PR](https://github.com/wailsapp/wails/pull/3145)
- Fix replace line in `go.mod` to use relative paths. Fixes Windows paths with
spaces - @leaanthony.
- Fix MacOS systray click handling when no attached window by
[thomas-senechal](https://github.com/thomas-senechal) in PR
[#3207](https://github.com/wailsapp/wails/pull/3207)
- Fix failing Windows build due to unknown option by
[thomas-senechal](https://github.com/thomas-senechal) in PR
[#3208](https://github.com/wailsapp/wails/pull/3208)
- Fix crash on windows left clicking the systray icon when not having an
attached window [tw1nk](https://github.com/tw1nk) in PR
[#3271](https://github.com/wailsapp/wails/pull/3271)
- Fix wrong baseURL when open window twice by @5aaee9 in PR
[#3273](https://github.com/wailsapp/wails/pull/3273)
- Fix ordering of if branches in `WebviewWindow.Restore` method by
[@fbbdev](https://github.com/fbbdev) in
[#3279](https://github.com/wailsapp/wails/pull/3279)
- Correctly compute `startURL` across multiple `GetStartURL` invocations when
`FRONTEND_DEVSERVER_URL` is present.
[#3299](https://github.com/wailsapp/wails/pull/3299)
- Fix the JS type of the `Screen` struct to match its Go counterpart by
[@fbbdev](https://github.com/fbbdev) in
[#3295](https://github.com/wailsapp/wails/pull/3295)
- Fix the `WML.Reload` method to ensure proper cleanup of registered event
listeners by [@fbbdev](https://github.com/fbbdev) in
[#3295](https://github.com/wailsapp/wails/pull/3295)
- Fix custom context menu closing immediately on linux by
[@abichinger](https://github.com/abichinger) in
[#3330](https://github.com/wailsapp/wails/pull/3330)
- Fix the output path and extension of model files produced by the binding
generator by [@fbbdev](https://github.com/fbbdev) in
[#3334](https://github.com/wailsapp/wails/pull/3334)
- Fix the import paths of model files in JS code produced by the binding
generator by [@fbbdev](https://github.com/fbbdev) in
[#3334](https://github.com/wailsapp/wails/pull/3334)
- Fix drag-n-drop on some linux distros by
[@abichinger](https://github.com/abichinger) in
[#3346](https://github.com/wailsapp/wails/pull/3346)
- Fix missing task for macOS when using `wails3 task dev` by
[@hfoxy](https://github.com/hfoxy) in
[#3417](https://github.com/wailsapp/wails/pull/3417)
- Fix registering events causing a nil map assignment by
[@hfoxy](https://github.com/hfoxy) in
[#3426](https://github.com/wailsapp/wails/pull/3426)
- Fix unmarshaling of bound method parameters by
[@fbbdev](https://github.com/fbbdev) in
[#3431](https://github.com/wailsapp/wails/pull/3431)
- Fix handling of multiple return values from bound methods by
[@fbbdev](https://github.com/fbbdev) in
[#3431](https://github.com/wailsapp/wails/pull/3431)
- Fix doctor detection of npm that is not installed with system package manager
by [@pekim](https://github.com/pekim) in
[#3458](https://github.com/wailsapp/wails/pull/3458)
- Fix missing MicrosoftEdgeWebview2Setup.exe. Thanks to
[@robin-samuel](https://github.com/robin-samuel).
- Fix random crash on linux due to window ID handling by @leaanthony. Based on
PR [#3466](https://github.com/wailsapp/wails/pull/3622) by
[@5aaee9](https://github.com/5aaee9).
- Fix systemTray.setIcon crashing on Linux by
[@windom](https://github.com/windom/) in
[#3636](https://github.com/wailsapp/wails/pull/3636).
- Fix Ensure Window Frame is Applied on First Call in `setFrameless` Function on
Windows by [@bruxaodev](https://github.com/bruxaodev/) in
[#3691](https://github.com/wailsapp/wails/pull/3691).
### Changed
- Renamed `AbsolutePosition()` to `Position()` by
[mmghv](https://github.com/mmghv) in
[#3611](https://github.com/wailsapp/wails/pull/3611)
- Update linux webkit dependency to webkit2gtk-4.1 over webkitgtk2-4.0 to
support Ubuntu 24.04 LTS by [atterpac](https://github.com/atterpac) in
[#3461](https://github.com/wailsapp/wails/pull/3461)
- The bundled JS runtime script is now an ESM module: script tags importing it
must have the `type="module"` attribute. By
[@fbbdev](https://github.com/fbbdev) in
[#3295](https://github.com/wailsapp/wails/pull/3295)
- The `@wailsio/runtime` package does not publish its API on the `window.wails`
object, and does not start the WML system. This has been done to improve
encapsulation. The WML system can be started manually if desired by calling
the new `WML.Enable` method. The bundled JS runtime script still performs both
operations automatically. By [@fbbdev](https://github.com/fbbdev) in
[#3295](https://github.com/wailsapp/wails/pull/3295)
- The Window API module `@wailsio/runtime/src/window` now exposes the containing
window object as a default export. It is not possible anymore to import
individual methods through ESM named or namespace import syntax.
- The JS window API has been updated to match the current Go `WebviewWindow`
API. Some methods have changed name or prototype, specifically: `Screen`
becomes `GetScreen`; `GetZoomLevel`/`SetZoomLevel` become `GetZoom`/`SetZoom`;
`GetZoom`, `Width` and `Height` now return values directly instead of wrapping
them within objects. By [@fbbdev](https://github.com/fbbdev) in
[#3295](https://github.com/wailsapp/wails/pull/3295)
- The binding generator now uses calls by ID by default. The `-id` CLI option
has been removed. Use the `-names` CLI option to switch back to calls by name.
By [@fbbdev](https://github.com/fbbdev) in
[#3468](https://github.com/wailsapp/wails/pull/3468)
- New binding code layout: output files were previously organised in folders
named after their containing package; now full Go import paths are used,
including the module path. By [@fbbdev](https://github.com/fbbdev) in
[#3468](https://github.com/wailsapp/wails/pull/3468)
- The struct field `application.Options.Bind` has been renamed to
`application.Options.Services`. By [@fbbdev](https://github.com/fbbdev) in
[#3468](https://github.com/wailsapp/wails/pull/3468)
- New syntax for binding services: service instances must now be wrapped in a
call to `application.NewService`. By [@fbbdev](https://github.com/fbbdev) in
[#3468](https://github.com/wailsapp/wails/pull/3468)
- Disable spinner on Non-Terminal or CI Environment by
[@DeltaLaboratory](https://github.com/DeltaLaboratory) in
[#3574](https://github.com/wailsapp/wails/pull/3574)

View file

@ -0,0 +1,27 @@
---
title: Links
---
This page serves as a list for community related links.
:::tip[How to Submit a Link]
You can click the `Edit page` at the bottom of this page to submit a PR.
:::
## Awesome Wails
The [definitive list](https://github.com/wailsapp/awesome-wails) of links
related to Wails.
## Support Channels
- [Wails Discord Server](https://discord.gg/bdj28QNHmT)
- [Github Issues](https://github.com/wailsapp/wails/issues)
## Social Media
- [Twitter](https://x.com/wailsapp)
- [Wails Chinese Community QQ Group](https://qm.qq.com/cgi-bin/qm/qr?k=PmIURne5hFGNd7QWzW5qd6FV-INEjNJv&jump_from=webapi) -
Group number: 1067173054

View file

@ -0,0 +1,25 @@
---
title: My Project
draft: true
---
<!--
TEMPLATE FOR SHOWCASE ENTRIES
1. Replace "My Project" with your project name
2. Add your screenshot to: src/assets/showcase-images/your-project.webp
3. Update the image path below
4. Add a description of your project
5. Add a link to your project website/repo
6. Remove the "draft: true" line when ready to publish
-->
![My Project Screenshot](../../../../assets/showcase-images/your-project.webp)
<!-- Add a description of your project here -->
Your project description goes here. Explain what it does, what makes it special, and why you built it with Wails.
<!-- Add a link to your project -->
[Visit Project Website](https://your-project.com) | [View on GitHub](https://github.com/yourusername/your-project)

View file

@ -0,0 +1,16 @@
---
title: BulletinBoard
---
![BulletinBoard](../../../../assets/showcase-images/bboard.webp)
The [BulletinBoard](https://github.com/raguay/BulletinBoard) application is a
versital message board for static messages or dialogs to get information from
the user for a script. It has a TUI for creating new dialogs that can latter be
used to get information from the user. It's design is to stay running on your
system and show the information as needed and then hide away. I have a process
for watching a file on my system and sending the contents to BulletinBoard when
changed. It works great with my workflows. T here is also an
[Alfred workflow](https://github.com/raguay/MyAlfred/blob/master/Alfred%205/EmailIt.alfredworkflow)
for sending information to the program. The workflow is also for working with
[EmailIt](https://github.com/raguay/EmailIt).

View file

@ -0,0 +1,36 @@
---
title: CFN Tracker
---
![CFN Tracker](../../../../assets/showcase-images/cfntracker.webp)
[CFN Tracker](https://github.com/williamsjokvist/cfn-tracker) - Track any Street
Fighter 6 or V CFN profile's live matches. Check
[the website](https://cfn.williamsjokvist.se/) to get started.
## Features
- Real-time match tracking
- Storing match logs and statistics
- Support for displaying live stats to OBS via Browser Source
- Support for both SF6 and SFV
- Ability for users to create their own OBS Browser themes with CSS
### Major tech used alongside Wails
- [Task](https://github.com/go-task/task) - wrapping the Wails CLI to make
common commands easy to use
- [React](https://github.com/facebook/react) - chosen for its rich ecosystem
(radix, framer-motion)
- [Bun](https://github.com/oven-sh/bun) - used for its fast dependency
resolution and build-time
- [Rod](https://github.com/go-rod/rod) - headless browser automation for
authentication and polling changes
- [SQLite](https://github.com/mattn/go-sqlite3) - used for storing matches,
sessions and profiles
- [Server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) -
a http stream to send tracking updates to OBS browser sources
- [i18next](https://github.com/i18next/) - with backend connector to serve
localization objects from the Go layer
- [xstate](https://github.com/statelyai/xstate) - state machines for auth
process and tracking

View file

@ -0,0 +1,18 @@
---
title: Clave
---
![Clave](../../../../assets/showcase-images/clave.png)
Key Features
- 🎨 Simple, intuitive design for hassle-free experience
- ✅ Add accounts easily via manual entry or QR code image import.
- 🔒 End-to-end encryption with PIN or Touch ID(macOS only).
- 💻 Available for macOS, Windows, and Linux
- ⚡ Quick access from system tray for convenience
- 📂 Easily backup and restore your profiles
Try Clave Today!
💻 [https://clave.rocks](https://clave.rocks)

View file

@ -0,0 +1,14 @@
---
title: EmailIt
---
![EmailIt](../../../../assets/showcase-images/emailit.webp)
[EmailIt](https://github.com/raguay/EmailIt/) is a Wails 2 program that is a
markdown based email sender only with nine notepads, scripts to manipulate the
text, and templates. It also has a scripts terminal to run scripts in EmailIt on
files in your system. The scripts and templates can be used from the commandline
itself or with the Alfred, Keyboard Maestro, Dropzone, or PopClip extensions. It
also supports scripts and themes downloaded form GitHub. Documentation is not
complete, but the programs works. Its built using Wails2 and Svelte, and the
download is a universal macOS application.

View file

@ -0,0 +1,16 @@
---
title: EncryptEasy
---
![EncryptEasy](../../../../assets/showcase-images/encrypteasy.webp)
**[EncryptEasy](https://www.encrypteasy.app) is a simple and easy to use PGP
encryption tool, managing all your and your contacts keys. Encryption should be
simple. Developed with Wails.**
Encrypting messages using PGP is the industry standard. Everyone has a private
and a public key. Your private key, well, needs to be kept private so only you
can read messages. Your public key is distributed to anyone who wants to send
you secret, encrypted messages. Managing keys, encrypting messages and
decrypting messages should be a smooth experience. EncryptEasy is all about
making it easy.

View file

@ -0,0 +1,9 @@
---
title: ESP Studio
---
![ESP Studio](../../../../assets/showcase-images/esp-studio.png)
[ESP Studio](https://github.com/torabian/esp-studio) - Cross platform, Desktop,
Cloud, and Embedded software for controlling ESP/Arduino devices, and building
complex IOT workflows and control systems

View file

@ -0,0 +1,29 @@
---
title: FileHound Export Utility
---
![FileHound Export Utility](../../../../assets/showcase-images/filehound.webp)
[FileHound Export Utility](https://www.filehound.co.uk/) FileHound is a cloud
document management platform made for secure file retention, business process
automation and SmartCapture capabilities.
The FileHound Export Utility allows FileHound Administrators the ability to run
a secure document and data extraction tasks for alternative back-up and recovery
purposes. This application will download all documents and/or meta data saved in
FileHound based on the filters you choose. The metadata will be exported in both
JSON and XML formats.
Backend built with:
- Go 1.15
- Wails 1.11.0
- go-sqlite3 1.14.6
- go-linq 3.2
Frontend with:
- Vue 2.6.11
- Vuex 3.4.0
- TypeScript
- Tailwind 1.9.6

View file

@ -0,0 +1,8 @@
---
title: hiposter
---
![hiposter](../../../../assets/showcase-images/hiposter.webp)
[hiposter](https://github.com/obity/hiposter) is a simple and efficient http API
testing client tool. Based on Wails, Go and sveltejs.

View file

@ -0,0 +1,189 @@
---
title: Overview
sidebar:
order: 1
---
import { ShowcaseImage } from "starlight-showcases";
import { Steps } from "@astrojs/starlight/components";
:::tip[See how to add your project]
Check out the
[How to add my project in showcase](#how-to-add-my-project-in-showcase) section.
:::
<ShowcaseImage
entries={[
{
thumbnail: import("../../../../assets/showcase-images/bboard.webp"),
href: "/community/showcase/bulletinboard",
title: "BulletinBoard",
},
{
thumbnail: import("../../../../assets/showcase-images/cfntracker.webp"),
href: "/community/showcase/cfntracker",
title: "CFN Tracker",
},
{
thumbnail: import("../../../../assets/showcase-images/clave.png"),
href: "/community/showcase/clave",
title: "Clave",
},
{
thumbnail: import("../../../../assets/showcase-images/emailit.webp"),
href: "/community/showcase/emailit",
title: "EmailIt",
},
{
thumbnail: import("../../../../assets/showcase-images/encrypteasy.webp"),
href: "/community/showcase/encrypteasy",
title: "EncryptEasy",
},
{
thumbnail: import("../../../../assets/showcase-images/esp-studio.png"),
href: "/community/showcase/espstudio",
title: "ESP Studio",
},
{
thumbnail: import("../../../../assets/showcase-images/filehound.webp"),
href: "/community/showcase/filehound",
title: "FileHound Export Utility",
},
{
thumbnail: import("../../../../assets/showcase-images/hiposter.webp"),
href: "/community/showcase/hiposter",
title: "hiposter",
},
{
thumbnail: import("../../../../assets/showcase-images/mchat.png"),
href: "/community/showcase/mchat",
title: "Mchat",
},
{
thumbnail: import(
"../../../../assets/showcase-images/minecraft-mod-updater.webp"
),
href: "/community/showcase/minecraftupdater",
title: "Minecraft Updater",
},
{
thumbnail: import(
"../../../../assets/showcase-images/minesweeper-xp.webp"
),
href: "/community/showcase/minesweeper-xp",
title: "Minesweeper XP",
},
{
thumbnail: import(
"../../../../assets/showcase-images/modalfilemanager.webp"
),
href: "/community/showcase/modalfilemanager",
title: "Modal File Manager",
},
{
thumbnail: import("../../../../assets/showcase-images/mollywallet.webp"),
href: "/community/showcase/mollywallet",
title: "Molly Wallet",
},
{
thumbnail: import("../../../../assets/showcase-images/october.webp"),
href: "/community/showcase/october",
title: "October",
},
{
thumbnail: import("../../../../assets/showcase-images/optimus.webp"),
href: "/community/showcase/optimus",
title: "Optimus",
},
{
thumbnail: import("../../../../assets/showcase-images/portfall.webp"),
href: "/community/showcase/portfall",
title: "Portfall",
},
{
thumbnail: import("../../../../assets/showcase-images/resizem.webp"),
href: "/community/showcase/resizem",
title: "Resizem",
},
{
thumbnail: import(
"../../../../assets/showcase-images/restic-browser-2.png"
),
href: "/community/showcase/restic-browser",
title: "Restic Browser",
},
{
thumbnail: import(
"../../../../assets/showcase-images/riftshare-main.webp"
),
href: "/community/showcase/riftshare",
title: "RiftShare",
},
{
thumbnail: import("../../../../assets/showcase-images/scriptbar.webp"),
href: "/community/showcase/scriptbar",
title: "ScriptBar",
},
{
thumbnail: import(
"../../../../assets/showcase-images/snippetexpandergui-search-and-paste.png"
),
href: "/community/showcase/snippetexpander",
title: "Snippet Expander",
},
{
thumbnail: import("../../../../assets/showcase-images/surge.png"),
href: "/community/showcase/surge",
title: "Surge",
},
{
thumbnail: import("../../../../assets/showcase-images/tiny-rdm1.webp"),
href: "/community/showcase/tinyrdm",
title: "Tiny RDM",
},
{
thumbnail: import("../../../../assets/showcase-images/wailsterm.webp"),
href: "/community/showcase/wailsterm",
title: "WailsTerm",
},
{
thumbnail: import("../../../../assets/showcase-images/wally.webp"),
href: "/community/showcase/wally",
title: "Wally",
},
{
thumbnail: import("../../../../assets/showcase-images/warmine1.png"),
href: "/community/showcase/warmine",
title: "WarMine",
},
{
thumbnail: import("../../../../assets/showcase-images/wombat.webp"),
href: "/community/showcase/wombat",
title: "Wombat",
},
{
thumbnail: import("../../../../assets/showcase-images/ytd.webp"),
href: "/community/showcase/ytd",
title: "Ytd",
},
]}
/>
## How to add my project in showcase
<Steps>
1. Make a fork of the repository.
2. Add the image(s) under `docs/src/assets/showcase-images` folder.
3. Make a copy of the `_template.md` file under
`docs/src/content/docs/community/showcase` folder.
4. Rename the copied file to the name of your project. (Name should not start
with `_`))
5. Update the title, image, link and content of the file.
6. Add it on the above list in
`docs/src/content/docs/community/showcase/index.mdx`.
7. Submit a PR.
</Steps>

View file

@ -0,0 +1,8 @@
---
title: Mchat
---
![Mchat](../../../../assets/showcase-images/mchat.png)
[Official page](https://marcio199226.github.io/mchat-site/public/) Fully
anonymous end2end encrypted chat.

View file

@ -0,0 +1,10 @@
---
title: Minecraft Updater
---
![Minecraft Updater](../../../../assets/showcase-images/minecraft-mod-updater.webp)
[Minecraft Updater](https://github.com/Gurkengewuerz/MinecraftModUpdater) is a
utility tool to update and synchronize Minecraft mods for your userbase. Its
built using Wails2 and React with [antd](https://ant.design/) as frontend
framework.

View file

@ -0,0 +1,8 @@
---
title: Minesweeper XP
---
![Minesweeper XP](../../../../assets/showcase-images/minesweeper-xp.webp)
[Minesweeper-XP](https://git.new/Minesweeper-XP) allows you to experience the
classic Minesweeper XP (+ 98 and 3.1) on macOS, Windows, and Linux!

View file

@ -0,0 +1,21 @@
---
title: Modal File Manager
---
![Modal File Manager](../../../../assets/showcase-images/modalfilemanager.webp)
[Modal File Manager](https://github.com/raguay/ModalFileManager) is a dual pane
file manager using web technologies. My original design was based on NW.js and
can be found [here](https://github.com/raguay/ModalFileManager-NWjs). This
version uses the same Svelte based frontend code (but it has be greatly modified
since the departure from NW.js), but the backend is a
[Wails 2](https://wails.io/) implementation. By using this implementation, I no
longer use command line `rm`, `cp`, etc. commands, but a git install has to be
on the system to download themes and extensions. It is fully coded using Go and
runs much faster than the previous versions.
This file manager is designed around the same principle as Vim: a state
controlled keyboard actions. The number of states isn't fixed, but very
programmable. Therefore, an infinite number of keyboard configurations can be
created and used. This is the main difference from other file managers. There
are themes and extensions available to download from GitHub.

View file

@ -0,0 +1,9 @@
---
title: Molley Wallet
---
![Molly Wallet](../../../../assets/showcase-images/mollywallet.webp)
[Molly Wallet](https://github.com/grvlle/constellation_wallet/) the official
$DAG wallet of the Constellation Network. It'll let users interact with the
Hypergraph Network in various ways, not limited to producing $DAG transactions.

View file

@ -0,0 +1,16 @@
---
title: October
---
![October](../../../../assets/showcase-images/october.webp)
[October](https://october.utf9k.net) is a small Wails application that makes it
really easy to extract highlights from
[Kobo eReaders](https://en.wikipedia.org/wiki/Kobo_eReader) and then forward
them to [Readwise](https://readwise.io).
It has a relatively small scope with all platform versions weighing in under
10MB, and that's without enabling [UPX compression](https://upx.github.io/)!
In contrast, the author's previous attempts with Electron quickly bloated to
several hundred megabytes.

View file

@ -0,0 +1,9 @@
---
title: Optimus
---
![Optimus](../../../../assets/showcase-images/optimus.webp)
[Optimus](https://github.com/splode/optimus) is a desktop image optimization
application. It supports conversion and compression between WebP, JPEG, and PNG
image formats.

View file

@ -0,0 +1,8 @@
---
title: Portfall
---
![Portfall](../../../../assets/showcase-images/portfall.webp)
[Portfall](https://github.com/rekon-oss/portfall) - A desktop k8s
port-forwarding portal for easy access to all your cluster UIs

View file

@ -0,0 +1,9 @@
---
title: Resizem
---
![Resizem Screenshot](../../../../assets/showcase-images/resizem.webp)
[Resizem](https://github.com/barats/resizem) - is an app designed for bulk image
process. It is particularly useful for users who need to resize, convert, and
manage large numbers of image files at once.

View file

@ -0,0 +1,9 @@
---
title: Restic Browser
---
![Restic Browser](../../../../assets/showcase-images/restic-browser-2.png)
[Restic-Browser](https://github.com/emuell/restic-browser) - A simple,
cross-platform [restic](https://github.com/restic/restic) backup GUI for
browsing and restoring restic repositories.

View file

@ -0,0 +1,23 @@
---
title: RiftShare
---
![RiftShare](../../../../assets/showcase-images/riftshare-main.webp)
Easy, Secure, and Free file sharing for everyone. Learn more at
[Riftshare.app](https://riftshare.app)
## Features
- Easy secure file sharing between computers both in the local network and
through the internet
- Supports sending files or directories securely through the
[magic wormhole protocol](https://magic-wormhole.readthedocs.io/en/latest/)
- Compatible with all other apps using magic wormhole (magic-wormhole or
wormhole-william CLI, wormhole-gui, etc.)
- Automatic zipping of multiple selected files to send at once
- Full animations, progress bar, and cancellation support for sending and
receiving
- Native OS File Selection
- Open files in one click once received
- Auto Update - don't worry about having the latest release!

View file

@ -0,0 +1,14 @@
---
title: ScriptBar
---
![ScriptBar](../../../../assets/showcase-images/scriptbar.webp)
[ScriptBar](https://GitHub.com/raguay/ScriptBarApp) is a program to show the
output of scripts or [Node-Red](https://nodered.org) server. It runs scripts
defined in EmailIt program and shows the output. Scripts from xBar or TextBar
can be used, but currently on the TextBar scripts work well. It also displays
the output of scripts on your system. ScriptBar doesn't put them in the menubar,
but has them all in a convient window for easy viewing. You can have multiple
tabs to have many different things show. You can also keep the links to your
most visited web sites.

View file

@ -0,0 +1,34 @@
---
title: Snippet Expander
---
![Snippet Expander Screenshot](../../../../assets/showcase-images/snippetexpandergui-select-snippet.png)
Screenshot of Snippet Expander's Select Snippet window
![Snippet Expander Screenshot](../../../../assets/showcase-images/snippetexpandergui-add-snippet.png)
Screenshot of Snippet Expander's Add Snippet screen
![Snippet Expander Screenshot](../../../../assets/showcase-images/snippetexpandergui-search-and-paste.png)
Screenshot of Snippet Expander's Search & Paste window
[Snippet Expander](https://snippetexpander.org) is "Your little expandable text
snippets helper", for Linux.
Snippet Expander comprises of a GUI application built with Wails for managing
snippets and settings, with a Search & Paste window mode for quickly selecting
and pasting a snippet.
The Wails based GUI, go-lang CLI and vala-lang auto expander daemon all
communicate with a go-lang daemon via D-Bus. The daemon does the majority of the
work, managing the database of snippets and common settings, and providing
services for expanding and pasting snippets etc.
Check out the
[source code](https://git.sr.ht/~ianmjones/snippetexpander/tree/trunk/item/cmd/snippetexpandergui/app.go#L38)
to see how the Wails app sends messages from the UI to the backend that are then
sent to the daemon, and subscribes to a D-Bus event to monitor changes to
snippets via another instance of the app or CLI and show them instantly in the
UI via a Wails event.

View file

@ -0,0 +1,9 @@
---
title: Surge
---
![Surge Screenshot](../../../../assets/showcase-images/surge.png)
[Surge](https://getsurge.io/) is a p2p filesharing app designed to utilize
blockchain technologies to enable 100% anonymous file transfers. Surge is
end-to-end encrypted, decentralized and open source.

View file

@ -0,0 +1,12 @@
---
title: Tiny RDM
---
![Tiny RDM Screenshot](../../../../assets/showcase-images/tiny-rdm1.webp)
![Tiny RDM Screenshot](../../../../assets/showcase-images/tiny-rdm2.webp)
The [Tiny RDM](https://redis.tinycraft.cc/) application is an open-source,
modern lightweight Redis GUI. It has a beautful UI, intuitive Redis database
management, and compatible with Windows, Mac, and Linux. It provides visual
key-value data operations, supports various data decoding and viewing options,
built-in console for executing commands, slow log queries and more.

View file

@ -0,0 +1,8 @@
---
title: WailsTerm
---
![WailsTerm Screenshot](../../../../assets/showcase-images/wailsterm.webp)
[WailsTerm](https://github.com/rlshukhov/wailsterm) is a simple translucent
terminal app powered by Wails and Xterm.js.

View file

@ -0,0 +1,10 @@
---
title: Wally
---
![Wally Screenshot](../../../../assets/showcase-images/wally.webp)
[Wally](https://ergodox-ez.com/pages/wally) is the official firmware flasher for
[Ergodox](https://ergodox-ez.com/) keyboards. It looks great and is a fantastic
example of what you can achieve with Wails: the ability to combine the power of
Go and the rich graphical tools of the web development world.

View file

@ -0,0 +1,17 @@
---
title: Minecraft launcher for WarMine
---
![WarMine Screenshot](../../../../assets/showcase-images/warmine1.png)
![WarMine Screenshot](../../../../assets/showcase-images/warmine2.png)
[Minecraft launcher for WarMine](https://warmine.ru/) is a Wails application,
that allows you to easily join modded game servers and manage your game
accounts.
The Launcher downloads the game files, checks their integrity and launches the
game with a wide range of customization options for the launch arguments from
the backend.
Frontend is written in Svelte, whole launcher fits in 9MB and supports Windows
7-11.

View file

@ -0,0 +1,7 @@
---
title: Wombat
---
![Wombat Screenshot](../../../../assets/showcase-images/wombat.webp)
[Wombat](https://github.com/rogchap/wombat) is a cross platform gRPC client.

View file

@ -0,0 +1,10 @@
---
title: Ytd
---
![Ytd Screenshot](../../../../assets/showcase-images/ytd.webp)
[Ytd](https://github.com/marcio199226/ytd/tree/v2-wails) is an app for
downloading tracks from youtube, creating offline playlists and share them with
your friends, your friends will be able to playback your playlists or download
them for offline listening, has an built-in player.

View file

@ -0,0 +1,132 @@
---
title: Templates
---
:::caution
This page might be outdated for Wails v3.
:::
<!-- TODO: Update this link -->
This page serves as a list for community supported templates. To build your own
template, please see the [Templates](https://wails.io/docs/guides/templates)
guide.
:::tip[How to Submit a Template]
You can click `Edit this page` at the bottom to include your templates.
:::
To use these templates, run
`wails init -n "Your Project Name" -t [the link below[@version]]`
If there is no version suffix, the main branch code template is used by default.
If there is a version suffix, the code template corresponding to the tag of this
version is used.
Example:
`wails init -n "Your Project Name" -t https://github.com/misitebao/wails-template-vue`
:::danger[Attention]
**The Wails project does not maintain, is not responsible nor liable for 3rd
party templates!**
If you are unsure about a template, inspect `package.json` and `wails.json` for
what scripts are run and what packages are installed.
:::
## Vue
- [wails-template-vue](https://github.com/misitebao/wails-template-vue) - Wails
template based on Vue ecology (Integrated TypeScript, Dark theme,
Internationalization, Single page routing, TailwindCSS)
- [wails-template-quasar-js](https://github.com/sgosiaco/wails-template-quasar-js) -
A template using JavaScript + Quasar V2 (Vue 3, Vite, Sass, Pinia, ESLint,
Prettier)
- [wails-template-quasar-ts](https://github.com/sgosiaco/wails-template-quasar-ts) -
A template using TypeScript + Quasar V2 (Vue 3, Vite, Sass, Pinia, ESLint,
Prettier, Composition API with &lt;script setup&gt;)
- [wails-template-naive](https://github.com/tk103331/wails-template-naive) -
Wails template based on Naive UI (A Vue 3 Component Library)
- [wails-template-nuxt](https://github.com/gornius/wails-template-nuxt) - Wails
template using clean Nuxt3 and TypeScript with auto-imports for wails js
runtime
- [Wails-Tool-Template](https://github.com/xisuo67/Wails-Tool-Template) - Wails
template using Vue+TypeScript+Vite+Element-plus(仿网易云)
## Angular
- [wails-template-angular](https://github.com/mateothegreat/wails-template-angular) -
Angular 15+ action packed & ready to roll to production.
- [wails-angular-template](https://github.com/TAINCER/wails-angular-template) -
Angular with TypeScript, Sass, Hot-Reload, Code-Splitting and i18n
## React
- [wails-react-template](https://github.com/AlienRecall/wails-react-template) -
A template using reactjs
- [wails-react-template](https://github.com/flin7/wails-react-template) - A
minimal template for React that supports live development
- [wails-template-nextjs](https://github.com/LGiki/wails-template-nextjs) - A
template using Next.js and TypeScript
- [wails-template-nextjs-app-router](https://github.com/thisisvk-in/wails-template-nextjs-app-router) -
A template using Next.js and TypeScript with App router
- [wails-template-nextjs-app-router-src](https://github.com/edai-git/wails-template-nextjs-app-router) -
A template using Next.js and TypeScript with App router src + example
- [wails-vite-react-ts-tailwind-template](https://github.com/hotafrika/wails-vite-react-ts-tailwind-template) -
A template for React + TypeScript + Vite + TailwindCSS
- [wails-vite-react-ts-tailwind-shadcnui-template](https://github.com/Mahcks/wails-vite-react-tailwind-shadcnui-ts) -
A template with Vite, React, TypeScript, TailwindCSS, and shadcn/ui
## Svelte
- [wails-svelte-template](https://github.com/raitonoberu/wails-svelte-template) -
A template using Svelte
- [wails-vite-svelte-template](https://github.com/BillBuilt/wails-vite-svelte-template) -
A template using Svelte and Vite
- [wails-vite-svelte-tailwind-template](https://github.com/BillBuilt/wails-vite-svelte-tailwind-template) -
A template using Svelte and Vite with TailwindCSS v3
- [wails-svelte-tailwind-vite-template](https://github.com/PylotLight/wails-vite-svelte-tailwind-template/tree/master) -
An updated template using Svelte v4.2.0 and Vite with TailwindCSS v3.3.3
- [wails-sveltekit-template](https://github.com/h8gi/wails-sveltekit-template) -
A template using SvelteKit
- [wails-template-shadcn-svelte](https://github.com/xijaja/wails-template-shadcn-svelte) -
A template using Sveltekit and Shadcn-Svelte
## Solid
- [wails-template-vite-solid-ts](https://github.com/xijaja/wails-template-solid-ts) -
A template using Solid + Ts + Vite
- [wails-template-vite-solid-js](https://github.com/xijaja/wails-template-solid-js) -
A template using Solid + Js + Vite
## Elm
- [wails-elm-template](https://github.com/benjamin-thomas/wails-elm-template) -
Develop your GUI app with functional programming and a **snappy** hot-reload
setup :tada: :rocket:
- [wails-template-elm-tailwind](https://github.com/rnice01/wails-template-elm-tailwind) -
Combine the powers :muscle: of Elm + Tailwind CSS + Wails! Hot reloading
supported.
## HTMX
- [wails-htmx-templ-chi-tailwind](https://github.com/PylotLight/wails-hmtx-templ-template) -
Use a unique combination of pure htmx for interactivity plus templ for
creating components and forms
## Pure JavaScript (Vanilla)
- [wails-pure-js-template](https://github.com/KiddoV/wails-pure-js-template) - A
template with nothing but just basic JavaScript, HTML, and CSS
## Lit (web components)
- [wails-lit-shoelace-esbuild-template](https://github.com/Braincompiler/wails-lit-shoelace-esbuild-template) -
Wails template providing frontend with lit, Shoelace component library +
pre-configured prettier and typescript.

View file

@ -0,0 +1,665 @@
---
title: How Wails Works
description: Understanding the Wails architecture and how it achieves native performance
sidebar:
order: 1
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
Wails is a framework for building desktop applications using **Go for the backend** and **web technologies for the frontend**. But unlike Electron, Wails doesn't bundle a browser—it uses the **operating system's native WebView**.
```d2
direction: right
User: "User" {
shape: person
style.fill: "#3B82F6"
}
Application: "Your Wails Application" {
Frontend: "Frontend\n(HTML/CSS/JS)" {
shape: rectangle
style.fill: "#8B5CF6"
}
Runtime: "Wails Runtime" {
Bridge: "Message Bridge" {
shape: diamond
style.fill: "#10B981"
}
Bindings: "Type-Safe Bindings" {
shape: rectangle
style.fill: "#10B981"
}
}
Backend: "Go Backend" {
Services: "Your Services" {
shape: rectangle
style.fill: "#00ADD8"
}
NativeAPIs: "OS APIs" {
shape: rectangle
style.fill: "#00ADD8"
}
}
}
OS: "Operating System" {
WebView: "Native WebView\n(WebKit/WebView2/WebKitGTK)" {
shape: rectangle
style.fill: "#6B7280"
}
SystemAPIs: "System APIs\n(Windows/macOS/Linux)" {
shape: rectangle
style.fill: "#6B7280"
}
}
User -> Application.Frontend: "Interacts with UI"
Application.Frontend <-> Application.Runtime.Bridge: "JSON messages"
Application.Runtime.Bridge <-> Application.Backend.Services: "Direct function calls"
Application.Runtime.Bindings -> Application.Frontend: "TypeScript definitions"
Application.Frontend -> OS.WebView: "Renders in"
Application.Backend.NativeAPIs -> OS.SystemAPIs: "Native calls"
```
**Key differences from Electron:**
| Aspect | Wails | Electron |
|--------|-------|----------|
| **Browser** | OS-provided WebView | Bundled Chromium (~100MB) |
| **Backend** | Go (compiled) | Node.js (interpreted) |
| **Communication** | In-memory bridge | IPC (inter-process) |
| **Bundle Size** | ~15MB | ~150MB |
| **Memory** | ~10MB | ~100MB+ |
| **Startup** | &lt;0.5s | 2-3s |
## Core Components
### 1. Native WebView
Wails uses the operating system's built-in web rendering engine:
<Tabs syncKey="platform">
<TabItem label="Windows" icon="seti:windows">
**WebView2** (Microsoft Edge WebView2)
- Based on Chromium (same as Edge browser)
- Pre-installed on Windows 10/11
- Automatic updates via Windows Update
- Full modern web standards support
</TabItem>
<TabItem label="macOS" icon="apple">
**WebKit** (Safari's rendering engine)
- Built into macOS
- Same engine as Safari browser
- Excellent performance and battery life
- Full modern web standards support
</TabItem>
<TabItem label="Linux" icon="linux">
**WebKitGTK** (GTK port of WebKit)
- Installed via package manager
- Same engine as GNOME Web (Epiphany)
- Good standards support
- Lightweight and performant
</TabItem>
</Tabs>
**Why this matters:**
- **No bundled browser** → Smaller app size
- **OS-native** → Better integration and performance
- **Auto-updates** → Security patches from OS updates
- **Familiar rendering** → Same as system browser
### 2. The Wails Bridge
The bridge is the heart of Wails—it enables **direct communication** between Go and JavaScript.
```d2
direction: down
Frontend: "Frontend (JavaScript)" {
shape: rectangle
style.fill: "#8B5CF6"
}
Bridge: "Wails Bridge" {
Encoder: "JSON Encoder" {
shape: rectangle
}
Router: "Method Router" {
shape: diamond
style.fill: "#10B981"
}
Decoder: "JSON Decoder" {
shape: rectangle
}
}
Backend: "Backend (Go)" {
Services: "Registered Services" {
shape: rectangle
style.fill: "#00ADD8"
}
}
Frontend -> Bridge.Encoder: "1. Call Go method\nGreet('Alice')"
Bridge.Encoder -> Bridge.Router: "2. Encode to JSON\n{method: 'Greet', args: ['Alice']}"
Bridge.Router -> Backend.Services: "3. Route to service\nGreetService.Greet('Alice')"
Backend.Services -> Bridge.Decoder: "4. Return result\n'Hello, Alice!'"
Bridge.Decoder -> Frontend: "5. Decode to JS\nPromise resolves"
```
**How it works:**
1. **Frontend calls a Go method** (via auto-generated binding)
2. **Bridge encodes the call** to JSON (method name + arguments)
3. **Router finds the Go method** in registered services
4. **Go method executes** and returns a value
5. **Bridge decodes the result** and sends back to frontend
6. **Promise resolves** in JavaScript with the result
**Performance characteristics:**
- **In-memory**: No network overhead, no HTTP
- **Zero-copy** where possible (for large data)
- **Async by default**: Non-blocking on both sides
- **Type-safe**: TypeScript definitions auto-generated
### 3. Service System
Services are the recommended way to expose Go functionality to the frontend.
```go
// Define a service (just a regular Go struct)
type GreetService struct {
prefix string
}
// Methods with exported names are automatically available
func (g *GreetService) Greet(name string) string {
return g.prefix + name + "!"
}
func (g *GreetService) GetTime() time.Time {
return time.Now()
}
// Register the service
app := application.New(application.Options{
Services: []application.Service{
application.NewService(&GreetService{prefix: "Hello, "}),
},
})
```
**Service discovery:**
- Wails **scans your struct** at startup
- **Exported methods** become callable from frontend
- **Type information** is extracted for TypeScript bindings
- **Error handling** is automatic (Go errors → JS exceptions)
**Generated TypeScript binding:**
```typescript
// Auto-generated in frontend/bindings/GreetService.ts
export function Greet(name: string): Promise<string>
export function GetTime(): Promise<Date>
```
**Why services?**
- **Type-safe**: Full TypeScript support
- **Auto-discovery**: No manual registration of methods
- **Organised**: Group related functionality
- **Testable**: Services are just Go structs
[Learn more about services →](/features/bindings/services)
### 4. Event System
Events enable **pub/sub communication** between components.
```d2
direction: right
GoService: "Go Service" {
shape: rectangle
style.fill: "#00ADD8"
}
EventBus: "Event Bus" {
shape: cylinder
style.fill: "#10B981"
}
Frontend1: "Window 1" {
shape: rectangle
style.fill: "#8B5CF6"
}
Frontend2: "Window 2" {
shape: rectangle
style.fill: "#8B5CF6"
}
GoService -> EventBus: "Emit('data-updated', data)"
EventBus -> Frontend1: "Notify subscribers"
EventBus -> Frontend2: "Notify subscribers"
Frontend1 -> EventBus: "On('data-updated', handler)"
Frontend2 -> EventBus: "On('data-updated', handler)"
```
**Use cases:**
- **Window communication**: One window notifies others
- **Background tasks**: Go service notifies UI of progress
- **State synchronisation**: Keep multiple windows in sync
- **Loose coupling**: Components don't need direct references
**Example:**
```go
// Go: Emit an event
app.Event.Emit("user-logged-in", user)
```
```javascript
// JavaScript: Listen for event
import { On } from '@wailsio/runtime'
On('user-logged-in', (user) => {
console.log('User logged in:', user)
})
```
[Learn more about events →](/features/events/system)
## Application Lifecycle
Understanding the lifecycle helps you know when to initialise resources and clean up.
```d2
direction: down
Start: "Application Start" {
shape: oval
style.fill: "#10B981"
}
Init: "Initialisation" {
Create: "Create Application" {
shape: rectangle
}
Register: "Register Services" {
shape: rectangle
}
Setup: "Setup Windows/Menus" {
shape: rectangle
}
}
Run: "Event Loop" {
Events: "Process Events" {
shape: rectangle
}
Messages: "Handle Messages" {
shape: rectangle
}
Render: "Update UI" {
shape: rectangle
}
}
Shutdown: "Shutdown" {
Cleanup: "Cleanup Resources" {
shape: rectangle
}
Save: "Save State" {
shape: rectangle
}
}
End: "Application End" {
shape: oval
style.fill: "#EF4444"
}
Start -> Init.Create
Init.Create -> Init.Register
Init.Register -> Init.Setup
Init.Setup -> Run.Events
Run.Events -> Run.Messages
Run.Messages -> Run.Render
Run.Render -> Run.Events: "Loop"
Run.Events -> Shutdown.Cleanup: "Quit signal"
Shutdown.Cleanup -> Shutdown.Save
Shutdown.Save -> End
```
**Lifecycle hooks:**
```go
// Services provide startup/shutdown hooks
type AppService struct{}
func (s *AppService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
// Initialise database, load config, etc.
return nil
}
func (s *AppService) ServiceShutdown() error {
// Save state, close connections, etc.
return nil
}
app := application.New(application.Options{
Name: "My App",
Services: []application.Service{
application.NewService(&AppService{}),
},
// Called when app is about to quit (before service shutdown)
OnShutdown: func() {
// Additional cleanup
},
})
```
[Learn more about lifecycle →](/concepts/lifecycle)
## Build Process
Understanding how Wails builds your application:
```d2
direction: down
Source: "Source Code" {
Go: "Go Code\n(main.go, services)" {
shape: rectangle
style.fill: "#00ADD8"
}
Frontend: "Frontend Code\n(HTML/CSS/JS)" {
shape: rectangle
style.fill: "#8B5CF6"
}
}
Build: "Build Process" {
AnalyseGo: "Analyse Go Code" {
shape: rectangle
}
GenerateBindings: "Generate Bindings" {
shape: rectangle
}
BuildFrontend: "Build Frontend" {
shape: rectangle
}
CompileGo: "Compile Go" {
shape: rectangle
}
Embed: "Embed Assets" {
shape: rectangle
}
}
Output: "Output" {
Binary: "Native Binary\n(myapp.exe/.app)" {
shape: rectangle
style.fill: "#10B981"
}
}
Source.Go -> Build.AnalyseGo
Build.AnalyseGo -> Build.GenerateBindings: "Extract types"
Build.GenerateBindings -> Source.Frontend: "TypeScript bindings"
Source.Frontend -> Build.BuildFrontend: "Compile (Vite/webpack)"
Build.BuildFrontend -> Build.Embed: "Bundled assets"
Source.Go -> Build.CompileGo
Build.CompileGo -> Build.Embed
Build.Embed -> Output.Binary
```
**Build steps:**
1. **Analyse Go code**
- Scan services for exported methods
- Extract parameter and return types
- Generate method signatures
2. **Generate TypeScript bindings**
- Create `.ts` files for each service
- Include full type definitions
- Add JSDoc comments
3. **Build frontend**
- Run your bundler (Vite, webpack, etc.)
- Minify and optimise
- Output to `frontend/dist/`
4. **Compile Go**
- Compile with optimisations (`-ldflags="-s -w"`)
- Include build metadata
- Platform-specific compilation
5. **Embed assets**
- Embed frontend files into Go binary
- Compress assets
- Create single executable
**Result:** A single native executable with everything embedded.
[Learn more about building →](/guides/build/building)
## Development vs Production
Wails behaves differently in development and production:
<Tabs syncKey="mode">
<TabItem label="Development (wails3 dev)" icon="seti:config">
**Characteristics:**
- **Hot reload**: Frontend changes reload instantly
- **Source maps**: Debug with original source
- **DevTools**: Browser DevTools available
- **Logging**: Verbose logging enabled
- **External frontend**: Served from dev server (Vite)
**How it works:**
```d2
direction: right
WailsApp: "Wails App" {
shape: rectangle
style.fill: "#00ADD8"
}
DevServer: "Vite Dev Server\n(localhost:5173)" {
shape: rectangle
style.fill: "#8B5CF6"
}
WebView: "WebView" {
shape: rectangle
style.fill: "#6B7280"
}
WailsApp -> DevServer: "Proxy requests"
DevServer -> WebView: "Serve with HMR"
WebView -> WailsApp: "Call Go methods"
```
**Benefits:**
- Instant feedback on changes
- Full debugging capabilities
- Faster iteration
</TabItem>
<TabItem label="Production (wails3 build)" icon="rocket">
**Characteristics:**
- **Embedded assets**: Frontend built into binary
- **Optimised**: Minified, compressed
- **No DevTools**: Disabled by default
- **Minimal logging**: Errors only
- **Single file**: Everything in one executable
**How it works:**
```d2
direction: right
Binary: "Single Binary\n(myapp.exe)" {
GoCode: "Compiled Go" {
shape: rectangle
style.fill: "#00ADD8"
}
Assets: "Embedded Assets\n(HTML/CSS/JS)" {
shape: rectangle
style.fill: "#8B5CF6"
}
}
WebView: "WebView" {
shape: rectangle
style.fill: "#6B7280"
}
Binary.Assets -> WebView: "Serve from memory"
WebView -> Binary.GoCode: "Call Go methods"
```
**Benefits:**
- Single file distribution
- Smaller size (minified)
- Better performance
- No external dependencies
</TabItem>
</Tabs>
## Memory Model
Understanding memory usage helps you build efficient applications.
{/* VISUAL PLACEHOLDER: Memory Diagram
Description: Memory layout diagram showing:
1. Go Heap (services, application state)
2. WebView Memory (DOM, JavaScript heap)
3. Shared Memory (bridge communication)
4. Arrows showing data flow between regions
5. Annotations for zero-copy optimisations
Style: Technical diagram with memory regions as boxes, clear labels, size indicators
*/}
**Memory regions:**
1. **Go Heap**
- Your services and application state
- Managed by Go garbage collector
- Typically 5-10MB for simple apps
2. **WebView Memory**
- DOM, JavaScript heap, CSS
- Managed by WebView's engine
- Typically 10-20MB for simple apps
3. **Bridge Memory**
- Message buffers for communication
- Minimal overhead (\<1MB)
- Zero-copy for large data where possible
**Optimisation tips:**
- **Avoid large data transfers**: Pass IDs, fetch details on demand
- **Use events for updates**: Don't poll from frontend
- **Stream large files**: Don't load entirely into memory
- **Clean up listeners**: Remove event listeners when done
[Learn more about performance →](/guides/advanced/performance)
## Security Model
Wails provides a secure-by-default architecture:
```d2
direction: down
Frontend: "Frontend (Untrusted)" {
shape: rectangle
style.fill: "#EF4444"
}
Bridge: "Wails Bridge (Validation)" {
shape: diamond
style.fill: "#F59E0B"
}
Backend: "Backend (Trusted)" {
shape: rectangle
style.fill: "#10B981"
}
Frontend -> Bridge: "Call method"
Bridge -> Bridge: "Validate:\n- Method exists?\n- Types correct?\n- Access allowed?"
Bridge -> Backend: "Execute if valid"
Backend -> Bridge: "Return result"
Bridge -> Frontend: "Send response"
```
**Security features:**
1. **Method whitelisting**
- Only exported methods are callable
- Private methods are inaccessible
- Explicit service registration required
2. **Type validation**
- Arguments checked against Go types
- Invalid types rejected
- Prevents injection attacks
3. **No eval()**
- Frontend can't execute arbitrary Go code
- Only predefined methods callable
- No dynamic code execution
4. **Context isolation**
- Each window has its own context
- Services can check caller context
- Permissions per window possible
**Best practices:**
- **Validate user input** in Go (don't trust frontend)
- **Use context** for authentication/authorisation
- **Sanitise file paths** before file operations
- **Rate limit** expensive operations
[Learn more about security →](/guides/advanced/security)
## Next Steps
**Application Lifecycle** - Understand startup, shutdown, and lifecycle hooks
[Learn More →](/concepts/lifecycle)
**Go-Frontend Bridge** - Deep dive into how the bridge works
[Learn More →](/concepts/bridge)
**Build System** - Understand how Wails builds your application
[Learn More →](/concepts/build-system)
**Start Building** - Apply what you've learned in a tutorial
[Tutorials →](/tutorials/03-notes-vanilla)
---
**Questions about architecture?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [API reference](/reference/overview).

View file

@ -0,0 +1,702 @@
---
title: Go-Frontend Bridge
description: Deep dive into how Wails enables direct communication between Go and JavaScript
sidebar:
order: 3
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
## Direct Go-JavaScript Communication
Wails provides a **direct, in-memory bridge** between Go and JavaScript, enabling seamless communication without HTTP overhead, process boundaries, or serialisation bottlenecks.
## The Big Picture
```d2
direction: right
Frontend: "Frontend (JavaScript)" {
UI: "React/Vue/Vanilla" {
shape: rectangle
style.fill: "#8B5CF6"
}
Bindings: "Auto-Generated Bindings" {
shape: rectangle
style.fill: "#A78BFA"
}
}
Bridge: "Wails Bridge" {
Encoder: "JSON Encoder" {
shape: rectangle
style.fill: "#10B981"
}
Router: "Method Router" {
shape: diamond
style.fill: "#10B981"
}
Decoder: "JSON Decoder" {
shape: rectangle
style.fill: "#10B981"
}
TypeGen: "Type Generator" {
shape: rectangle
style.fill: "#10B981"
}
}
Backend: "Backend (Go)" {
Services: "Your Services" {
shape: rectangle
style.fill: "#00ADD8"
}
Registry: "Service Registry" {
shape: rectangle
style.fill: "#00ADD8"
}
}
Frontend.UI -> Frontend.Bindings: "import { Method }"
Frontend.Bindings -> Bridge.Encoder: "Call Method('arg')"
Bridge.Encoder -> Bridge.Router: "Encode to JSON"
Bridge.Router -> Backend.Registry: "Find service"
Backend.Registry -> Backend.Services: "Invoke method"
Backend.Services -> Bridge.Decoder: "Return result"
Bridge.Decoder -> Frontend.Bindings: "Decode to JS"
Frontend.Bindings -> Frontend.UI: "Promise resolves"
Bridge.TypeGen -> Frontend.Bindings: "Generate types"
```
**Key insight:** No HTTP, no IPC, no process boundaries. Just **direct function calls** with **type safety**.
## How It Works: Step by Step
### 1. Service Registration (Startup)
When your application starts, Wails scans your services:
```go
type GreetService struct {
prefix string
}
func (g *GreetService) Greet(name string) string {
return g.prefix + name + "!"
}
func (g *GreetService) Add(a, b int) int {
return a + b
}
// Register service
app := application.New(application.Options{
Services: []application.Service{
application.NewService(&GreetService{prefix: "Hello, "}),
},
})
```
**What Wails does:**
1. **Scans the struct** for exported methods
2. **Extracts type information** (parameters, return types)
3. **Builds a registry** mapping method names to functions
4. **Generates TypeScript bindings** with full type definitions
### 2. Binding Generation (Build Time)
Wails generates TypeScript bindings automatically:
```typescript
// Auto-generated: frontend/bindings/GreetService.ts
export function Greet(name: string): Promise<string>
export function Add(a: number, b: number): Promise<number>
```
**Type mapping:**
| Go Type | TypeScript Type |
|---------|-----------------|
| `string` | `string` |
| `int`, `int32`, `int64` | `number` |
| `float32`, `float64` | `number` |
| `bool` | `boolean` |
| `[]T` | `T[]` |
| `map[string]T` | `Record<string, T>` |
| `struct` | `interface` |
| `time.Time` | `Date` |
| `error` | Exception (thrown) |
### 3. Frontend Call (Runtime)
Developer calls the Go method from JavaScript:
```javascript
import { Greet, Add } from './bindings/GreetService'
// Call Go from JavaScript
const greeting = await Greet("World")
console.log(greeting) // "Hello, World!"
const sum = await Add(5, 3)
console.log(sum) // 8
```
**What happens:**
1. **Binding function called** - `Greet("World")`
2. **Message created** - `{ service: "GreetService", method: "Greet", args: ["World"] }`
3. **Sent to bridge** - Via WebView's JavaScript bridge
4. **Promise returned** - Awaits response
### 4. Bridge Processing (Runtime)
The bridge receives the message and processes it:
```d2
direction: down
Receive: "Receive Message" {
shape: rectangle
style.fill: "#10B981"
}
Parse: "Parse JSON" {
shape: rectangle
}
Validate: "Validate" {
Check: "Service exists?" {
shape: diamond
}
CheckMethod: "Method exists?" {
shape: diamond
}
CheckTypes: "Types correct?" {
shape: diamond
}
}
Invoke: "Invoke Go Method" {
shape: rectangle
style.fill: "#00ADD8"
}
Encode: "Encode Result" {
shape: rectangle
}
Send: "Send Response" {
shape: rectangle
style.fill: "#10B981"
}
Error: "Send Error" {
shape: rectangle
style.fill: "#EF4444"
}
Receive -> Parse
Parse -> Validate.Check
Validate.Check -> Validate.CheckMethod: "Yes"
Validate.Check -> Error: "No"
Validate.CheckMethod -> Validate.CheckTypes: "Yes"
Validate.CheckMethod -> Error: "No"
Validate.CheckTypes -> Invoke: "Yes"
Validate.CheckTypes -> Error: "No"
Invoke -> Encode: "Success"
Invoke -> Error: "Error"
Encode -> Send
```
**Security:** Only registered services and exported methods are callable.
### 5. Go Execution (Runtime)
The Go method executes:
```go
func (g *GreetService) Greet(name string) string {
// This runs in Go
return g.prefix + name + "!"
}
```
**Execution context:**
- Runs in a **goroutine** (non-blocking)
- Has access to **all Go features** (file system, network, databases)
- Can call **other Go code** freely
- Returns result or error
### 6. Response (Runtime)
Result is sent back to JavaScript:
```javascript
// Promise resolves with result
const greeting = await Greet("World")
// greeting = "Hello, World!"
```
**Error handling:**
```go
func (g *GreetService) Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
```
```javascript
try {
const result = await Divide(10, 0)
} catch (error) {
console.error("Go error:", error) // "division by zero"
}
```
## Performance Characteristics
### Speed
**Typical call overhead:** &lt;1ms
```
Frontend Call → Bridge → Go Execution → Bridge → Frontend Response
↓ ↓ ↓ ↓ ↓
&lt;0.1ms &lt;0.1ms [varies] &lt;0.1ms &lt;0.1ms
```
**Compared to alternatives:**
- **HTTP/REST:** 5-50ms (network stack, serialisation)
- **IPC:** 1-10ms (process boundaries, marshalling)
- **Wails Bridge:** &lt;1ms (in-memory, direct call)
### Memory
**Per-call overhead:** ~1KB (message buffer)
**Zero-copy optimisation:** Large data (>1MB) uses shared memory where possible.
### Concurrency
**Calls are concurrent:**
- Each call runs in its own goroutine
- Multiple calls can execute simultaneously
- No blocking between calls
```javascript
// These run concurrently
const [result1, result2, result3] = await Promise.all([
SlowOperation1(),
SlowOperation2(),
SlowOperation3(),
])
```
## Type System
### Supported Types
#### Primitives
```go
// Go
func Example(
s string,
i int,
f float64,
b bool,
) (string, int, float64, bool) {
return s, i, f, b
}
```
```typescript
// TypeScript (auto-generated)
function Example(
s: string,
i: number,
f: number,
b: boolean,
): Promise<[string, number, number, boolean]>
```
#### Slices and Arrays
```go
// Go
func Sum(numbers []int) int {
total := 0
for _, n := range numbers {
total += n
}
return total
}
```
```typescript
// TypeScript
function Sum(numbers: number[]): Promise<number>
// Usage
const total = await Sum([1, 2, 3, 4, 5]) // 15
```
#### Maps
```go
// Go
func GetConfig() map[string]interface{} {
return map[string]interface{}{
"theme": "dark",
"fontSize": 14,
"enabled": true,
}
}
```
```typescript
// TypeScript
function GetConfig(): Promise<Record<string, any>>
// Usage
const config = await GetConfig()
console.log(config.theme) // "dark"
```
#### Structs
```go
// Go
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func GetUser(id int) (*User, error) {
return &User{
ID: id,
Name: "Alice",
Email: "alice@example.com",
}, nil
}
```
```typescript
// TypeScript (auto-generated)
interface User {
id: number
name: string
email: string
}
function GetUser(id: number): Promise<User>
// Usage
const user = await GetUser(1)
console.log(user.name) // "Alice"
```
**JSON tags:** Use `json:` tags to control field names in TypeScript.
#### Time
```go
// Go
func GetTimestamp() time.Time {
return time.Now()
}
```
```typescript
// TypeScript
function GetTimestamp(): Promise<Date>
// Usage
const timestamp = await GetTimestamp()
console.log(timestamp.toISOString())
```
#### Errors
```go
// Go
func Validate(input string) error {
if input == "" {
return errors.New("input cannot be empty")
}
return nil
}
```
```typescript
// TypeScript
function Validate(input: string): Promise<void>
// Usage
try {
await Validate("")
} catch (error) {
console.error(error) // "input cannot be empty"
}
```
### Unsupported Types
These types **cannot** be passed across the bridge:
- **Channels** (`chan T`)
- **Functions** (`func()`)
- **Interfaces** (except `interface{}` / `any`)
- **Pointers** (except to structs)
- **Unexported fields** (lowercase)
**Workaround:** Use IDs or handles:
```go
// ❌ Can't pass file handle
func OpenFile(path string) (*os.File, error) {
return os.Open(path)
}
// ✅ Return file ID instead
var files = make(map[string]*os.File)
func OpenFile(path string) (string, error) {
file, err := os.Open(path)
if err != nil {
return "", err
}
id := generateID()
files[id] = file
return id, nil
}
func ReadFile(id string) ([]byte, error) {
file := files[id]
return io.ReadAll(file)
}
func CloseFile(id string) error {
file := files[id]
delete(files, id)
return file.Close()
}
```
## Advanced Patterns
### Context Passing
Services can access the call context:
```go
type UserService struct{}
func (s *UserService) GetCurrentUser(ctx context.Context) (*User, error) {
// Access window that made the call
window := application.ContextWindow(ctx)
// Access application
app := application.ContextApplication(ctx)
// Your logic
return getCurrentUser(), nil
}
```
**Context provides:**
- Window that made the call
- Application instance
- Request metadata
### Streaming Data
For large data, use events instead of return values:
```go
func ProcessLargeFile(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
lineNum := 0
for scanner.Scan() {
lineNum++
// Emit progress events
app.Event.Emit("file-progress", map[string]interface{}{
"line": lineNum,
"text": scanner.Text(),
})
}
return scanner.Err()
}
```
```javascript
import { On } from '@wailsio/runtime'
import { ProcessLargeFile } from './bindings/FileService'
// Listen for progress
On('file-progress', (data) => {
console.log(`Line ${data.line}: ${data.text}`)
})
// Start processing
await ProcessLargeFile('/path/to/large/file.txt')
```
### Cancellation
Use context for cancellable operations:
```go
func LongRunningTask(ctx context.Context) error {
for i := 0; i < 1000; i++ {
// Check if cancelled
select {
case <-ctx.Done():
return ctx.Err()
default:
// Continue work
time.Sleep(100 * time.Millisecond)
}
}
return nil
}
```
**Note:** Context cancellation on frontend disconnect is automatic.
### Batch Operations
Reduce bridge overhead by batching:
```go
// ❌ Inefficient: N bridge calls
for _, item := range items {
await ProcessItem(item)
}
// ✅ Efficient: 1 bridge call
await ProcessItems(items)
```
```go
func ProcessItems(items []Item) ([]Result, error) {
results := make([]Result, len(items))
for i, item := range items {
results[i] = processItem(item)
}
return results, nil
}
```
## Debugging the Bridge
### Enable Debug Logging
```go
import "log/slog"
app := application.New(application.Options{
Name: "My App",
Logger: application.DefaultLogger(slog.LevelDebug),
LogLevel: slog.LevelDebug,
})
```
**Output shows:**
- Method calls
- Parameters
- Return values
- Errors
- Timing information
### Inspect Generated Bindings
Check `frontend/bindings/` to see generated TypeScript:
```typescript
// frontend/bindings/MyService.ts
export function MyMethod(arg: string): Promise<number> {
return window.wails.Call('MyService.MyMethod', arg)
}
```
### Test Services Directly
Test Go services without the frontend:
```go
func TestGreetService(t *testing.T) {
service := &GreetService{prefix: "Hello, "}
result := service.Greet("Test")
if result != "Hello, Test!" {
t.Errorf("Expected 'Hello, Test!', got '%s'", result)
}
}
```
## Performance Tips
### ✅ Do
- **Batch operations** - Reduce bridge calls
- **Use events for streaming** - Don't return large arrays
- **Keep methods fast** - &lt;100ms ideal
- **Use goroutines** - For long operations
- **Cache on Go side** - Avoid repeated calculations
### ❌ Don't
- **Don't make excessive calls** - Batch when possible
- **Don't return huge data** - Use pagination or streaming
- **Don't block** - Use goroutines for long operations
- **Don't pass complex types** - Keep it simple
- **Don't ignore errors** - Always handle them
## Security
The bridge is secure by default:
1. **Whitelist only** - Only registered services callable
2. **Type validation** - Arguments checked against Go types
3. **No eval()** - Frontend can't execute arbitrary Go code
4. **No reflection abuse** - Only exported methods accessible
**Best practices:**
- **Validate input** in Go (don't trust frontend)
- **Use context** for authentication/authorisation
- **Rate limit** expensive operations
- **Sanitise** file paths and user input
## Next Steps
**Build System** - Learn how Wails builds and bundles your application
[Learn More →](/concepts/build-system)
**Services** - Deep dive into the service system
[Learn More →](/features/bindings/services)
**Events** - Use events for pub/sub communication
[Learn More →](/features/events/system)
---
**Questions about the bridge?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [binding examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/binding).

View file

@ -0,0 +1,694 @@
---
title: Build System
description: Understanding how Wails builds and packages your application
sidebar:
order: 4
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
## Unified Build System
Wails provides a **unified build system** that compiles Go code, bundles frontend assets, embeds everything into a single executable, and handles platform-specific builds—all with one command.
```bash
wails3 build
```
**Output:** Native executable with everything embedded.
## Build Process Overview
```d2
direction: down
Source: "Source Code" {
Go: "Go Code\n(main.go, services)" {
shape: rectangle
style.fill: "#00ADD8"
}
Frontend: "Frontend Code\n(HTML/CSS/JS)" {
shape: rectangle
style.fill: "#8B5CF6"
}
}
Analysis: "Analysis Phase" {
ScanGo: "Scan Go Services" {
shape: rectangle
}
ExtractTypes: "Extract Types" {
shape: rectangle
}
}
Generation: "Generation Phase" {
GenBindings: "Generate TypeScript Bindings" {
shape: rectangle
style.fill: "#10B981"
}
BuildFrontend: "Build Frontend\n(Vite/webpack)" {
shape: rectangle
style.fill: "#8B5CF6"
}
}
Compilation: "Compilation Phase" {
CompileGo: "Compile Go\n(with optimisations)" {
shape: rectangle
style.fill: "#00ADD8"
}
EmbedAssets: "Embed Frontend Assets" {
shape: rectangle
style.fill: "#10B981"
}
}
Output: "Output" {
Binary: "Native Binary\n(myapp.exe/.app)" {
shape: rectangle
style.fill: "#10B981"
}
}
Source.Go -> Analysis.ScanGo
Analysis.ScanGo -> Analysis.ExtractTypes
Analysis.ExtractTypes -> Generation.GenBindings
Generation.GenBindings -> Source.Frontend: "TypeScript types"
Source.Frontend -> Generation.BuildFrontend
Generation.BuildFrontend -> Compilation.EmbedAssets: "Bundled assets"
Source.Go -> Compilation.CompileGo
Compilation.CompileGo -> Compilation.EmbedAssets
Compilation.EmbedAssets -> Output.Binary
```
## Build Phases
### 1. Analysis Phase
Wails scans your Go code to understand your services:
```go
type GreetService struct {
prefix string
}
func (g *GreetService) Greet(name string) string {
return g.prefix + name + "!"
}
```
**What Wails extracts:**
- Service name: `GreetService`
- Method name: `Greet`
- Parameter types: `string`
- Return types: `string`
**Used for:** Generating TypeScript bindings
### 2. Generation Phase
#### TypeScript Bindings
Wails generates type-safe bindings:
```typescript
// Auto-generated: frontend/bindings/GreetService.ts
export function Greet(name: string): Promise<string> {
return window.wails.Call('GreetService.Greet', name)
}
```
**Benefits:**
- Full type safety
- IDE autocomplete
- Compile-time errors
- JSDoc comments
#### Frontend Build
Your frontend bundler runs (Vite, webpack, etc.):
```bash
# Vite example
vite build --outDir dist
```
**What happens:**
- JavaScript/TypeScript compiled
- CSS processed and minified
- Assets optimised
- Source maps generated (dev only)
- Output to `frontend/dist/`
### 3. Compilation Phase
#### Go Compilation
Go code is compiled with optimisations:
```bash
go build -ldflags="-s -w" -o myapp.exe
```
**Flags:**
- `-s`: Strip symbol table
- `-w`: Strip DWARF debugging info
- Result: Smaller binary (~30% reduction)
**Platform-specific:**
- Windows: `.exe` with icon embedded
- macOS: `.app` bundle structure
- Linux: ELF binary
#### Asset Embedding
Frontend assets are embedded into the Go binary:
```go
//go:embed frontend/dist
var assets embed.FS
```
**Result:** Single executable with everything inside.
### 4. Output
**Single native binary:**
- Windows: `myapp.exe` (~15MB)
- macOS: `myapp.app` (~15MB)
- Linux: `myapp` (~15MB)
**No dependencies** (except system WebView).
## Development vs Production
<Tabs syncKey="mode">
<TabItem label="Development (wails3 dev)" icon="seti:config">
**Optimised for speed:**
```bash
wails3 dev
```
**What happens:**
1. Starts frontend dev server (Vite on port 9245)
2. Compiles Go without optimisations
3. Launches app pointing to dev server
4. Enables hot reload
5. Includes source maps
**Characteristics:**
- **Fast rebuilds** (<1s for frontend changes)
- **No asset embedding** (served from dev server)
- **Debug symbols** included
- **Source maps** enabled
- **Verbose logging**
**File size:** Larger (~50MB with debug symbols)
</TabItem>
<TabItem label="Production (wails3 build)" icon="rocket">
**Optimised for size and performance:**
```bash
wails3 build
```
**What happens:**
1. Builds frontend for production (minified)
2. Compiles Go with optimisations
3. Strips debug symbols
4. Embeds assets
5. Creates single binary
**Characteristics:**
- **Optimised code** (minified, tree-shaken)
- **Assets embedded** (no external files)
- **Debug symbols stripped**
- **No source maps**
- **Minimal logging**
**File size:** Smaller (~15MB)
</TabItem>
</Tabs>
## Build Commands
### Basic Build
```bash
wails3 build
```
**Output:** Binary in `bin/` directory.
`wails3 build` is a wrapper for `wails3 task build`. It runs the `build` task defined in your project's `Taskfile.yml`, which dispatches to the appropriate platform-specific Taskfile.
### Build with Tags
```bash
# Pass additional Go build tags
wails3 build -tags production
```
### Build with CLI Variables
You can pass CLI variables to customize the build:
```bash
wails3 build PRODUCTION=true
```
These variables are forwarded to the underlying task and can be accessed in your `Taskfile.yml` using Go template syntax.
### Platform-Specific Tasks
The default project structure includes platform-specific Taskfiles. The main `Taskfile.yml` dispatches to the correct one based on `{{OS}}`:
```bash
# Run platform-specific tasks directly
wails3 task darwin:build
wails3 task windows:build
wails3 task linux:build
```
## Build Configuration
### Taskfile.yml
Wails uses [Taskfile](https://taskfile.dev/) for build configuration. The main `Taskfile.yml` in the project root includes platform-specific Taskfiles:
```yaml
# Taskfile.yml
version: '3'
includes:
common: ./build/Taskfile.yml
windows: ./build/windows/Taskfile.yml
darwin: ./build/darwin/Taskfile.yml
linux: ./build/linux/Taskfile.yml
vars:
APP_NAME: "myproject"
BIN_DIR: "bin"
VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}'
tasks:
build:
summary: Builds the application
cmds:
- task: "{{OS}}:build"
package:
summary: Packages a production build of the application
cmds:
- task: "{{OS}}:package"
run:
summary: Runs the application
cmds:
- task: "{{OS}}:run"
dev:
summary: Runs the application in development mode
cmds:
- wails3 dev -config ./build/config.yml -port {{.VITE_PORT}}
```
**Run tasks:**
```bash
wails3 task build
wails3 task package
wails3 task run
```
## Asset Embedding
### How It Works
Wails uses Go's `embed` package:
```go
package main
import (
"embed"
"github.com/wailsapp/wails/v3/pkg/application"
)
//go:embed frontend/dist
var assets embed.FS
func main() {
app := application.New(application.Options{
Name: "My App",
Assets: application.AssetOptions{
Handler: application.AssetFileServerFS(assets),
},
})
app.Window.New()
app.Run()
}
```
**At build time:**
1. Frontend built to `frontend/dist/`
2. `//go:embed` directive includes files
3. Files compiled into binary
4. Binary contains everything
**At runtime:**
1. App starts
2. Assets served from memory
3. No disk I/O for assets
4. Fast loading
### Custom Assets
Embed additional files:
```go
//go:embed frontend/dist
var frontendAssets embed.FS
//go:embed data/*.json
var dataAssets embed.FS
//go:embed templates/*.html
var templateAssets embed.FS
```
## Build Optimisations
### Frontend Optimisations
**Vite (default):**
```javascript
// vite.config.js
export default {
build: {
minify: 'terser',
terserOptions: {
compress: {
drop_console: true, // Remove console.log
drop_debugger: true,
},
},
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'], // Separate vendor bundle
},
},
},
},
}
```
**Results:**
- JavaScript minified (~70% reduction)
- CSS minified (~60% reduction)
- Images optimised
- Tree-shaking applied
### Go Optimisations
**Compiler flags:**
```bash
-ldflags="-s -w"
```
- `-s`: Strip symbol table (~10% reduction)
- `-w`: Strip DWARF debug info (~20% reduction)
**Additional optimisations:**
```bash
-ldflags="-s -w -X main.version=1.0.0"
```
- `-X`: Set variable values at build time
- Useful for version numbers, build dates
### Binary Compression
**UPX (optional):**
```bash
# After building
upx --best bin/myapp.exe
```
**Results:**
- ~50% size reduction
- Slightly slower startup (~100ms)
- Not recommended for macOS (code signing issues)
## Platform-Specific Builds
### Windows
**Output:** `myapp.exe`
**Includes:**
- Application icon
- Version information
- Manifest (UAC settings)
**Icon:**
Icon generation is handled by the platform-specific Taskfile. The default Windows Taskfile generates an `.ico` file from `build/appicon.png` using the `wails3 generate icons` command.
**Manifest:**
```xml
<!-- build/windows/manifest.xml -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0" name="MyApp"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
```
### macOS
**Output:** `myapp.app` (application bundle)
**Structure:**
```
myapp.app/
├── Contents/
│ ├── Info.plist # App metadata
│ ├── MacOS/
│ │ └── myapp # Binary
│ ├── Resources/
│ │ └── icon.icns # Icon
│ └── _CodeSignature/ # Code signature (if signed)
```
**Info.plist:**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>My App</string>
<key>CFBundleIdentifier</key>
<string>com.example.myapp</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
</dict>
</plist>
```
**Universal Binary:**
The macOS Taskfile supports building universal binaries. You can also use the `wails3 tool lipo` command to combine architecture-specific binaries:
```bash
wails3 tool lipo -i bin/myapp-amd64 -i bin/myapp-arm64 -output bin/myapp-universal
```
### Linux
**Output:** `myapp` (ELF binary)
**Dependencies:**
- GTK3
- WebKitGTK
**Desktop file:**
```ini
# myapp.desktop
[Desktop Entry]
Name=My App
Exec=/usr/bin/myapp
Icon=myapp
Type=Application
Categories=Utility;
```
**Installation:**
```bash
# Copy binary
sudo cp myapp /usr/bin/
# Copy desktop file
sudo cp myapp.desktop /usr/share/applications/
# Copy icon
sudo cp icon.png /usr/share/icons/hicolor/256x256/apps/myapp.png
```
## Build Performance
### Typical Build Times
| Phase | Time | Notes |
|-------|------|-------|
| Analysis | <1s | Go code scanning |
| Binding Generation | <1s | TypeScript generation |
| Frontend Build | 5-30s | Depends on project size |
| Go Compilation | 2-10s | Depends on code size |
| Asset Embedding | <1s | Embedding frontend |
| **Total** | **10-45s** | First build |
| **Incremental** | **5-15s** | Subsequent builds |
### Speeding Up Builds
**1. Use build cache:**
```bash
# Go build cache is automatic
# Frontend cache (Vite)
npm run build # Uses cache by default
```
**2. Use faster tools:**
```bash
# Use esbuild instead of webpack
# (Vite uses esbuild by default)
```
## Troubleshooting
### Build Fails
**Symptom:** `wails3 build` exits with error
**Common causes:**
1. **Go compilation error**
```bash
# Check Go code compiles
go build
```
2. **Frontend build error**
```bash
# Check frontend builds
cd frontend
npm run build
```
3. **Missing dependencies**
```bash
# Install dependencies
npm install
go mod download
```
### Binary Too Large
**Symptom:** Binary is >50MB
**Solutions:**
1. **Strip debug symbols** (should be automatic)
The default Taskfiles include `-ldflags="-s -w"` in the build task.
2. **Check embedded assets**
```bash
# Remove unnecessary files from frontend/dist/
# Check for large images, videos, etc.
```
3. **Use UPX compression**
```bash
upx --best bin/myapp.exe
```
### Slow Builds
**Symptom:** Builds take >1 minute
**Solutions:**
1. **Use build cache**
- Go cache is automatic
- Frontend cache (Vite) is automatic
2. **Optimise frontend build**
```javascript
// vite.config.js
export default {
build: {
minify: 'esbuild', // Faster than terser
},
}
```
## Best Practices
### ✅ Do
- **Use `wails3 dev` during development** - Fast iteration
- **Use `wails3 build` for releases** - Optimised output
- **Version your builds** - Use `-ldflags` in your Taskfile to embed version
- **Test builds on target platforms** - Cross-compilation isn't perfect
- **Keep frontend builds fast** - Optimise bundler config
- **Use build cache** - Speeds up subsequent builds
### ❌ Don't
- **Don't commit `build/` directory** - Add to `.gitignore`
- **Don't skip testing builds** - Always test before release
- **Don't embed unnecessary assets** - Keep binaries small
- **Don't use debug builds for production** - Use optimised builds
- **Don't forget code signing** - Required for distribution
## Next Steps
**Building Applications** - Detailed guide to building and packaging
[Learn More →](/guides/building)
**Cross-Platform Builds** - Build for all platforms from one machine
[Learn More →](/guides/cross-platform)
**Creating Installers** - Create installers for end users
[Learn More →](/guides/installers)
---
**Questions about building?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [build examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/build).

View file

@ -0,0 +1,696 @@
---
title: Application Lifecycle
description: Understanding the Wails application lifecycle from startup to shutdown
sidebar:
order: 2
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
## Understanding Application Lifecycle
Desktop applications have a lifecycle from startup to shutdown. Wails provides hooks at each stage to **initialise resources**, **clean up properly**, **handle errors gracefully**, and **manage multiple windows** effectively.
## The Lifecycle Stages
```d2
direction: down
Start: "Application Start" {
shape: oval
style.fill: "#10B981"
}
PreInit: "Pre-Initialisation" {
Parse: "Parse Options" {
shape: rectangle
}
Register: "Register Services" {
shape: rectangle
}
Validate: "Validate Config" {
shape: rectangle
}
}
ServiceStartup: "Service Startup" {
shape: rectangle
style.fill: "#3B82F6"
}
CreateWindows: "Create Windows" {
shape: rectangle
}
EventLoop: "Event Loop" {
Process: "Process Events" {
shape: rectangle
}
Handle: "Handle Messages" {
shape: rectangle
}
Update: "Update UI" {
shape: rectangle
}
}
QuitSignal: "Quit Signal" {
shape: diamond
style.fill: "#F59E0B"
}
ShouldQuit: "ShouldQuit Check" {
shape: rectangle
style.fill: "#3B82F6"
}
OnShutdown: "Shutdown Hooks" {
shape: rectangle
style.fill: "#3B82F6"
}
Cleanup: "Cleanup" {
Close: "Close Windows" {
shape: rectangle
}
Release: "Release Resources" {
shape: rectangle
}
}
End: "Application End" {
shape: oval
style.fill: "#EF4444"
}
Start -> PreInit.Parse
PreInit.Parse -> PreInit.Register
PreInit.Register -> PreInit.Validate
PreInit.Validate -> ServiceStartup
ServiceStartup -> CreateWindows
CreateWindows -> EventLoop.Process
EventLoop.Process -> EventLoop.Handle
EventLoop.Handle -> EventLoop.Update
EventLoop.Update -> EventLoop.Process: "Loop"
EventLoop.Process -> QuitSignal: "User quits"
QuitSignal -> ShouldQuit: "Can cancel?"
ShouldQuit -> EventLoop.Process: "Cancelled"
ShouldQuit -> OnShutdown: "Confirmed"
OnShutdown -> Cleanup.Close
Cleanup.Close -> Cleanup.Release
Cleanup.Release -> End
```
### 1. Pre-Initialisation
Before your code runs, Wails:
1. Parses `application.Options`
2. Registers services
3. Validates configuration
4. Sets up the runtime
**You don't control this phase** - it happens automatically.
### 2. Service Startup
Your first opportunity to run code is through the `ServiceStartup` interface. Services that implement this interface receive a startup notification during `app.Run()`:
```go
type AppService struct {
db *sql.DB
config *Config
}
func (s *AppService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
// Initialise database
var err error
s.db, err = sql.Open("sqlite3", "app.db")
if err != nil {
return fmt.Errorf("failed to open database: %w", err)
}
// Load configuration
s.config, err = loadConfig()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
return nil
}
app := application.New(application.Options{
Name: "My App",
Services: []application.Service{
application.NewService(&AppService{}),
},
})
```
**When it runs:** During `app.Run()`, before the event loop starts
**Use it for:**
- Database connections
- Configuration loading
- Resource initialisation
- Authentication checks
**Context:** The `context.Context` is valid as long as the application is running and is cancelled right before shutdown.
### 3. Window Creation
After services start up, you create windows:
```go
window := app.Window.New()
```
**What happens:**
1. Window is created (but not shown)
2. WebView is initialised
3. Frontend assets are loaded
4. Window is shown (unless `Hidden: true`)
### 4. Event Loop
The application enters the event loop:
```go
err := app.Run() // Blocks here until quit
```
**What happens in the loop:**
- OS events processed (mouse, keyboard, window events)
- Go-to-JS messages handled
- JS-to-Go calls executed
- UI updates rendered
**This is where your application spends most of its time.**
### 5. Quit Signal
User triggers quit via:
- Closing last window (default behaviour)
- Cmd+Q / Alt+F4 / File → Quit
- Your code calling `app.Quit()`
### 6. ShouldQuit / Window Close Prevention
**Application-level quit prevention with `ShouldQuit`:**
```go
app := application.New(application.Options{
ShouldQuit: func() bool {
// Return false to cancel quit
// Return true to allow quit
if hasUnsavedChanges() {
return false
}
return true
},
})
```
**Window-level close prevention with `RegisterHook`:**
```go
window := app.Window.New()
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
if hasUnsavedChanges() {
e.Cancel() // Prevent the window from closing
}
})
```
**Use cases:**
- Confirm quit with unsaved changes
- Prevent accidental closure
- Save state before quitting
### 7. Shutdown Hooks
There are multiple hooks for shutdown:
**`OnShutdown` (application option)** - runs first during shutdown:
```go
app := application.New(application.Options{
OnShutdown: func() {
// Save application state
saveState()
},
})
```
**`app.OnShutdown()` (runtime registration)** - add shutdown tasks dynamically:
```go
app.OnShutdown(func() {
// Additional cleanup
cleanup()
})
```
**`ServiceShutdown` (service interface)** - services shut down in reverse registration order:
```go
func (s *MyService) ServiceShutdown() error {
// Close database, release resources
return s.db.Close()
}
```
**`PostShutdown` (application option)** - runs after everything else, just before process exit:
```go
app := application.New(application.Options{
PostShutdown: func() {
log.Println("Application has finished shutting down")
},
})
```
**Shutdown order:**
1. `OnShutdown` callbacks (in order added)
2. `ServiceShutdown` (reverse registration order)
3. Windows closed, system trays destroyed
4. `PostShutdown` callback
**Important:** Keep shutdown fast. OS may force-kill if too slow.
### 8. Cleanup & Exit
Wails automatically:
1. Closes all windows
2. Releases WebView resources
3. Exits the process
## Lifecycle Hooks Reference
| Hook | When | Can Cancel? | Use For |
|------|------|-------------|---------|
| `ServiceStartup` | During `app.Run()`, before event loop | Yes (return error) | Initialisation |
| `ShouldQuit` | App quit requested | Yes (return false) | Confirm quit |
| `ShouldClose` | Window closing | Yes (return false) | Prevent window close |
| `OnShutdown` | After quit confirmed | No | Cleanup |
| `PostShutdown` | After shutdown complete | No | Logging, testing |
## Common Patterns
### Pattern 1: Database Lifecycle
```go
type DatabaseService struct {
db *sql.DB
}
func (d *DatabaseService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
var err error
d.db, err = sql.Open("sqlite3", "app.db")
if err != nil {
return fmt.Errorf("failed to open database: %w", err)
}
// Run migrations
if err := runMigrations(d.db); err != nil {
return fmt.Errorf("migrations failed: %w", err)
}
return nil
}
func (d *DatabaseService) ServiceShutdown() error {
if d.db != nil {
return d.db.Close()
}
return nil
}
app := application.New(application.Options{
Services: []application.Service{
application.NewService(&DatabaseService{}),
},
})
```
### Pattern 2: Configuration Management
```go
type Config struct {
Theme string
Language string
WindowPos Point
}
type ConfigService struct {
config *Config
}
func (c *ConfigService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
var err error
c.config, err = loadConfig() // Load from disk
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
return nil
}
func (c *ConfigService) ServiceShutdown() error {
return saveConfig(c.config) // Save to disk
}
app := application.New(application.Options{
Services: []application.Service{
application.NewService(&ConfigService{}),
},
})
```
### Pattern 3: Confirm Quit with Unsaved Changes
Use `RegisterHook` with `events.Common.WindowClosing` to prevent window close, or `ShouldQuit` on the application options:
```go
// Application-level: prevent quit
app := application.New(application.Options{
ShouldQuit: func() bool {
if hasUnsavedChanges {
// Return false to prevent quit
return false
}
return true
},
})
// Window-level: prevent individual window close
window := app.Window.New()
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
if hasUnsavedChanges {
e.Cancel() // Prevent window from closing
}
})
```
### Pattern 4: Background Tasks
```go
type SyncService struct{}
func (s *SyncService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
// Start background task
go func() {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
performBackgroundSync()
case <-ctx.Done():
// Context cancelled, quit
return
}
}
}()
return nil
}
app := application.New(application.Options{
Services: []application.Service{
application.NewService(&SyncService{}),
},
})
```
**Important:** Use `ctx.Done()` to know when to stop background tasks. The context is cancelled right before shutdown.
## Window Lifecycle
Each window has its own lifecycle:
```d2
direction: down
Create: "Create Window" {
shape: oval
style.fill: "#10B981"
}
Load: "Load Frontend" {
shape: rectangle
}
Show: "Show Window" {
shape: rectangle
}
Active: "Window Active" {
Events: "Handle Events" {
shape: rectangle
}
}
CloseRequest: "Close Request" {
shape: diamond
style.fill: "#F59E0B"
}
CloseHook: "WindowClosing Hook" {
shape: rectangle
style.fill: "#3B82F6"
}
Destroy: "Destroy Window" {
shape: rectangle
}
End: "Window Closed" {
shape: oval
style.fill: "#EF4444"
}
Create -> Load
Load -> Show
Show -> Active.Events
Active.Events -> Active.Events: "Loop"
Active.Events -> CloseRequest: "User closes"
CloseRequest -> CloseHook
CloseHook -> Active.Events: "Cancelled"
CloseHook -> Destroy: "Confirmed"
Destroy -> End
```
**Key points:**
- Each window is independent
- Closing last window quits app (by default)
- Windows can prevent their own closure
## Multi-Window Lifecycle
With multiple windows:
```go
// Create main window
mainWindow := app.Window.New()
// Create secondary window
secondaryWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Settings",
Width: 400,
Height: 600,
})
// Closing secondary window doesn't quit app
// Closing main window quits app (closes all windows)
```
**Default behaviour:**
- Closing any window closes just that window
- Closing the **last** window quits the application
**Custom behaviour:**
```go
// Prevent app quit when last window closes
app := application.New(application.Options{
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: false,
},
})
// Now app stays running even with no windows
// Useful for menu bar / system tray apps
```
## Error Handling During Lifecycle
### Startup Errors
Return an error from `ServiceStartup` to abort application startup. The error is propagated from `app.Run()`:
```go
func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
if err := initialise(); err != nil {
return fmt.Errorf("initialisation failed: %w", err)
}
return nil
}
// In main:
err := app.Run()
if err != nil {
log.Fatal("Failed to start:", err)
}
```
### Shutdown Errors
Errors returned from `ServiceShutdown` are logged but do not prevent shutdown:
```go
func (s *MyService) ServiceShutdown() error {
if err := saveState(); err != nil {
// Error will be logged by Wails
return fmt.Errorf("failed to save state: %w", err)
}
return nil
}
```
**Important:** Shutdown hooks run during quit - don't show dialogs or try to cancel.
## Platform Differences
### macOS
- **Application menu** persists even with no windows
- **Cmd+Q** always quits (can't be prevented)
- **Dock icon** remains unless hidden
### Windows
- **No application menu** without a window
- **Alt+F4** closes window (can be prevented)
- **System tray** can keep app running
### Linux
- **Behaviour varies** by desktop environment
- **Generally similar to Windows**
## Debugging Lifecycle Issues
### Problem: Resources Not Cleaned Up
**Symptom:** Database connections left open, files not closed
**Solution:** Implement `ServiceShutdown` on your services:
```go
func (s *MyService) ServiceShutdown() error {
log.Println("Cleaning up...")
// Your cleanup code
return s.db.Close()
}
```
### Problem: Application Won't Quit
**Symptom:** App hangs when trying to quit
**Causes:**
1. `ShouldQuit` returning `false`
2. Window close hook cancelling close events
3. Shutdown tasks taking too long
4. Background goroutines not stopping
**Solution:**
```go
// 1. Check ShouldQuit logic
app := application.New(application.Options{
ShouldQuit: func() bool {
log.Println("ShouldQuit called")
return true // Allow quit
},
})
// 2. Keep shutdown fast
app.OnShutdown(func() {
log.Println("Shutdown started")
// Fast cleanup only
log.Println("Shutdown finished")
})
// 3. Stop background tasks using context
func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
go func() {
for {
select {
case <-ctx.Done():
log.Println("Background task stopped")
return
default:
// Work
}
}
}()
return nil
}
```
### Problem: Initialisation Fails Silently
**Symptom:** App starts but doesn't work correctly
**Solution:** Return errors from `ServiceStartup` to abort startup:
```go
func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
if err := initialise(); err != nil {
return fmt.Errorf("initialisation failed: %w", err)
}
return nil
}
// In main - app.Run() returns the error:
if err := app.Run(); err != nil {
log.Fatal("Failed to start:", err)
}
```
## Best Practices
### ✅ Do
- **Initialise in ServiceStartup** - Database, config, resources
- **Clean up in ServiceShutdown** - Close connections, save state
- **Keep shutdown fast** - &lt;1 second
- **Use context for cancellation** - Stop background tasks
- **Handle errors gracefully** - Return errors from ServiceStartup
- **Test quit scenarios** - Unsaved changes, background tasks
### ❌ Don't
- **Don't block ServiceStartup** - Keep it fast (&lt;2 seconds)
- **Don't show dialogs during shutdown** - App is quitting
- **Don't ignore errors** - Log or return them
- **Don't leak resources** - Always clean up in ServiceShutdown
- **Don't forget background tasks** - Stop them using ctx.Done()
## Next Steps
**Go-Frontend Bridge** - Understand how Go and JavaScript communicate
[Learn More →](/concepts/bridge)
**Build System** - Learn how Wails builds your application
[Learn More →](/concepts/build-system)
**Events System** - Use events for communication between components
[Learn More →](/features/events/system)
**Window Management** - Create and manage multiple windows
[Learn More →](/features/windows/basics)
---
**Questions about lifecycle?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).

View file

@ -0,0 +1,266 @@
---
title: Manager API
description: Organized API structure with focused manager interfaces
sidebar:
order: 2
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
The Wails v3 Manager API provides an organized and discoverable way to access application functionality through focused manager structs. This new API structure groups related methods together while maintaining full backward compatibility with the traditional App API.
## Overview
The Manager API organizes application functionality into eleven focused areas:
- **`app.Window`** - Window creation, management, and callbacks
- **`app.ContextMenu`** - Context menu registration and management
- **`app.KeyBinding`** - Global key binding management
- **`app.Browser`** - Browser integration (opening URLs and files)
- **`app.Env`** - Environment information and system state
- **`app.Dialog`** - File and message dialog operations
- **`app.Event`** - Custom event handling and application events
- **`app.Menu`** - Application menu management
- **`app.Screen`** - Screen management and coordinate transformations
- **`app.Clipboard`** - Clipboard text operations
- **`app.SystemTray`** - System tray icon creation and management
## Benefits
- **Better discoverability** - IDE autocomplete shows organized API surface
- **Improved code organization** - Related methods grouped together
- **Enhanced maintainability** - Separation of concerns across managers
- **Future extensibility** - Easier to add new features to specific areas
## Usage
The Manager API provides organized access to all application functionality:
```go
// Events and custom event handling
app.Event.Emit("custom", data)
app.Event.On("custom", func(e *CustomEvent) { ... })
// Window management
window, _ := app.Window.GetByName("main")
app.Window.OnCreate(func(window Window) { ... })
// Browser integration
app.Browser.OpenURL("https://wails.io")
// Menu management
menu := app.Menu.New()
app.Menu.Set(menu)
// System tray
systray := app.SystemTray.New()
```
## Manager Reference
### Window Manager
Manages window creation, retrieval, and lifecycle callbacks.
```go
// Create windows
window := app.Window.New()
window := app.Window.NewWithOptions(options)
current := app.Window.Current()
// Find windows
window, exists := app.Window.GetByName("main")
windows := app.Window.GetAll()
// Window callbacks
app.Window.OnCreate(func(window Window) {
// Handle window creation
})
```
### Event Manager
Handles custom events and application event listening.
```go
// Custom events
app.Event.Emit("userAction", data)
cancelFunc := app.Event.On("userAction", func(e *CustomEvent) {
// Handle event
})
app.Event.Off("userAction")
app.Event.Reset() // Remove all listeners
// Application events
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *ApplicationEvent) {
// Handle system theme change
})
```
### Browser Manager
Provides browser integration for opening URLs and files.
```go
// Open URLs and files in default browser
err := app.Browser.OpenURL("https://wails.io")
err := app.Browser.OpenFile("/path/to/document.pdf")
```
### Environment Manager
Access to system environment information.
```go
// Get environment info
env := app.Env.Info()
fmt.Printf("OS: %s, Arch: %s\n", env.OS, env.Arch)
// Check system theme
if app.Env.IsDarkMode() {
// Dark mode is active
}
// Open file manager
err := app.Env.OpenFileManager("/path/to/folder", false)
```
### Dialog Manager
Organized access to file and message dialogs.
```go
// File dialogs
result, err := app.Dialog.OpenFile().
AddFilter("Text Files", "*.txt").
PromptForSingleSelection()
result, err = app.Dialog.SaveFile().
SetFilename("document.txt").
PromptForSingleSelection()
// Message dialogs
app.Dialog.Info().
SetTitle("Information").
SetMessage("Operation completed successfully").
Show()
app.Dialog.Error().
SetTitle("Error").
SetMessage("An error occurred").
Show()
```
### Menu Manager
Application menu creation and management.
```go
// Create and set application menu
menu := app.Menu.New()
fileMenu := menu.AddSubmenu("File")
fileMenu.Add("New").OnClick(func(ctx *Context) {
// Handle menu click
})
app.Menu.Set(menu)
// Show about dialog
app.Menu.ShowAbout()
```
### Key Binding Manager
Dynamic management of global key bindings.
```go
// Add key bindings
app.KeyBinding.Add("ctrl+n", func(window application.Window) {
// Handle Ctrl+N
})
app.KeyBinding.Add("ctrl+q", func(window application.Window) {
app.Quit()
})
// Remove key bindings
app.KeyBinding.Remove("ctrl+n")
// Get all bindings
bindings := app.KeyBinding.GetAll()
```
### Context Menu Manager
Advanced context menu management (for library authors).
```go
// Create and register context menu
menu := app.ContextMenu.New()
app.ContextMenu.Add("myMenu", menu)
// Retrieve context menu
menu, exists := app.ContextMenu.Get("myMenu")
// Remove context menu
app.ContextMenu.Remove("myMenu")
```
### Screen Manager
Screen management and coordinate transformations for multi-monitor setups.
```go
// Get screen information
screens := app.Screen.GetAll()
primary := app.Screen.GetPrimary()
// Coordinate transformations
physicalPoint := app.Screen.DipToPhysicalPoint(logicalPoint)
logicalPoint := app.Screen.PhysicalToDipPoint(physicalPoint)
// Screen detection
screen := app.Screen.ScreenNearestDipPoint(point)
screen = app.Screen.ScreenNearestDipRect(rect)
```
### Clipboard Manager
Clipboard operations for reading and writing text.
```go
// Set text to clipboard
success := app.Clipboard.SetText("Hello World")
if !success {
// Handle error
}
// Get text from clipboard
text, ok := app.Clipboard.Text()
if !ok {
// Handle error
} else {
// Use the text
}
```
### SystemTray Manager
System tray icon creation and management.
```go
// Create system tray
systray := app.SystemTray.New()
systray.SetLabel("My App")
systray.SetIcon(iconBytes)
// Add menu to system tray
menu := app.Menu.New()
menu.Add("Open").OnClick(func(ctx *Context) {
// Handle click
})
systray.SetMenu(menu)
// Destroy system tray when done
systray.Destroy()
```

View file

@ -0,0 +1,275 @@
---
title: Contributing
description: Contribute to Wails
sidebar:
order: 100
---
import { Card, CardGrid } from "@astrojs/starlight/components";
## Welcome Contributors!
We welcome contributions to Wails! Whether you're fixing bugs, adding features, or improving documentation, your help is appreciated.
## Ways to Contribute
### 1. Report Issues
Found a bug? [Open an issue](https://github.com/wailsapp/wails/issues/new) with:
- Clear description
- Steps to reproduce
- Expected vs actual behaviour
- System information
- Code samples
### 2. Improve Documentation
Documentation improvements are always welcome:
- Fix typos and errors
- Add examples
- Clarify explanations
- Translate content
### 3. Submit Code
Contribute code through pull requests:
- Bug fixes
- New features
- Performance improvements
- Tests
## Getting Started
### Fork and Clone
```bash
# Fork the repository on GitHub
# Then clone your fork
git clone https://github.com/YOUR_USERNAME/wails.git
cd wails
# Add upstream remote
git remote add upstream https://github.com/wailsapp/wails.git
```
### Build from Source
```bash
# Install dependencies
go mod download
# Build Wails CLI
cd v3/cmd/wails3
go build
# Test your build
./wails3 version
```
### Run Tests
```bash
# Run all tests
go test ./...
# Run specific package tests
go test ./v3/pkg/application
# Run with coverage
go test -cover ./...
```
## Making Changes
### Create a Branch
```bash
# Update main
git checkout main
git pull upstream main
# Create feature branch
git checkout -b feature/my-feature
```
### Make Your Changes
1. **Write code** following Go conventions
2. **Add tests** for new functionality
3. **Update documentation** if needed
4. **Run tests** to ensure nothing breaks
5. **Commit changes** with clear messages
### Commit Guidelines
```bash
# Good commit messages
git commit -m "fix: resolve window focus issue on macOS"
git commit -m "feat: add support for custom window chrome"
git commit -m "docs: improve bindings documentation"
# Use conventional commits:
# - feat: New feature
# - fix: Bug fix
# - docs: Documentation
# - test: Tests
# - refactor: Code refactoring
# - chore: Maintenance
```
### Submit Pull Request
```bash
# Push to your fork
git push origin feature/my-feature
# Open pull request on GitHub
# Provide clear description
# Reference related issues
```
## Pull Request Guidelines
### Good PR Description
```markdown
## Description
Brief description of changes
## Changes
- Added feature X
- Fixed bug Y
- Updated documentation
## Testing
- Tested on macOS 14
- Tested on Windows 11
- All tests passing
## Related Issues
Fixes #123
```
### PR Checklist
- [ ] Code follows Go conventions
- [ ] Tests added/updated
- [ ] Documentation updated
- [ ] All tests passing
- [ ] No breaking changes (or documented)
- [ ] Commit messages clear
## Code Guidelines
### Go Code Style
```go
// ✅ Good: Clear, documented, tested
// ProcessData processes the input data and returns the result.
// It returns an error if the data is invalid.
func ProcessData(data string) (string, error) {
if data == "" {
return "", errors.New("data cannot be empty")
}
result := process(data)
return result, nil
}
// ❌ Bad: No docs, no error handling
func ProcessData(data string) string {
return process(data)
}
```
### Testing
```go
func TestProcessData(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{"valid input", "test", "processed", false},
{"empty input", "", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ProcessData(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("ProcessData() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("ProcessData() = %v, want %v", got, tt.want)
}
})
}
}
```
## Documentation
### Writing Docs
Documentation uses Starlight (Astro):
```bash
cd docs
npm install
npm run dev
```
### Documentation Style
- Use International English spelling
- Start with the problem
- Provide working examples
- Include troubleshooting
- Cross-reference related content
## Community
### Get Help
- **Discord:** [Join our community](https://discord.gg/JDdSxwjhGf)
- **GitHub Discussions:** Ask questions
- **GitHub Issues:** Report bugs
### Code of Conduct
Be respectful, inclusive, and professional. We're all here to build great software together.
## Recognition
Contributors are recognised in:
- Release notes
- Contributors list
- GitHub insights
Thank you for contributing to Wails! 🎉
## Next Steps
<CardGrid>
<Card title="GitHub Repository" icon="github">
Visit the Wails repository.
[View on GitHub →](https://github.com/wailsapp/wails)
</Card>
<Card title="Discord Community" icon="discord">
Join the community.
[Join Discord →](https://discord.gg/JDdSxwjhGf)
</Card>
<Card title="Documentation" icon="open-book">
Read the docs.
[Browse Docs →](/quick-start/why-wails)
</Card>
</CardGrid>

View file

@ -0,0 +1,9 @@
{
"label": "Technical Documentation",
"link": {
"type": "generated-index",
"description": "Deep dive into Wails v3 internals for developers who want to understand or contribute to the codebase."
},
"order": 50,
"collapsed": false
}

View file

@ -0,0 +1,170 @@
---
title: Wails v3 Architecture
description: Deep-dive diagrams and explanations of every moving part inside Wails v3
sidebar:
order: 1
---
Wails v3 is a **full-stack desktop framework** consisting of a Go runtime,
a JavaScript bridge, a task-driven tool-chain and a collection of templates that
let you ship native applications powered by modern web tech.
This page presents the *big picture* in four diagrams:
1. **Overall Architecture** how every subsystem connects
2. **Runtime Flow** what happens when JS calls Go and vice-versa
3. **Development vs Production** two modes of the asset server
4. **Platform Implementations** where OS-specific code lives
---
## 1 · Overall Architecture
**Wails v3 High-Level Stack**
```d2
direction: right
Developer: "wails3 CLI"
Build: {
label: "Build-time Tool-chain"
GEN: "Binding Generator
(static analysis)"
TMP: "Template Engine"
ASSETDEV: "Asset Server (dev)"
PKG: "Cross-compilation & Packaging"
}
Runtime: {
label: "Native Runtime"
RT: "Desktop Runtime
(window, dialogs, tray, …)"
BRIDGE: "Message Bridge
(JSON channel)"
}
App: {
label: "Your Application"
BACKEND: "Go Backend"
FRONTEND: "Web Frontend
(React/Vue/…)"
}
Developer -> TMP: "init"
Developer -> GEN: "generate"
Developer -> ASSETDEV: "dev"
Developer -> PKG: "build / package"
GEN -> BACKEND: "Go & TS stubs"
GEN -> FRONTEND: "bindings.json"
ASSETDEV <-> FRONTEND: "HTTP"
BACKEND <-> BRIDGE: "calls / events"
FRONTEND <-> BRIDGE: "invoke / emit"
BRIDGE <-> RT: "native API"
RT -> ASSETDEV: "serve assets"
```
---
## 2 · Runtime Call Flow
**Runtime JavaScript ⇄ Go Calling Path**
```d2
direction: right
JS: "JavaScript
(frontend)"
Bridge: "Bridge
(WebView callback)"
MP: "Message Processor
(Go)"
GoNode: "Bound Go Function"
JS -> Bridge: "invoke(\"Greet\",\"Alice\")"
Bridge -> MP: "JSON call {t:c,id:42,…}"
MP -> GoNode: "call Greet(\"Alice\")"
GoNode -> MP: "\"Hello Alice\""
MP -> Bridge: "JSON {t:r,id:42,result:\"Hello Alice\"}"
Bridge -> JS: "Promise.resolve(\"Hello Alice\")"
```
Key points:
* **No HTTP / IPC** the bridge uses the native WebViews in-memory channel
* **Method IDs** deterministic FNV-hash enables O(1) lookup in Go
* **Promises** errors propagate as rejections with stack & code
---
## 3 · Development vs Production Asset Flow
**Dev ↔ Prod Asset Server**
```d2
direction: right
Dev: {
label: "`wails3 dev`"
VITE: "Framework Dev Server
(port 5173)"
ASDEV: "Asset Server (dev)
(proxy + disk)"
FRONTENDDEV: "Browser"
}
Prod: {
label: "`wails3 build`"
EMBED: "Embedded FS
(go:embed)"
ASPROD: "Asset Server (prod)
(read-only)"
FRONTENDPROD: "WebView Window"
}
VITE <-> ASDEV: "proxy / HMR"
ASDEV <-> FRONTENDDEV: "HTTP"
EMBED -> ASPROD: "serve assets"
ASPROD <-> FRONTENDPROD: "in-memory"
```
* In **dev** the server proxies unknown paths to the frameworks live-reload
server and serves static assets from disk.
* In **prod** the same API is backed by `go:embed`, producing a zero-dependency
binary.
---
## 4 · Platform-Specific Runtime Split
**Per-OS Runtime Files**
```d2
direction: right
Window: "runtime::Window
Shared interface (pkg/application)"
WindowDarwin: "Window_darwin
//go:build darwin
Objective-C (cgo)
NSWindow* ptr"
WindowLinux: "Window_linux
//go:build linux
Pure Go GTK calls
GtkWindow* ptr"
WindowWindows: "Window_windows
//go:build windows
Win32 API via syscall
HWND ptr"
Window -> WindowDarwin
Window -> WindowLinux
Window -> WindowWindows
```
Every feature follows this pattern:
1. **Common interface** in `pkg/application`
2. **Message processor** entry in `pkg/application/messageprocessor_*.go`
3. **Implementation** per OS under `internal/runtime/*.go` guarded by build tags
Missing functionality on an OS should return `ErrCapability` and register
availability via `internal/capabilities`.
---
## Summary
These diagrams outline **where the code lives**, **how data moves**, and
**which layers own which responsibilities**.
Keep them handy while exploring the detailed pages that follow they are your
map to the Wails v3 source tree.

View file

@ -0,0 +1,156 @@
---
title: Binding System
description: How the binding system collects, processes, and generates JavaScript/TypeScript code
sidebar:
order: 1
---
import { FileTree } from "@astrojs/starlight/components";
This guide explains how the Wails binding system works internally, providing insights for developers who want to understand the mechanics behind the automatic code generation.
## Architecture Overview
The Wails binding system consists of three main components:
1. **Collection**: Analyzes Go code to extract information about services, models, and other declarations
2. **Configuration**: Manages settings and options for the binding generation process
3. **Rendering**: Generates JavaScript/TypeScript code based on the collected information
<FileTree>
- internal/generator/
- collect/ # Package analysis and information extraction
- config/ # Configuration structures and interfaces
- render/ # Code generation for JS/TS
</FileTree>
## Collection Process
The collection process is responsible for analyzing Go packages and extracting information about services, models, and other declarations. This is handled by the `collect` package.
### Key Components
- **Collector**: Manages package information and caches collected data
- **Package**: Represents a Go package being analyzed and stores collected services, models, and directives
- **Service**: Collects information about service types and their methods
- **Model**: Collects detailed information about model types, including fields, values, and type parameters
- **Directive**: Parses and interprets `//wails:` directives in Go source code
### Collection Flow
1. The collector scans the Go packages specified in the project
2. It identifies service types (structs with methods that will be exposed to the frontend)
3. For each service, it collects information about its methods
4. It identifies model types (structs used as parameters or return values in service methods)
5. For each model, it collects information about its fields and type parameters
6. It processes any `//wails:` directives found in the code
## Rendering Process
The rendering process is responsible for generating JavaScript/TypeScript code based on the collected information. This is handled by the `render` package.
### Key Components
- **Renderer**: Orchestrates the rendering of service, model, and index files
- **Module**: Represents a single generated JavaScript/TypeScript module
- **Templates**: Text templates used for code generation
### Rendering Flow
1. For each service, the renderer generates a JavaScript/TypeScript file with functions that mirror the service methods
2. For each model, the renderer generates a JavaScript/TypeScript class that mirrors the model struct
3. The renderer generates index files that re-export all services and models
4. The renderer applies any custom code injections specified by `//wails:inject` directives
## Type Mapping
One of the most important aspects of the binding system is how Go types are mapped to JavaScript/TypeScript types. Here's a summary of the mapping:
| Go Type | JavaScript Type | TypeScript Type |
|---------|----------------|----------------|
| `bool` | `boolean` | `boolean` |
| `int`, `int8`, `int16`, `int32`, `int64`, `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `float32`, `float64` | `number` | `number` |
| `string` | `string` | `string` |
| `[]byte` | `Uint8Array` | `Uint8Array` |
| `[]T` | `Array<T>` | `T[]` |
| `map[K]V` | `Object` | `Record<K, V>` |
| `struct` | `Object` | Custom class |
| `interface{}` | `any` | `any` |
| `*T` | `T \| null` | `T \| null` |
| `func` | Not supported | Not supported |
| `chan` | Not supported | Not supported |
## Directives System
The binding system supports several directives that can be used to customize the generated code. These directives are added as comments in your Go code.
### Available Directives
- `//wails:inject`: Injects custom JavaScript/TypeScript code into the generated bindings
- `//wails:include`: Includes additional files with the generated bindings
- `//wails:internal`: Marks a type or method as internal, preventing it from being exported to the frontend
- `//wails:ignore`: Completely ignores a method during binding generation
- `//wails:id`: Specifies a custom ID for a method, overriding the default hash-based ID
### Directive Processing
1. During the collection phase, the collector identifies and parses directives in the Go code
2. The directives are stored with the corresponding declarations (services, methods, models, etc.)
3. During the rendering phase, the renderer applies the directives to customize the generated code
## Advanced Features
### Conditional Code Generation
The binding system supports conditional code generation using a two-character condition prefix for `include` and `inject` directives:
```
<language><style>:<content>
```
Where:
- `<language>` can be:
- `*` - Both JavaScript and TypeScript
- `j` - JavaScript only
- `t` - TypeScript only
- `<style>` can be:
- `*` - Both classes and interfaces
- `c` - Classes only
- `i` - Interfaces only
For example:
```go
//wails:inject j*:console.log("JavaScript only");
//wails:inject t*:console.log("TypeScript only");
```
### Custom Method IDs
By default, methods are identified by a hash-based ID. However, you can specify a custom ID using the `//wails:id` directive:
```go
//wails:id 42
func (s *Service) CustomIDMethod() {}
```
This can be useful for maintaining compatibility when refactoring code.
## Performance Considerations
The binding generator is designed to be efficient, but there are a few things to keep in mind:
1. The first run will be slower as it builds up a cache of packages to scan
2. Subsequent runs will be faster as they use the cached information
3. The generator processes all packages in the project, which can be time-consuming for large projects
4. You can use the `-clean` flag to clean the output directory before generation
## Debugging
If you encounter issues with the binding generation, you can use the `-v` flag to enable debug output:
```bash
wails3 generate bindings -v
```
This will provide detailed information about the collection and rendering process, which can help identify the source of the issue.

View file

@ -0,0 +1,203 @@
---
title: Asset Server
description: How Wails v3 serves and embeds your web assets in development and production
sidebar:
order: 4
---
## Overview
Every Wails application ships a **single native executable** that combines:
1. Your *Go* backend
2. A *Web* frontend (HTML + JS + CSS)
The **Asset Server** is the glue that makes this possible.
It has **two operating modes** that are selected at compile-time via Go build
tags:
| Mode | Tag | Purpose |
|------|-----|---------|
| **Development** | `//go:build dev` | Fast iteration with hot-reload |
| **Production** | `//go:build !dev` | Zero-dependency, embedded assets |
The implementation lives in
`v3/internal/assetserver/` with clear file splits:
```
assetserver_dev.go # ⬅️ runtime dev server
assetserver_production.go # ⬅️ embedded server
assetserver_darwin.go # OS-specific helpers (same for linux/windows)
asset_fileserver.go # Shared static file logic
content_type_sniffer.go # MIME type detection
ringqueue.go # Tiny LRU for mime cache
```
---
## Development Mode
### Lifecycle
1. `wails3 dev` boots and **spawns your frontend dev server**
(Vite, SvelteKit, React-SWC …) by running the task defined in
`frontend/Taskfile.yml` (usually `npm run dev`).
2. Wails starts the **Dev Asset Server** listening on `localhost:<random>` and
tells the Go runtime to load `http://<host>:<port>` as the window URL.
3. Incoming requests are handled by `assetserver_dev.go`:
```
┌─────────┐ /runtime/... ┌─────────────┐
│ Browser │ ── native bridge ───▶ │ Runtime │
├─────────┤ └─────────────┘
│ JS │ / (index.html) proxy / -> Vite
└─────────┘ ◀─────────────┐
AssetServer │
┌────────────┐
│ Vite Dev │
│ Server │
└────────────┘
```
4. Static files (`/assets/logo.svg`) are **served directly from disk** via
`asset_fileserver.go` (for speed) while anything unknown is **proxied** to
the framework dev server, giving you *instant* hot-module replacement.
### Features
* **Live Reload** Vite/Snowpack/… injects HMR WebSocket; Wails only has to
proxy it.
* **Source Map Support** because assets are not bundled, your browser devtools
map errors back to original source.
* **No Go Re-compile** Only the frontend rebuilds; Go code stays running until
you change `.go` files.
### Switching Frameworks
The dev proxy is **framework-agnostic**:
* The `wails.json` template injects two env vars:
`FRONTEND_DEV_HOST` & `FRONTEND_DEV_PORT`.
* Taskfiles for each template emit those vars before running their dev servers.
* `assetserver_dev.go` simply proxies to that target.
Add a new template → define its dev task → Asset Server just works.
---
## Production Mode
When you run `wails3 build` the pipeline:
1. Runs the frontend **production build** (`npm run build`) producing
`/frontend/dist/**`.
2. **Embeds** that folder into `go:embed` FS at compile time (see
`bundled_assetserver.go` generated file).
3. Compiles the Go binary with `-tags production` (implicit).
### Request Handling
```go
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 1. Try embedded static assets (exact path)
// 2. Fallback to index.html for SPA routing
// 3. Sniff content-type if extension unknown
// 4. Set strong cache headers
}
```
* **MIME Detection** If the build tool produced extension-less files (e.g.
`/assets/manifest`) `content_type_sniffer.go` inspects the first 512 bytes and
caches the result in a tiny lock-free LRU.
* **Ring Queue Caching** Frequently accessed assets (logo, CSS) are kept
in-memory for the lifetime of the app, removing the need for disk or embed FS
lookups.
* **Security Headers** Disallows `file://` navigation, enables `nosniff`.
Because everything is embedded, the shipped binary has **no external
dependencies** (even on Windows).
---
## Bridging Dev ↔ Prod
Both modes expose the **same public interface**:
```go
type AssetServer interface {
URL() string // dev: http://localhost:34115, prod: wails://app
Open() error // start listening
Close() // graceful shutdown
}
```
`pkg/application` happily uses whichever implementation was compiled in, meaning
**your application code does not change** between `dev` and `build`.
---
## How Frontend Frameworks Integrate
### Templates
Each official template (React, Vue, Svelte, Solid…) contains:
* `frontend/Taskfile.yml`
* `frontend/vite.config.ts` (or equivalent)
They export two tasks:
| Task | Purpose |
|------|---------|
| `dev` | Starts the framework dev server on a **random free port** and prints it to stdout (`PORT=5173`). |
| `build` | Produces static assets into `dist/` + manifest for cache-busting. |
`internal/commands/dev.go` parses that stdout, sets `FRONTEND_DEV_*` env vars
and launches the **Dev Asset Server**.
Frameworks remain fully decoupled from Go:
* No need to import Wails JS SDK at build time the runtime injects it at
window creation.
* Any framework with an HTTP dev server can plug in.
---
## Extending / Customising
Need custom headers, auth, or gzip?
1. Implement `type Middleware func(http.Handler) http.Handler`
2. Register via `internal/assetserver/options.go`
3. For prod, remember to add the same middleware in `assetserver_production.go`.
---
## Key Source Files
| File | Role |
|------|------|
| `assetserver_dev.go` | Reverse proxy + disk file server |
| `assetserver_production.go` | Embedded FS handler |
| `options.go` | Config struct parsed from `pkg/options/assetserver` |
| `build_dev.go` / `build_production.go` | Build-tag wrappers selecting correct implementation |
| `bundled_assetserver.go` | Generated embed data (only present in production builds) |
---
## Gotchas & Debugging
* **White Screen in Prod** usually SPA routing: ensure `History API Fallback`
is enabled in dev and `index.html` fallback works in prod.
* **404 in Dev** mis-matched `FRONTEND_DEV_PORT`; run with
`WAILSDEV_VERBOSE=1` to print every proxied request.
* **Large Assets** they are embedded; consider
[`assetserver.WithExternalDir("/path")`](https://pkg.go.dev) to load from disk.
---
You now know how the Wails **Asset Server** feeds your web code to the native
window in both **development** and **production**.
Master this layer and you can debug loading issues, add middlewares, or even
swap in a completely different frontend tool-chain with confidence.

View file

@ -0,0 +1,240 @@
---
title: Binding System
description: How Wails v3 lets Go and JavaScript call each other with zero boilerplate
sidebar:
order: 5
---
> “Bindings” are the **type-safe contract** that lets you write:
```go
msg, err := chatService.Send("Hello")
```
in Go *and*
```ts
const msg = await window.backend.ChatService.Send("Hello")
```
in TypeScript **without manual glue code**.
This document breaks down *how* that magic happens, from **static analysis** at
build time, through **code generation**, to the **runtime bridge** that moves
bytes over the WebView.
---
## 1. 30-Second Overview
| Stage | Component | Output |
|-------|-----------|--------|
| **Analysis** | `internal/generator/analyse.go` | In-memory AST of exported Go structs, methods, params, return types |
| **Generation** | `internal/generator/render/*.tmpl` | • `pkg/application/bindings.go` (Go)<br />• `frontend/src/wailsjs/**.ts` (TS defs)<br />• `runtime/desktop/@wailsio/runtime/internal/bindings.json` |
| **Runtime** | `pkg/application/messageprocessor_call.go` + JS runtime (`invoke.ts`) | JSON messages over WebView native bridge |
The flow is orchestrated by the `wails3` CLI:
```
wails3 generate ─┬─> generator.Collect() // parse Go packages
├─> generator.Analyse() // build semantic model
└─> generator.Render() // write files + `go fmt`
```
---
## 2. Static Analysis
### Entry Point
```
internal/generator/analyse.go
```
`Analyse(cfg generator.Config)`:
1. Recursively loads provided Go packages with `go/packages`.
2. Walks the *type* and *value* specs of every AST file.
3. Filters **exported** symbols (capitalised) outside `internal/` packages.
4. Records:
* Receiver type (`struct`, `interface`)
* Method name
* Parameter list (name, type, pointer, variadic)
* Return tuple (values + error presence)
5. Normalises types to a **serialisable set**
(`int`, `float64`, `string`, `[]byte`, slices, maps, structs, pointers).
Unsupported types produce a **compile-time error** so mistakes are caught early.
### Model
```go
type Method struct {
ID uint32 // stable hash for runtime lookup
Name string
Params []Param
Results []Result
Receiver *Struct // nil for package funcs
}
```
A deterministic **FNV-1a** hash (`internal/hash/fnv.go`) of
`<Receiver>.<Name>(<Signature>)` becomes the *method ID* used at runtime.
---
## 3. Code Generation
### Templates
`internal/generator/render/` contains text/template files:
| Template | Target |
|----------|--------|
| `go_bindings.tmpl` | `pkg/application/bindings.go` |
| `ts_bindings.tmpl` | `frontend/wailsjs/go/models.d.ts` |
| `ts_index.tmpl` | `frontend/wailsjs/index.ts` |
Add or adjust templates here to customise generation.
### Go Output
`bindings.go` registers a lookup table:
```go
var bindings = []application.BoundMethod{
{ID: 0x7A1201D3, Name: "ChatService.Send", Call: chatServiceSend},
}
func chatServiceSend(ctx context.Context, in []byte) ([]byte, error) {
var req struct{ Msg string }
if err := json.Unmarshal(in, &req); err != nil { return nil, err }
res, err := chatService.Send(req.Msg)
return json.Marshal(res), err
}
```
Key points:
* **Zero reflection** at runtime → performance.
* Marshal/Unmarshal is **per-method** with generated struct wrappers.
### TypeScript Output
```ts
export namespace backend {
export namespace ChatService {
function Send(msg: string): Promise<string>;
}
}
```
* Emitted as **ES modules** so any bundler can tree-shake.
* Method IDs are embedded in an auto-generated `bindings.json` for the JS
runtime.
---
## 4. Runtime Invocation Protocol
### JavaScript Side
```ts
import { invoke } from "@wailsio/runtime";
await invoke(0x7a1201d3 /* ChatService.Send */, ["Hello"]);
```
Implementation: `runtime/desktop/@wailsio/runtime/invoke.ts`
1. Packs `{t:"c", id:<methodID>, p:[...args]}` into JSON.
2. Calls `window.external.invoke(payload)` (WebView2) or equivalent.
3. Returns a `Promise` that resolves/rejects based on the reply.
### Go Side
1. `messageprocessor_call.go` receives the JSON.
2. Looks up `methodID` in the `bindings` slice.
3. Executes the generated stub (`chatServiceSend`).
4. Serialises `{result, error}` back to JS.
### Error Mapping
| Go | JavaScript |
|----|------------|
| `error == nil` | `Promise` resolves with result |
| `errors.New(...)` | `Promise` rejects with `{message, stack, code}` |
The mapping code lives in `runtime/desktop/@wailsio/runtime/errors.ts`.
---
## 5. Calling JavaScript from Go
*Browser → Go* is covered above.
*Go → Browser* uses **Events** or **Eval**:
```go
window.Eval(`alert("Hi")`)
app.Publish("chat:new-message", msg)
```
Binding generation is one-way (Go methods).
For JS-exposed functions use `runtime.EventsOn` in JS and `application.Publish`
from Go.
---
## 6. Extending & Troubleshooting
### Adding Custom Serialisers
* Implement `generator.TypeConverter` interface.
* Register in `generator.Config.Converters`.
* Update JS runtime deserialisation if needed.
### Unsupported Type Error
```
error: field "Client" uses unsupported type: chan struct{}
```
→ wrap the channel in a struct with an exposed API or redesign.
### Version Skew
Bindings are regenerated on **every** `wails3 dev` / `wails3 build`.
If IDE intellisense shows stale stubs, delete `frontend/wailsjs` and rebuild.
### Performance Tips
* Prefer **value** receivers for small structs to reduce allocations.
* Avoid large byte slices over the bridge; use `application.FileServer` instead.
* Batch multiple quick calls into one method to minimise bridge latency.
---
## 7. Key Files Map
| Concern | File |
|---------|------|
| Static analysis entry | `internal/generator/analyse.go` |
| Render pipeline | `internal/generator/generate.go` |
| Template assets | `internal/generator/render/*.tmpl` |
| Go binding table | `pkg/application/bindings.go` (generated) |
| Call processor | `pkg/application/messageprocessor_call.go` |
| JS runtime | `runtime/desktop/@wailsio/runtime/invoke.ts` |
| Errors mapping | `runtime/desktop/@wailsio/runtime/errors.ts` |
Keep this cheat-sheet handy when you trace a bridge bug.
---
## 8. Recap
1. **Generator** scans your Go code → semantic model.
2. **Templates** emit **Go stubs** + **TypeScript definitions**.
3. **Message Processor** executes stubs at runtime.
4. **JS Runtime** wraps it all in idiomatic promises.
All without reflection, without IPC servers, and without you writing a single
line of boilerplate. Thats the Wails v3 binding system. Go forth and bind!

View file

@ -0,0 +1,206 @@
---
title: Build & Packaging Pipeline
description: What happens under the hood when you run `wails3 build`, how cross-platform binaries are produced, and how installers are generated for each OS.
sidebar:
order: 6
---
`wails3 build` is a **single command** that drives a _multi-stage_ pipeline:
1. **Frontend production build** (Vite / React / …)
2. **Asset embedding** into Go sources
3. **Native compilation** for the target OS/arch
4. **Post-processing** (icon injection, version info, codesign)
5. **Packaging** into a distributable artefact (AppImage, DMG, MSI, …)
This document follows every stage, shows where the code lives, and explains how
to customise or debug it.
---
## 1. Entry Point: `internal/commands/build-assets.go`
```
wails3 build # cmd/wails3/main.go
└─ internal/commands.Build() # build-assets.go
```
High-level tasks are delegated to **Taskfile** targets so the same logic runs in
CI or locally.
| Stage | Taskfile target | Go implementation |
|-------|-----------------|-------------------|
| Frontend build | `frontend:build` | `internal/commands/task.go` |
| Embed assets | `embed:assets` | generated `bundled_assetserver.go` |
| Go compile | `build:go` | `tool_buildinfo.go`, `tool_package.go` |
| Package | `package:*` | `internal/packager`, `internal/commands/*` |
---
## 2. Frontend Production Build
1. The CLI changes into `frontend/` and executes the `build` task found in the
project `Taskfile.yml` (`npm run build` by default).
2. Output is written to `frontend/dist/`.
3. A **content hash** manifest is produced (`manifest.json`) so cache-busting
works out of the box.
If the task fails the pipeline aborts early, returning the exit code of your
build tool.
---
## 3. Embedding Assets
Implemented in `internal/assetserver/build_production.go`:
* Walks `frontend/dist` and generates a `go:embed` file
`bundled_assetserver.go` (ignored by git).
* Adds the `production` build tag.
Result: the final binary contains every HTML/JS/CSS byte, so the executable is
self-contained.
---
## 4. Native Compilation
### Build Flags
| Flag | Purpose |
|------|---------|
| `-tags production` | Select prod asset server & runtime stubs |
| `-ldflags "-s -w"` | Strip symbol table & DWARF (smaller binaries) |
| `-X internal/buildinfo.BuildTime=$(date)` | Embed reproducible metadata |
`internal/commands/tool_buildinfo.go` collects version, git commit, and build
time then injects them using `go build -ldflags`.
### Cross Compilation Matrix
| OS | Arch | Build Tag | CGO | Notes |
|----|------|-----------|-----|-------|
| Windows | amd64 / arm64 | `windows` | cgo enabled for WebView2 | Generates `.syso` via `internal/commands/syso.go` |
| macOS | amd64 / arm64 | `darwin` | cgo enabled (Objective-C) | Codesign & notarisation tasks available |
| Linux | amd64 / arm64 | `linux` | pure Go (webkit option) | GTK/WebKitGTK headers required for CGO build |
Cross-compilation is done by setting `GOOS` and `GOARCH` environment variables. If CGO is needed on the host that can't compile the target (e.g. building Windows from Linux), a cross-compiler toolchain (like `mingw-w64`) is required.
---
## 5. Post-Processing
### Icons & Resources
* **Windows** `syso.go` merges your PNG/ICO into a `.syso` that `go build`
links automatically. Also writes `manifest.xml` enabling High-DPI support.
* **macOS** `icons.go` turns `icon.png` → `.icns`, injects into
`MyApp.app/Contents/Resources`.
* **Linux** `.desktop` files are generated in `/usr/share/applications`
by each packager backend.
### Code Signing (Optional)
* macOS: `codesign` + `xcrun altool --notarize-app`
* Windows: `signtool.exe`
* Linux: Not common (repository GPG handled externally)
Task targets: `sign:mac`, `sign:windows`.
---
## 6. Packaging Back-Ends
### Linux
| Artefact | Code Path | Tooling |
|----------|-----------|---------|
| **AppImage** (default) | `internal/commands/appimage.go` | `linuxdeploy` + `linuxdeploy-plugin-gtk` |
| **deb / rpm / pacman** | `internal/packager` | `fpm` (invoked via Go) |
Linux packages can be generated using `wails3 tool package`:
```
wails3 tool package -format deb -name myapp -config build/linux/nfpm/nfpm.yaml
wails3 tool package -format rpm -name myapp -config build/linux/nfpm/nfpm.yaml
```
### macOS
* **DMG** `internal/commands/packager.go` calls `appdmg` to generate a
drag-&-drop installer.
* **ZIP** fallback if `appdmg` is missing.
* Sets CFBundle identifiers and version plist automatically.
### Windows
* **MSI** `internal/commands/packager.go` wraps **WiX** candle & light tools.
Heat autogenerates the component tree from the built `.exe`.
Extra resources:
* `internal/commands/windows_resources/` contains templated **.wxs** fragments.
* Version info injected via `rsrc` utility.
---
## 7. Build Artefact Layout
After a successful build the CLI prints:
```
bin/
├── myapp
└── myapp.AppImage
```
The exact files depend on the platform and the tasks executed.
---
## 8. Customising the Pipeline
| Need | Approach |
|------|----------|
| Run a linter before build | Add a `lint` task in **Taskfile.yml** and make `build` depend on it. |
| Extra linker flags | Add `-ldflags` to the `go build` command in your platform Taskfile. |
| Skip packaging | Run `wails3 build` instead of `wails3 package` to get a raw binary. |
| Bring your own packager | Add custom packaging tasks to your platform Taskfile. |
All Taskfile targets use environment variables exported by the CLI, so you can
reference values like `$WAILS_VERSION` or `$WAILS_PLATFORM` inside custom tasks.
---
## 9. Troubleshooting
| Symptom | Likely Cause | Fix |
|---------|--------------|-----|
| **`ld: framework not found WebKit` (mac)** | Xcode CLI tools missing | `xcode-select --install` |
| **Blank window in prod build** | Frontend build failed or SPA routing | Check `frontend/dist/index.html` exists and `History API Fallback` is set. |
| **`wixl` not found** (Linux MSI cross-build) | WiX toolset not installed in Docker image | Use `--docker` build or install WiX manually. |
| **Duplicated symbol `_WinMain`** | Mixed `windowsgui` flag and syso | Remove custom `-ldflags` or let Wails manage resources. |
For verbose output, use `wails3 task -v build` to see every command executed.
---
## 10. Key Source Map
| Concern | File |
|---------|------|
| Task runner glue | `internal/commands/task_wrapper.go` |
| Build dispatcher | `internal/commands/build-assets.go` |
| AppImage builder | `internal/commands/appimage.go` |
| Generic packager interface | `internal/packager/packager.go` |
| Windows resource generator | `internal/commands/syso.go` |
| Build info injector | `internal/commands/tool_buildinfo.go` |
| Version constants | `internal/version/version.go` |
Keep this table handy when you trace a build failure.
---
You now have the full picture from **source code** to **installer**. Armed with
this knowledge you can tweak the pipeline, add new packaging targets, or debug
cross-compilation issues without fear. Happy shipping!

View file

@ -0,0 +1,216 @@
---
title: Codebase Layout
description: How the Wails v3 repository is organised and how the pieces fit together
sidebar:
order: 2
---
Wails v3 lives in a **monorepo** that contains the framework runtime, CLI,
examples, documentation, and build tool-chain.
This page walks through the *directory structure* that matters to anyone digging
into the internals.
## Top-Level View
```
wails/
├── v3/ # ⬅️ Everything specific to Wails v3 lives here
├── v2/ # Legacy v2 implementation (can be ignored for v3 work)
├── docs/ # Astro-powered v3 docs site (this page!)
├── website/ # Docusaurus v2 site and marketing pages (main site)
├── scripts/ # Misc helper scripts (e.g. sponsor image generator)
├── mkdocs-website/ # Deprecated v3 docs prototype
└── *.md # Project-wide meta files (CHANGELOG, LICENSE, …)
```
From here on, we zoom into the **`v3/`** tree.
## `v3/` Root
```
v3/
├── cmd/ # Compilable commands (currently only wails3 CLI)
├── examples/ # Hands-on sample apps testing every feature
├── internal/ # Framework implementation (not public API)
├── pkg/ # Public Go packages the API surface
├── tasks/ # Taskfile-based release utilities
├── templates/ # RFC-style proposals (WEP) + common assets
├── go.mod
└── go.sum
```
### Mental Model
1. **`pkg/`** exposes *what application developers import*
2. **`internal/`** contains *how the magic is implemented*
3. **`cmd/wails3`** drives *project lifecycle & builds*
4. **`examples/`** double as *living tests* and *reference code*
Everything else supports those three pillars.
---
## `cmd/` Commands
| Path | Notes |
|------|-------|
| `v3/cmd/wails3` | The **CLI entrypoint**. A tiny `main.go` delegates all logic to packages in `internal/commands`. |
| `internal/commands/*` | Sub-commands (init, dev, build, doctor, …). Each lives in its own file for easy discoverability. |
| `internal/commands/task_wrapper.go` | Bridges between CLI flags and the Taskfile build pipeline. |
The CLI owns:
* **Project scaffolding** (`init`, template generation)
* **Dev server orchestration** (`dev`, live-reload)
* **Production builds & packaging** (`build`, `package`, platform wrappers)
* **Diagnostics** (`doctor`)
---
## `internal/` The Engine Room
```
internal/
├── assetserver/ # Serving & embedding web assets
├── buildinfo/ # Reproducible build metadata
├── commands/ # CLI mechanics (see above)
├── runtime/ # Desktop runtime (per-platform)
├── generator/ # Static analysis & binding generator
├── templates/ # Project templates (frontend stacks)
├── packager/ # Linux AppImage/deb/rpm, Windows MSI, macOS DMG
├── capabilities/ # Host OS capability probing
├── dbus/ # Linux system tray integration
├── service/ # IPC micro-services framework
└── ... # [other helper sub-packages]
```
### Key Sub-Packages
| Package | Responsibility | Where It Connects |
|---------|----------------|-------------------|
| `runtime` | Window/event loop, clipboard, dialogs, system tray. One file per OS with build-tags (`*_darwin.go`, `*_linux.go`, …). | Called by `pkg/application` and message processor. |
| `assetserver` | Dual-mode file server:<br />• Dev: serves from disk & proxies Vite<br />• Prod: embeds assets via `go:embed` | Initialized by `pkg/application` during startup. |
| `generator` | Parses Go source to build **binding metadata** which later produces TypeScript stub files and Go marshal/unmarshal code. | Triggered by `wails3 init` / `wails3 generate`. |
| `packager` | Wraps platform-specific packaging CLIs (eg. `electron-builder` equivalents) into Go for cross-platform automation. | Invoked by `wails3 package`. |
Supporting utilities (eg. `s/`, `hash/`, `flags/`) keep internal concerns decoupled.
---
## `pkg/` Public API
```
pkg/
├── application/ # Core API: windows, menus, dialogs, events, …
├── runtime/ # JS-side helpers (bridge, events, screen, ...)
├── options/ # Strongly-typed configuration structs
├── menu/ # Declarative menu DSL
└── ... # Platform helpers (mac/, windows/, assetserver/, …)
```
`pkg/application` bootstraps a Wails program:
```go
func main() {
app := application.New(application.Options{
Name: "MyApp",
Assets: application.NewAssetsFS(assetsFS),
})
window := app.Window.New(&application.WebviewWindowOptions{
Title: "Hello",
Width: 1024,
Height: 768,
})
app.Run()
}
```
Under the hood it:
1. Spins up `internal.runtime.*`
2. Sets up an `assetserver`
3. Registers bindings generated by `internal.generator`
4. Enters the OS main thread
---
## `examples/` Living Specs
Each sub-folder is a **self-contained application** exercising one feature.
Patterns youll see mirrored in real code:
* Binding services (`examples/binding/`)
* Multi-window (`examples/window/`)
* System tray (`examples/systray-*`)
* Packaging (`examples/file-association/`)
When in doubt, start here and drill into implementation.
---
## `templates/` Scaffolding Blueprints
`internal/templates` ships **base templates** (Go layout) and **frontend skins**
(React, Svelte, Vue, …).
At `wails3 init -t react`, the CLI:
1. Copies `_common` Go files
2. Merges desired frontend pack
3. Runs `npm install` + `task deps`
Editing templates **does not** affect existing apps, only future `init`s.
---
## `tasks/` Release Automation
Taskfiles wrap complex cross-compilation, version bumping, and changelog
generation. They are consumed programmatically by `internal/commands/task.go`
so the same logic powers **CLI** and **CI**.
---
## How the Pieces Interact
```d2
direction: down
CLI: "wails3 CLI"
Generator: "internal/generator"
AssetDev: "assetserver (dev)"
Packager: "internal/packager"
AppRuntime: {
label: "App runtime"
ApplicationPkg: "pkg.application"
InternalRuntime: "internal.runtime"
OSAPIs: "OS APIs"
}
CLI -> Generator: "build / generate"
CLI -> AssetDev: "dev"
CLI -> Packager: "package"
Generator -> ApplicationPkg: "bindings"
ApplicationPkg -> InternalRuntime
InternalRuntime -> OSAPIs
ApplicationPkg -> AssetDev
```
*CLI → generator → runtime* forms the core path from **source** to **running
desktop app**.
---
## Orientation Tips
| Need to understand… | Look at… |
|---------------------|----------|
| Platform shims | `internal/runtime/*_DARWIN.go`, `*_windows.go`, … |
| Bridge protocol | `pkg/application/messageprocessor_*.go` |
| Asset workflow | `internal/assetserver`, `v3/templates/base/assets` |
| Packaging flow | `internal/commands/appimage.go`, `internal/packager` |
| Template engine | `internal/templates/templates.go` |
| Static analysis | `internal/generator/*` |
---
You now have a **mental map** of the repository.
Use it with `ripgrep`, your IDEs “Go to File/Symbol”, and the example apps to
navigate deeper into any feature. Happy hacking!

View file

@ -0,0 +1,246 @@
---
title: Extending Wails
description: Practical guide to adding new features and platforms to Wails v3
sidebar:
order: 8
---
> Wails is designed to be **hackable**.
> Every major subsystem lives in Go code you can read, modify, and ship.
> This page shows _where_ to start and _how_ to stay cross-platform when you:
* Add a **Service** (file server, KV store, custom IPC…)
* Create a **new CLI command** (`wails3 <foo>`)
* Extend the **Runtime** (window API, dialogs, events)
* Introduce a **Platform capability** (Wayland, Apple Vision OS…)
* Keep **cross-platform compatibility** without drowning in `//go:build` tags
---
## 1. Adding a Service
Services are background Go components exposed to apps via the `application.Context`.
```
internal/service/
├── template/ # Boilerplate for new services
│ ├── template.go
│ └── README.md
└── service.go # Registration + lifecycle interfaces
```
### 1.1 Define the Service
```go
package chat
type Service struct {
messages []string
}
func New() *Service { return &Service{} }
func (s *Service) Send(msg string) string {
s.messages = append(s.messages, msg)
return "ok"
}
```
### 1.2 Implement `service.Service`
```go
func (s *Service) Init(ctx *application.Context) error { return nil }
func (s *Service) Shutdown() error { return nil }
```
### 1.3 Register Generator
*Add to* `internal/generator/collect/services.go`
```go
services.Register("chat", chat.New)
```
> Now `wails3 generate` emits bindings so JS can call
> `window.backend.chat.Send("hi")`.
### 1.4 Ship an Example
Copy `v3/examples/services/` and adjust.
---
## 2. Writing a New CLI Command
CLI logic lives under `internal/commands/`.
Each command is **one file** that mounts itself in `init()`.
### 2.1 Create the File
`v3/internal/commands/hello.go`
```go
package commands
import (
"github.com/spf13/cobra"
)
func init() {
Add(&cobra.Command{
Use: "hello",
Short: "Prints Hello World",
RunE: func(cmd *cobra.Command, args []string) error {
cmd.Println("Hello Wails!")
return nil
},
})
}
```
`Add()` registers with the root in `cmd/wails3/main.go`.
### 2.2 Re-compile CLI
```
go install ./v3/cmd/wails3
wails3 hello
```
If your command needs **Taskfile** integration, reuse helpers in
`internal/commands/task_wrapper.go`.
---
## 3. Modifying the Runtime
Common reasons:
* New window feature (`SetOpacity`, `Shake`, …)
* Extra dialog (`ColorPicker`)
* System-level API (screen brightness)
### 3.1 Public API
Add to `pkg/application/window.go`:
```go
func (w *WebviewWindow) SetOpacity(o float32) {
w.ctx.Call("window:setOpacity", o)
}
```
### 3.2 Message Processor
Create `messageprocessor_window_opacity.go`:
```go
const MsgSetOpacity = "window:setOpacity"
func init() { register(MsgSetOpacity, handleSetOpacity) }
func handleSetOpacity(ctx *Context, params json.RawMessage) ([]byte, error) {
var req struct {
ID uint64 `json:"id"`
Opacity float32 `json:"o"`
}
json.Unmarshal(params, &req)
return nil, runtime.SetOpacity(req.ID, req.Opacity)
}
```
### 3.3 Platform Implementation
```
internal/runtime/
webview_window_darwin.go
webview_window_linux.go
webview_window_windows.go
```
Add `SetOpacity()` to each, guarded by build tags.
If a platform cant support it, return `ErrCapability`.
### 3.4 Capability Flag
Expose via `internal/capabilities`.
```go
const CapOpacity = "window:opacity"
```
Runtime files `*_darwin.go` etc. should `registerCapability(CapOpacity)` when
successfully initialised.
Apps can query:
```go
if application.HasCapability(application.CapOpacity) { ... }
```
---
## 4. Adding New Platform Capabilities
Example: **Wayland** clipboard on Linux.
1. Fork `internal/runtime/clipboard_linux.go`.
2. Split file:
* `clipboard_linux_x11.go` → `//go:build linux && !wayland`
* `clipboard_linux_wayland.go` → `//go:build linux && wayland`
3. Add CLI flag `--tags wayland` in `internal/commands/dev.go`
(`goEnv.BuildTags += ",wayland"`).
4. Document in **Asset Server** & README.
> Keep default build tags minimal; reserve opt-in tags for niche features.
---
## 5. Cross-Platform Compatibility Checklist
| ✅ Step | Why |
|---------|-----|
| Provide **every** public method in all platform files (even if stub) | Keeps build green on all OS/arch |
| Gate platform specifics behind **capabilities** | Let apps degrade gracefully |
| Use **pure Go** first, CGO only when needed | Simplifies cross-compiles |
| Test with `task matrix:test` | Runs `go test ./...` on linux/mac/windows in Docker |
| Document new build tags in `docs/getting-started/installation.mdx` | Users must know flags |
---
## 6. Debug Builds & Iteration Speed
* `wails3 build -tags debug` adds extra log hooks (`logger_dev*.go`).
* Use `wails3 task reload` to rebuild runtime without restarting the whole app.
* Race detector: add `-race` to the `go build` command in your Taskfile (see `pkg/application/RACE.md`).
---
## 7. Upstream Contributions
1. Open an **issue** to discuss the idea & design.
2. Follow the techniques above to implement.
3. Add:
* Unit tests (`*_test.go`)
* Example (`v3/examples/<feat>/`)
* Docs (this file or API reference)
4. Ensure `go vet`, `golangci-lint`, and CI matrix pass.
---
### Quick Links
| Area | Location |
|------|----------|
| Services framework | `internal/service/` |
| CLI core | `internal/commands/` |
| Runtime per-OS | `internal/runtime/` |
| Capability helpers| `internal/capabilities/` |
| Taskfile DSL | `tasks/Taskfile.yml` |
| Dev matrix tests | `tasks/events/generate.go` |
---
You now have a **roadmap** for bending Wails to your will—add services, sprinkle
CLI magic, hack the runtime, or bring entirely new OS features.
Happy extending!

View file

@ -0,0 +1,369 @@
---
title: Getting Started
description: How to start contributing to Wails v3
---
import { Steps, Tabs, TabItem } from '@astrojs/starlight/components';
## Welcome, Contributor!
Thank you for your interest in contributing to Wails! This guide will help you make your first contribution.
## Prerequisites
Before you begin, ensure you have:
- **Go 1.25+** installed ([download](https://go.dev/dl/))
- **Node.js 20+** and **npm** ([download](https://nodejs.org/))
- **Git** configured with your GitHub account
- Basic familiarity with Go and JavaScript/TypeScript
### Platform-Specific Requirements
**macOS:**
- Xcode Command Line Tools: `xcode-select --install`
**Windows:**
- MSYS2 or similar Unix-like environment recommended
- WebView2 runtime (usually pre-installed on Windows 11)
**Linux:**
- `gcc`, `pkg-config`, `libgtk-3-dev`, `libwebkit2gtk-4.0-dev`
- Install via: `sudo apt install build-essential pkg-config libgtk-3-dev libwebkit2gtk-4.0-dev` (Debian/Ubuntu)
## Contribution Process Overview
The typical contribution workflow follows these steps:
1. **Fork & Clone** - Create your own copy of the Wails repository
2. **Setup** - Build the Wails CLI and verify your environment
3. **Branch** - Create a feature branch for your changes
4. **Develop** - Make your changes following our coding standards
5. **Test** - Run tests to ensure everything works
6. **Commit** - Commit with clear, conventional commit messages
7. **Submit** - Open a pull request for review
8. **Iterate** - Respond to feedback and make adjustments
9. **Merge** - Once approved, your changes become part of Wails!
## Step-by-Step Guide
Choose your contribution type:
<Tabs>
<TabItem label="Bug Fix">
<Steps>
1. **Find or Report the Bug**
- Check if the bug is already reported in [GitHub Issues](https://github.com/wailsapp/wails/issues)
- If not, create a new issue with steps to reproduce
- Wait for confirmation before starting work
2. **Fork and Clone**
Fork the repository at [github.com/wailsapp/wails/fork](https://github.com/wailsapp/wails/fork)
Clone your fork:
```bash
git clone https://github.com/YOUR_USERNAME/wails.git
cd wails
git remote add upstream https://github.com/wailsapp/wails.git
```
3. **Build and Verify**
Build Wails and verify you can reproduce the bug:
```bash
cd v3
go build -o ../wails3 ./cmd/wails3
# Reproduce the bug to understand it
```
4. **Create a Bug Fix Branch**
Create a branch for your fix:
```bash
git checkout -b fix/issue-123-window-crash
```
5. **Fix the Bug**
- Make the minimal changes needed to fix the bug
- Don't refactor unrelated code
- Add or update tests to prevent regression
```bash
# Make your changes
# Add tests in *_test.go files
```
6. **Test Your Fix**
Run tests to ensure the fix works:
```bash
go test ./...
# Test the specific package
go test ./pkg/application -v
# Run with race detector
go test ./... -race
```
7. **Commit Your Fix**
Commit with a clear message:
```bash
git commit -m "fix: prevent window crash when closing during initialization
Fixes #123"
```
8. **Submit Pull Request**
Push and create PR:
```bash
git push origin fix/issue-123-window-crash
```
In your PR description:
- Explain the bug and root cause
- Describe your fix
- Reference the issue: "Fixes #123"
- Include before/after behavior
9. **Respond to Feedback**
Address review comments and update your PR as needed.
</Steps>
</TabItem>
<TabItem label="Enhancements">
<Steps>
1. **Discuss the Feature**
- Open a [GitHub Discussion](https://github.com/wailsapp/wails/discussions) or issue
- Describe what you want to add and why
- Wait for maintainer feedback before implementing
- Ensure it aligns with Wails' goals
2. **Fork and Clone**
Fork the repository at [github.com/wailsapp/wails/fork](https://github.com/wailsapp/wails/fork)
Clone your fork:
```bash
git clone https://github.com/YOUR_USERNAME/wails.git
cd wails
git remote add upstream https://github.com/wailsapp/wails.git
```
3. **Setup Development Environment**
Build Wails and verify your environment:
```bash
cd v3
go build -o ../wails3 ./cmd/wails3
# Run tests to ensure everything works
go test ./...
```
4. **Create a Feature Branch**
Create a descriptive branch:
```bash
git checkout -b feat/window-transparency-support
```
5. **Implement the Feature**
- Follow our [Coding Standards](/contributing/standards)
- Keep changes focused on the feature
- Write clean, documented code
- Add comprehensive tests
```bash
# Example: Adding a new window method
# 1. Add to window.go interface
# 2. Implement in platform files (darwin, windows, linux)
# 3. Add tests
# 4. Update documentation
```
6. **Test Thoroughly**
Test your feature:
```bash
# Unit tests
go test ./pkg/application -v
# Integration test - create a test app
cd ..
./wails3 init -n feature-test
cd feature-test
# Add code using your new feature
../wails3 dev
```
7. **Document Your Feature**
- Add docstrings to all public APIs
- Update relevant documentation in `/docs`
- Add examples if applicable
8. **Commit with Convention**
Use conventional commits:
```bash
git commit -m "feat: add window transparency support
- Add SetTransparent() method to Window API
- Implement for macOS, Windows, and Linux
- Add tests and documentation
Closes #456"
```
9. **Submit Pull Request**
Push and create PR:
```bash
git push origin feat/window-transparency-support
```
In your PR:
- Describe the feature and use cases
- Show examples or screenshots
- List any breaking changes
- Reference the discussion/issue
10. **Iterate Based on Review**
Maintainers may request changes. Be patient and collaborative.
</Steps>
</TabItem>
<TabItem label="Documentation">
<Steps>
1. **Identify Documentation Needs**
- Found outdated docs while using Wails?
- Notice missing examples or explanations?
- Want to fix typos or improve clarity?
- Check [documentation issues](https://github.com/wailsapp/wails/labels/documentation)
2. **Fork and Clone**
Fork the repository at [github.com/wailsapp/wails/fork](https://github.com/wailsapp/wails/fork)
Clone your fork:
```bash
git clone https://github.com/YOUR_USERNAME/wails.git
cd wails
git remote add upstream https://github.com/wailsapp/wails.git
```
3. **Setup Documentation Environment**
The docs are in `/docs` and built with Astro:
```bash
cd docs
npm install
npm run dev
```
Open http://localhost:4321/ to preview changes live.
4. **Create a Documentation Branch**
Create a branch for your changes:
```bash
git checkout -b docs/improve-window-api-examples
```
5. **Make Your Changes**
Documentation files are in `/docs/src/content/docs/`:
```bash
# Edit MDX files
# Check the preview in your browser
# Ensure formatting is correct
```
**Best Practices:**
- Use clear, concise language
- Include practical code examples
- Add links to related sections
- Check spelling and grammar
- Test all code examples
6. **Verify Your Changes**
Check the live preview and ensure:
- Links work correctly
- Code examples are accurate
- Formatting renders properly
- No broken images or references
7. **Commit Documentation Changes**
Commit with clear message:
```bash
git commit -m "docs: add practical examples to Window API guide
- Add window positioning examples
- Include common patterns section
- Fix broken links to Event API"
```
8. **Submit Pull Request**
Push and create PR:
```bash
git push origin docs/improve-window-api-examples
```
In your PR:
- Describe what docs you improved
- Explain why the change helps users
- Include screenshots if visual changes
9. **Address Review Feedback**
Documentation PRs are usually quick to review and merge!
</Steps>
</TabItem>
</Tabs>
## Finding Issues to Work On
- Look for [`good first issue`](https://github.com/wailsapp/wails/labels/good%20first%20issue) labels
- Check [`help wanted`](https://github.com/wailsapp/wails/labels/help%20wanted) issues
- Browse [open issues](https://github.com/wailsapp/wails/issues) and ask to be assigned
## Getting Help
- **Discord:** Join [Wails Discord](https://discord.gg/JDdSxwjhGf)
- **Discussions:** Post in [GitHub Discussions](https://github.com/wailsapp/wails/discussions)
- **Issues:** Open an issue if you find a bug or have a question
## Code of Conduct
Be respectful, constructive, and welcoming. We're building a friendly community focused on creating great software together.
## Next Steps
- Set up your [Development Environment](/contributing/setup)
- Review our [Coding Standards](/contributing/standards)
- Explore the [Technical Documentation](/contributing)

View file

@ -0,0 +1,127 @@
---
title: Technical Overview
description: High-level architecture and roadmap to the Wails v3 codebase
sidebar:
order: 1
---
import { Card, CardGrid } from "@astrojs/starlight/components";
## Welcome to the Wails v3 Technical Documentation
This section is **not** about community guidelines or how to open a pull-request.
Instead, it dives into **how Wails v3 is built** so that you can quickly orient
yourself in the codebase and start hacking with confidence.
Whether you plan to patch the runtime, extend the CLI, craft new templates, or
simply understand the internals, the pages that follow provide the technical
context you need.
---
## High-Level Architecture
<CardGrid>
<Card title="Go Backend" icon="seti:go">
The heart of every Wails app is Go code compiled into a native executable.
It owns application logic, system integration and performance-critical
operations.
</Card>
<Card title="Web Frontend" icon="seti:html">
UI is written with standard web tech (React, Vue, Svelte, Vanilla, …)
rendered by a lightweight system WebView (WebKit on Linux/macOS, WebView2 on
Windows).
</Card>
<Card title="Bridging Layer" icon="puzzle">
A zero-copy, in-memory bridge enables **Go⇄JavaScript** calls with automatic
type conversion, event propagation and error forwarding.
</Card>
<Card title="CLI & Tooling" icon="terminal">
`wails3` orchestrates project creation, live-reload dev server, asset
bundling, cross-compilation and packaging (deb, rpm, AppImage, msi, dmg…).
</Card>
</CardGrid>
---
## Architectural Overview
**Wails v3 End-to-End Flow**
```d2
direction: right
Developer: "wails3 CLI
Init · Dev · Build · Package"
Build: {
label: "Build-Time"
GEN: "Binding System
(Static Analysis & Codegen)"
ASSET: "Asset Server
(Dev Proxy · Embed FS)"
PKG: "Build & Packaging
Pipeline"
}
Runtime: {
label: "Runtime"
RUNTIME: "Desktop Runtime
(Window · Events · Dialogs)"
BRIDGE: "Bridge
(Message Processor)"
}
Application: {
label: "Application"
GO: "Go Backend
(App Logic)"
WEB: "Web Frontend
(React/Vue/...)"
}
Developer -> GEN: "generate"
Developer -> ASSET: "dev / build"
Developer -> PKG: "compile & package"
GEN -> GO: "Code Stubs + TS"
GEN -> WEB: "Bindings JSON"
PKG -> GO: "Final Binary + Installer"
GO -> BRIDGE: "Function Calls"
WEB -> BRIDGE: "Invoke / Events"
BRIDGE <-> RUNTIME: "native messages"
RUNTIME -> ASSET: "Serve Assets"
WEB <-> ASSET: "HTTP / In-Memory"
```
The diagram shows the **end-to-end flow**:
1. **CLI** drives generation, dev server, compilation, and packaging.
2. **Binding System** produces glue code that lets the **Web Frontend** call into the **Go Backend**.
3. During development the **Asset Server** proxies to the framework dev server; in production it serves embedded files.
4. At runtime the **Desktop Runtime** manages windows and OS APIs, while the **Bridge** shuttles messages between Go and JavaScript.
---
## What This Documentation Covers
| Topic | Why It Matters |
| ----- | -------------- |
| **Codebase Layout** | Map of `/v3` directories and how modules interact. |
| **Runtime Internals** | Window management, system APIs, message processor, and platform shims. |
| **Asset & Dev Server** | How web assets are served in dev and embedded in production. |
| **Build & Packaging Pipeline** | Taskfile-based workflow, cross-platform compilation, and installer generation. |
| **Binding System** | Static analysis pipeline that generates type-safe Go⇄TS bindings. |
| **Template System** | Generator architecture that powers `wails init -t <framework>`. |
| **Testing & CI** | Unit/integration test harness, GitHub Actions, race detector guidance. |
| **Extending Wails** | Adding services, templates, or CLI sub-commands. |
Each subsequent page drills into these areas with concrete code samples,
diagrams, and references to the relevant source files.
---
:::note
Prerequisites: You should be comfortable with **Go 1.25+**, basic TypeScript,
and modern frontend build tools. If you are new to Go, consider skimming the
official tour first.
:::
Happy exploring — and welcome to the Wails v3 internals!

View file

@ -0,0 +1,175 @@
---
title: Runtime Internals
description: Deep-dive into how Wails v3 boots, runs, and talks to the OS
sidebar:
order: 3
---
The **runtime** is the layer that transforms ordinary Go functions into a
cross-platform desktop application.
This document explains the moving parts you will meet when tracing through the
source code.
---
## 1. Application Lifecycle
| Phase | Code Path | What Happens |
|-------|-----------|--------------|
| **Bootstrap** | `pkg/application/application.go:init()` | Registers build-time data, creates a global `application` singleton. |
| **New()** | `application.New(...)` | Validates `Options`, spins up the **AssetServer**, initialises logging. |
| **Run()** | `application.(*App).Run()` | 1. Calls platform `mainthread.X()` to enter the OS UI thread.<br />2. Boots the **runtime** (`internal/runtime`).<br />3. Blocks until the last window closes or `Quit()` is called. |
| **Shutdown** | `application.(*App).Quit()` | Broadcasts `application:shutdown` event, flushes log, tears down windows & services. |
The lifecycle is strictly **single-entry**: you may create many windows, but the
application object itself is initialised once.
---
## 2. Window Management
### Public API
```go
win := app.Window.New(&application.WebviewWindowOptions{
Title: "Dashboard",
Width: 1280,
Height: 720,
})
win.Show()
```
`NewWebviewWindow` delegates to `internal/runtime/webview_window_*.go` where
platform-specific constructors live:
```
internal/runtime/
├── webview_window_darwin.go
├── webview_window_linux.go
└── webview_window_windows.go
```
Each file:
1. Creates a native webview (WKWebView, WebKitGTK, WebView2).
2. Registers a **Message Processor** callback.
3. Maps Wails events (`WindowResized`, `Focus`, `DropFile`, …) to runtime
event IDs.
Windows are tracked by `screenmanager.go` which offers **query APIs** (all
displays, DPI, active window) and centralises resize / move bookkeeping.
---
## 3. Message Processing Pipeline
The bridge between JavaScript and Go is implemented by the **Message
Processor** family in `pkg/application/messageprocessor_*.go`.
Flow:
1. **JavaScript** calls `window.runtime.Invoke("Greet", "Alice")`.
2. The runtime serialises the request:
`{"t":"c","id":"123","m":"Greet","p":["Alice"]}`
(`t` = type, `c` = call).
3. **Go** receives this JSON via the webview callback.
4. `messageprocessor_call.go` looks up the bound method in the
generated table (`application.bindings.go`) and executes it.
5. The result or error is marshalled back to JS where a `Promise` resolves.
Specialised processors:
| File | Purpose |
|------|---------|
| `messageprocessor_window.go` | Window actions (hide, maximize, …) |
| `messageprocessor_dialog.go` | Native dialogs (`OpenFile`, `MessageBox`, …) |
| `messageprocessor_clipboard.go` | Clipboard read/write |
| `messageprocessor_events.go` | Event subscribe / emit |
| `messageprocessor_browser.go` | Browser navigation, devtools |
Processors are **stateless** they pull everything they need from the
`ApplicationContext` passed with each message.
---
## 4. Events System
Events are namespaced strings dispatched across three layers:
1. **Application events**: global lifecycle (`application:ready`,
`application:shutdown`).
2. **Window events**: per-window (`window:focus`, `window:resize`).
3. **Custom events**: user-defined (`chat:new-message`).
Implementation details:
* Constants are generated from `internal/events/defaults.go`.
* Go side:
`app.On("window:focus", func(e application.WindowEvent) {...})`
* JS side:
`window.runtime.EventsOn("chat:new-message", cb)`
Under the hood both map to the same **EventBus** in
`pkg/application/events.go`.
Subscribers are reference-counted; when a window closes, its callbacks are
auto-unregistered to avoid leaks.
---
## 5. Platform-Specific Implementations
Conditional compilation keeps the public API identical while hiding OS wrinkles.
| Concern | Darwin | Linux | Windows |
|---------|--------|-------|---------|
| Main Thread | `mainthread_darwin.go` (Cgo to Foundation) | `mainthread_linux.go` (GTK) | `mainthread_windows.go` (Win32 `AttachThreadInput`) |
| Dialogs | `dialogs_darwin.*` (NSAlert) | `dialogs_linux.go` (GtkFileChooser) | `dialogs_windows.go` (IFileOpenDialog) |
| Clipboard | `clipboard_darwin.go` | `clipboard_linux.go` | `clipboard_windows.go` |
| Tray Icons | `systemtray_darwin.*` | `systemtray_linux.go` (DBus) | `systemtray_windows.go` (Shell_NotifyIcon) |
Key principles:
* **No Cgo on Windows/Linux** unless unavoidable (performance, portability).
* Use **build tags** (`//go:build darwin && !production`) to keep files readable.
* Expose **capabilities** via `internal/capabilities` so higher layers can
degrade gracefully.
---
## 6. File Guide
| File | Why Youd Touch It |
|------|--------------------|
| `internal/runtime/runtime_*.go` | Change global startup logic, add debug hooks. |
| `internal/runtime/webview_window_*.go` | Implement a new window hint or behaviour. |
| `pkg/application/messageprocessor_*.go` | Add a new bridge command callable from JS. |
| `pkg/application/events_*.go` | Extend built-in event definitions. |
| `internal/assetserver/*` | Tweak dev/production asset handling. |
---
## 7. Debugging Tips
* Launch with `WAILS_LOG_LEVEL=debug` to print every message crossing the bridge.
* Use `wails3 dev -verbose` to see live reload & asset requests.
* On macOS run under `lldb --` to catch Objective-C exceptions early.
* For Windows Chromium issues, enable WebView2 debug logs:
`set WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=9222`
---
## 8. Extending the Runtime
1. Define a **capability flag** in `internal/capabilities`.
2. Implement the feature in each platform file using build tags.
3. Add public API in `pkg/application`.
4. Register a new message type or event if JS needs to call it.
5. Update at least one example in `v3/examples/` exercising the feature.
Follow this checklist and you'll keep the cross-platform contract intact.
---
You now have a guided tour of the runtime internals. Combine this knowledge with
the **Codebase Layout** map and the **Asset Server** docs to navigate confidently
and make impactful contributions. Happy coding!

View file

@ -0,0 +1,297 @@
---
title: Development Setup
description: Set up your development environment for Wails v3 development
---
## Development Environment Setup
This guide walks you through setting up a complete development environment for working on Wails v3.
## Required Tools
### Go Development
1. **Install Go 1.25 or later:**
```bash
# Download from https://go.dev/dl/
go version # Verify installation
```
2. **Configure Go environment:**
```bash
# Add to your shell profile (.bashrc, .zshrc, etc.)
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
```
3. **Install useful Go tools:**
```bash
go install golang.org/x/tools/cmd/goimports@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
```
### Node.js and npm
Required for building documentation and testing frontend integrations.
```bash
# Install Node.js 20+ and npm
node --version # Should be 20+
npm --version
```
### Platform-Specific Dependencies
**macOS:**
```bash
# Install Xcode Command Line Tools
xcode-select --install
# Verify installation
xcode-select -p # Should output a path
```
**Windows:**
1. Install [MSYS2](https://www.msys2.org/) for a Unix-like environment
2. WebView2 Runtime (pre-installed on Windows 11, [download](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) for Windows 10)
3. Optional: Install [Git for Windows](https://git-scm.com/download/win)
**Linux (Debian/Ubuntu):**
```bash
sudo apt update
sudo apt install build-essential pkg-config libgtk-3-dev libwebkit2gtk-4.0-dev
```
**Linux (Fedora/RHEL):**
```bash
sudo dnf install gcc pkg-config gtk3-devel webkit2gtk3-devel
```
**Linux (Arch):**
```bash
sudo pacman -S base-devel gtk3 webkit2gtk
```
## Repository Setup
### Clone and Configure
```bash
# Clone your fork
git clone https://github.com/YOUR_USERNAME/wails.git
cd wails
# Add upstream remote
git remote add upstream https://github.com/wailsapp/wails.git
# Verify remotes
git remote -v
```
### Build the Wails CLI
```bash
# Navigate to v3 directory
cd v3
# Build the CLI
go build -o ../wails3 ./cmd/wails3
# Test the build
cd ..
./wails3 version
```
### Add to PATH (Optional)
**Linux/macOS:**
```bash
# Add to ~/.bashrc or ~/.zshrc
export PATH=$PATH:/path/to/wails
```
**Windows:**
Add the Wails directory to your PATH environment variable through System Properties.
## IDE Setup
### VS Code (Recommended)
1. **Install VS Code:** [Download](https://code.visualstudio.com/)
2. **Install extensions:**
- Go (by Go Team at Google)
- ESLint
- Prettier
- MDX (for documentation)
3. **Configure workspace settings** (`.vscode/settings.json`):
```json
{
"go.useLanguageServer": true,
"go.lintTool": "golangci-lint",
"go.lintOnSave": "workspace",
"editor.formatOnSave": true,
"go.formatTool": "goimports"
}
```
### GoLand
1. **Install GoLand:** [Download](https://www.jetbrains.com/go/)
2. **Configure:**
- Enable Go modules support
- Set up file watchers for `goimports`
- Configure code style to match project conventions
## Verify Your Setup
Run these commands to verify everything is working:
```bash
# Go version check
go version
# Build Wails
cd v3
go build ./cmd/wails3
# Run tests
go test ./pkg/...
# Create a test app
cd ..
./wails3 init -n mytest -t vanilla
cd mytest
../wails3 dev
```
If the test app builds and runs, your environment is ready!
## Running Tests
### Unit Tests
```bash
cd v3
go test ./...
```
### Specific Package Tests
```bash
go test ./pkg/application
go test ./pkg/events -v # Verbose output
```
### Run with Coverage
```bash
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out
```
### Run with Race Detector
```bash
go test ./... -race
```
## Working with Documentation
The Wails documentation is built with Astro and Starlight.
```bash
cd docs
# Install dependencies
npm install
# Start dev server
npm run dev
# Build for production
npm run build
```
Documentation will be available at `http://localhost:4321/`
## Debugging
### Debugging Go Code
**VS Code:**
Create `.vscode/launch.json`:
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Wails CLI",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/v3/cmd/wails3",
"args": ["dev"]
}
]
}
```
**Command Line:**
```bash
# Use Delve debugger
go install github.com/go-delve/delve/cmd/dlv@latest
dlv debug ./cmd/wails3 -- dev
```
### Debugging Platform Code
Platform-specific debugging requires platform tools:
- **macOS:** Xcode Instruments
- **Windows:** Visual Studio Debugger
- **Linux:** GDB
## Common Issues
### "command not found: wails3"
Add the Wails directory to your PATH or use `./wails3` from the project root.
### "webkit2gtk not found" (Linux)
Install WebKit2GTK development packages:
```bash
sudo apt install libwebkit2gtk-4.0-dev # Debian/Ubuntu
```
### Build fails with Go module errors
```bash
cd v3
go mod tidy
go mod download
```
### "CGO_ENABLED" errors on Windows
Ensure you have a C compiler (MinGW-w64 via MSYS2) in your PATH.
## Next Steps
- Review [Coding Standards](/contributing/standards)
- Explore the [Technical Documentation](/contributing)
- Find an issue to work on: [Good First Issues](https://github.com/wailsapp/wails/labels/good%20first%20issue)

View file

@ -0,0 +1,465 @@
---
title: Coding Standards
description: Code style, conventions, and best practices for Wails v3
---
## Code Style and Conventions
Following consistent coding standards makes the codebase easier to read, maintain, and contribute to.
## Go Code Standards
### Code Formatting
Use standard Go formatting tools:
```bash
# Format all code
gofmt -w .
# Use goimports for import organization
goimports -w .
```
**Required:** All Go code must pass `gofmt` and `goimports` before committing.
### Naming Conventions
**Packages:**
- Lowercase, single word when possible
- `package application`, `package events`
- Avoid underscores or mixed caps
**Exported Names:**
- PascalCase for types, functions, constants
- `type WebviewWindow struct`, `func NewApplication()`
**Unexported Names:**
- camelCase for internal types, functions, variables
- `type windowImpl struct`, `func createWindow()`
**Interfaces:**
- Name by behavior: `Reader`, `Writer`, `Handler`
- Single-method interfaces: name with `-er` suffix
```go
// Good
type Closer interface {
Close() error
}
// Avoid
type CloseInterface interface {
Close() error
}
```
### Error Handling
**Always check errors:**
```go
// Good
result, err := doSomething()
if err != nil {
return fmt.Errorf("failed to do something: %w", err)
}
// Bad - ignoring errors
result, _ := doSomething()
```
**Use error wrapping:**
```go
// Wrap errors to provide context
if err := validate(); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
```
**Create custom error types when needed:**
```go
type ValidationError struct {
Field string
Value string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("invalid value %q for field %q", e.Value, e.Field)
}
```
### Comments and Documentation
**Package comments:**
```go
// Package application provides the core Wails application runtime.
//
// It handles window management, event dispatching, and service lifecycle.
package application
```
**Exported declarations:**
```go
// NewApplication creates a new Wails application with the given options.
//
// The application must be started with Run() or RunWithContext().
func NewApplication(opts Options) *Application {
// ...
}
```
**Implementation comments:**
```go
// processEvent handles incoming events from the runtime.
// It dispatches to registered handlers and manages event lifecycle.
func (a *Application) processEvent(event *Event) {
// Validate event before processing
if event == nil {
return
}
// Find and invoke handlers
// ...
}
```
### Function and Method Structure
**Keep functions focused:**
```go
// Good - single responsibility
func (w *Window) setTitle(title string) {
w.title = title
w.updateNativeTitle()
}
// Bad - doing too much
func (w *Window) updateEverything() {
w.setTitle(w.title)
w.setSize(w.width, w.height)
w.setPosition(w.x, w.y)
// ... 20 more operations
}
```
**Use early returns:**
```go
// Good
func validate(input string) error {
if input == "" {
return errors.New("empty input")
}
if len(input) > 100 {
return errors.New("input too long")
}
return nil
}
// Avoid deep nesting
```
### Concurrency
**Use context for cancellation:**
```go
func (a *Application) RunWithContext(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
case <-a.done:
return nil
}
}
```
**Protect shared state with mutexes:**
```go
type SafeCounter struct {
mu sync.Mutex
count int
}
func (c *SafeCounter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
```
**Avoid goroutine leaks:**
```go
// Good - goroutine has exit condition
func (a *Application) startWorker(ctx context.Context) {
go func() {
for {
select {
case <-ctx.Done():
return // Clean exit
case work := <-a.workChan:
a.process(work)
}
}
}()
}
```
### Testing
**Test file naming:**
```go
// Implementation: window.go
// Tests: window_test.go
```
**Table-driven tests:**
```go
func TestValidate(t *testing.T) {
tests := []struct {
name string
input string
wantErr bool
}{
{"empty input", "", true},
{"valid input", "hello", false},
{"too long", strings.Repeat("a", 101), true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validate(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
```
## JavaScript/TypeScript Standards
### Code Formatting
Use Prettier for consistent formatting:
```json
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5"
}
```
### Naming Conventions
**Variables and functions:**
- camelCase: `const userName = "John"`
**Classes and types:**
- PascalCase: `class WindowManager`
**Constants:**
- UPPER_SNAKE_CASE: `const MAX_RETRIES = 3`
### TypeScript
**Use explicit types:**
```typescript
// Good
function greet(name: string): string {
return `Hello, ${name}`
}
// Avoid implicit any
function process(data) { // Bad
return data
}
```
**Define interfaces:**
```typescript
interface WindowOptions {
title: string
width: number
height: number
}
function createWindow(options: WindowOptions): void {
// ...
}
```
## Commit Message Format
Use [Conventional Commits](https://www.conventionalcommits.org/):
```
<type>(<scope>): <subject>
<body>
<footer>
```
**Types:**
- `feat`: New feature
- `fix`: Bug fix
- `docs`: Documentation changes
- `refactor`: Code refactoring
- `test`: Adding or updating tests
- `chore`: Maintenance tasks
**Examples:**
```
feat(window): add SetAlwaysOnTop method
Implement SetAlwaysOnTop for keeping windows above others.
Adds platform implementations for macOS, Windows, and Linux.
Closes #123
```
```
fix(events): prevent event handler memory leak
Event listeners were not being properly cleaned up when
windows were closed. This adds explicit cleanup in the
window destructor.
```
## Pull Request Guidelines
### Before Submitting
- [ ] Code passes `gofmt` and `goimports`
- [ ] All tests pass (`go test ./...`)
- [ ] New code has tests
- [ ] Documentation updated if needed
- [ ] Commit messages follow conventions
- [ ] No merge conflicts with `master`
### PR Description Template
```markdown
## Description
Brief description of what this PR does.
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Testing
How was this tested?
## Checklist
- [ ] Tests pass
- [ ] Documentation updated
- [ ] No breaking changes (or documented)
```
## Code Review Process
### As a Reviewer
- Be constructive and respectful
- Focus on code quality, not personal preferences
- Explain why changes are suggested
- Approve once satisfied
### As an Author
- Respond to all comments
- Ask for clarification if needed
- Make requested changes or explain why not
- Be open to feedback
## Best Practices
### Performance
- Avoid premature optimization
- Profile before optimizing
- Use benchmarks for performance-critical code
```go
func BenchmarkProcess(b *testing.B) {
for i := 0; i < b.N; i++ {
process(testData)
}
}
```
### Security
- Validate all user input
- Sanitize data before display
- Use `crypto/rand` for random data
- Never log sensitive information
### Documentation
- Document exported APIs
- Include examples in documentation
- Update docs when changing APIs
- Keep README files current
## Platform-Specific Code
### File Naming
```
window.go // Common interface
window_darwin.go // macOS implementation
window_windows.go // Windows implementation
window_linux.go // Linux implementation
```
### Build Tags
```go
//go:build darwin
package application
// macOS-specific code
```
## Linting
Run linters before committing:
```bash
# golangci-lint (recommended)
golangci-lint run
# Individual linters
go vet ./...
staticcheck ./...
```
## Questions?
If you're unsure about any standards:
- Check existing code for examples
- Ask in [Discord](https://discord.gg/JDdSxwjhGf)
- Open a discussion on GitHub

View file

@ -0,0 +1,222 @@
---
title: Template System
description: How Wails v3 scaffolds new projects, how templates are organised, and how to build your own.
sidebar:
order: 7
---
Wails ships with a **template system** that lets `wails3 init` produce a ready-to-run
project for **any** modern frontend stack (React, Svelte, Vue, Solid, Vanilla
JS …).
This page covers:
1. Template directory layout
2. How the CLI chooses & renders templates
3. Creating a new template step-by-step
4. Updating or overriding existing templates
5. Troubleshooting and best practices
---
## 1. Where Templates Live
```
v3/
└── internal/templates/
├── _common/ # Files copied into EVERY project (main.go, .gitignore)
├── base/ # Backend-only “plain Go” template
├── react/ # React + Vite
├── react-ts/
├── svelte/
├── vue/
├── ... (others)
└── templates.go # Registry + helper functions
```
* **`_common/`** universal boilerplate (Go modules, Taskfile, README stub,
VS Code settings).
* **Framework folders** contain **frontend/** and any stack-specific config.
* Folder names match the **template ID** you pass to the CLI
(`wails3 init -t react-ts`).
> The whole directory is compiled into the CLI binary via `go:embed`, so users
> can scaffold projects offline.
---
## 2. How `wails3 init` Uses Templates
Call chain:
```
cmd/wails3/init.go
internal/commands/init.go ← parses flags, target dir
internal/templates.Load() ← templates.go
Template.CopyTo(dest) ← filesystem copy + text substitutions
```
### Substitutions
Placeholders wrapped in `{{ }}` are replaced at copy time:
| Placeholder | Example | Source |
|-------------|---------|--------|
| `{{ProjectName}}` | `myapp` | CLI flag/dir name |
| `{{ModulePath}}` | `github.com/me/myapp` | if `--module` provided |
| `{{WailsVersion}}`| `v3.0.0` | compiled constant |
You can add **any** placeholder; just ensure it gets a value in
`internal/commands/init.go`.
### Post-Copy Hooks
Each template may ship a **Taskfile** with a `deps` task.
After copy, the CLI runs:
```
task --taskfile Taskfile.yml deps
```
Typical work:
* `go mod tidy`
* `npm install`
* git init & initial commit (optional)
Hook behaviour defined in `_common/Taskfile.yml` but override-able per template.
---
## 3. Creating a New Template
> Example: Add **Qwik-TS** template
### 3.1 Folder & ID
```
internal/templates/qwik-ts/
```
The folder name = template ID. Keep it **kebab-case**.
### 3.2 Minimal File Set
```
qwik-ts/
├── README.md # Shown in CLI with -list
├── frontend/ # Your web project
│ ├── src/
│ ├── package.json
│ └── vite.config.ts
├── Taskfile.yml # deps & dev tasks
└── go/ # Optional extra Go files
```
Start by copying `react-ts` and pruning files.
### 3.3 Update Placeholders
Search/replace:
* `myapp` → `{{ProjectName}}`
* `github.com/you/myapp` → `{{ModulePath}}`
### 3.4 Register (optional)
If the folder exists, **templates.go auto-detects it** via `embed`.
Only add explicit code if you need **custom validation**:
```go
func (t *Template) Validate() error {
// check Node ≥ 20 for this template
}
```
### 3.5 Test
```bash
wails3 init -n demo -t qwik-ts
cd demo
wails3 dev
```
Ensure:
* Dev server starts
* `wailsjs` bindings generate
* Hot-reload works
---
## 4. Modifying Existing Templates
1. Edit files under `internal/templates/<id>/`.
2. Run `go generate ./v3/...` **or** just `go run ./v3/cmd/wails3 help`; the
embed directive recompiles automatically.
3. Bump any **dependency versions** in `frontend/package.json` & `Taskfile.yml`.
4. Update `README.md` inside the template — users see it via
`wails3 init -t react --help`.
### Common Tweaks
| Task | Where |
|------|-------|
| Change dev server port | `frontend/Taskfile.yml` (`vite dev --port {{DevPort}}`) |
| Add environment variables | same Taskfile or `.env` |
| Replace JS package manager | Switch `npm` → `pnpm` in Taskfile |
---
## 5. Template Authoring Tips
* **Keep frontend generic** avoid referencing Wails-specific globals; runtime
is injected automatically.
* **No compiled artefacts** exclude `node_modules`, `dist`, `.DS_Store` via
`.tmplignore`.
* **Document prerequisites** Node version, extra CLI tools, etc.
* **Avoid breaking changes** create a *new* template ID if overhaul is big.
---
## 6. Troubleshooting
| Symptom | Cause | Fix |
|---------|-------|-----|
| `unknown template id` | Folder name mismatch | Check `wails3 init -list` |
| Placeholders not replaced | Missing entry in `Data` map | Edit `internal/commands/init.go` |
| Dev server opens blank page | Wrong `DevPort` env | Ensure Taskfile echoes port to stdout |
| Frontend fails to build in prod | Forgotten vite `base` path | Set `base: "./"` in `vite.config.ts` |
Add `--verbose` to `wails3 init` to print every copied file and substitution.
---
## 7. Key Source File Map
| File | Responsibility |
|------|----------------|
| `internal/templates/templates.go` | Embeds template FS, returns `[]Template` |
| `internal/templates/<id>/**` | Actual template content |
| `internal/commands/init.go` | CLI glue: picks template, fills placeholders |
| `internal/commands/generate_template.go` | Utility to *export* a live project back into a template (handy for updates) |
---
## 8. Recap
* Templates live in **`internal/templates/`** and are baked into the CLI.
* `wails3 init -t <id>` copies `_common` + selected template, performs
substitutions, then runs a **deps** Taskfile hook.
* Creating a template is as simple as **making a folder**, adding files, and
using placeholders.
* The system is **extensible** and **self-contained** — perfect for sharing
custom stacks with your team or the community.
Happy templating!

View file

@ -0,0 +1,216 @@
---
title: Testing & Continuous Integration
description: How Wails v3 ensures quality with unit tests, integration suites, race detection, and GitHub Actions CI.
sidebar:
order: 9
---
Robust desktop frameworks demand rock-solid testing.
Wails v3 employs a **layered strategy**:
| Layer | Goal | Tooling |
|-------|------|---------|
| Unit tests | Fast feedback for isolated functions | `go test ./...` |
| Integration / Example tests | Validate whole features across packages | Example apps + headless assertions |
| Race detection | Catch data races in the runtime & bridge | `go test -race`, `wails3 dev -race` |
| CI matrix | Cross-OS confidence on every PR | GitHub Actions |
This document explains **where tests live**, **how to run them**, and **what happens in CI**.
---
## 1. Directory Conventions
```
v3/
├── internal/.../_test.go # Unit tests for internal packages
├── pkg/.../_test.go # Public API tests
├── examples/... # Example apps double as integration tests
├── tasks/events/... # Generated code tests run in CI
└── pkg/application/RACE.md # Race test guidance & known safe suppressions
```
Guidelines:
* **Keep unit tests next to code** (`foo.go` ↔ `foo_test.go`)
* Use the **black-box style** for `pkg/` packages (`package application_test`)
* Integration helpers go in `internal/testutil/` (shared mocks, fixtures)
---
## 2. Unit Tests
### Writing Tests
```go
func TestColourParseHex(t *testing.T) {
c, err := colour.Parse("#FF00FF")
require.NoError(t, err)
assert.Equal(t, 255, c.R)
}
```
Recommendations:
* Use [`stretchr/testify`](https://github.com/stretchr/testify) already vendor-imported.
* Follow **table-driven** style for branches.
* Stub platform quirks behind build tags (`_test_windows.go` etc.) when needed.
### Running Locally
```bash
cd v3
go test ./... -cover
```
Taskfile shortcut:
```
task test # same as above + vet + lint
```
---
## 3. Integration & Example Tests
Every folder under `v3/examples/` is a **self-contained app**.
Three techniques turn them into tests:
| Technique | File | What it checks |
|-----------|------|----------------|
| `go test ./...` inside example | `*_test.go` spins up `wails dev` in headless mode | Build & startup |
| CLI golden tests | `internal/commands/appimage_test.go` | Generated artefact hashes |
| Scripted e2e | `tasks/events/generate.go` | Emits Go that launches example, asserts runtime logs |
Run all with:
```
task integration
```
> Headless mode uses **Xvfb** on Linux runners; macOS & Windows start minimized.
---
## 4. Race Detection
Data races are fatal in GUI runtimes.
### Dedicated Doc
See `v3/pkg/application/RACE.md` for:
* Known benign races and suppression rationale
* How to interpret stack-traces crossing Cgo boundaries
### Local Race Suite
```
go test -race ./...
```
or
```
wails3 dev -race
```
*The latter rebuilds runtime with race flags and launches a demo window.
Close the window; any races print after exit.*
CI runs the race suite on **linux/amd64** (fastest) for every PR.
---
## 5. GitHub Actions Workflows
### 5.1 `build-and-test-v3.yml`
Location: `.github/workflows/build-and-test-v3.yml`
Key features:
* **Matrix** over `os: [ubuntu-latest, windows-latest, macos-latest]`
* Caches Go & npm deps for speed
* Steps:
1. `setup-go` @ 1.25
2. `go vet ./...`
3. `go test ./... -v -coverprofile=cover.out`
4. Build CLI + sample app (`wails3 build`)
5. Upload coverage to Codecov
* Conditional **race** job on Ubuntu:
```yaml
- name: Race Detector
if: matrix.os == 'ubuntu-latest'
run: go test -race ./...
```
### 5.2 Static Analysis
* `semgrep.yml` security/lint scanning (`.github/workflows/semgrep.yml`)
* `qodana.yaml` JetBrains Qodana cloud inspection (optional in PR)
### 5.3 Release Pipeline
`runtime.yml` builds and publishes the JS runtime to npm on tag push; out of testing scope but referenced by build matrix.
---
## 6. Local CI Parity
Run the **exact** CI task set via Taskfile:
```
task ci
```
It executes:
1. `lint` (golangci-lint, govet)
2. `test` (unit + race)
3. `integration`
4. `build` (for host platform)
---
## 7. Troubleshooting Failing Tests
| Symptom | Likely Cause | Fix |
|---------|--------------|-----|
| **Race in `runtime_darwin.go`** | Forgot to lock `mainthread.Call` | Use `mainthread.Sync` helper |
| **Windows headless hangs** | Console window waiting for input | Pass `-verbose` and ensure no dialogs open |
| **CI example build fails** | Template changed without bumping example deps | `task update-examples` regenerates lockfiles |
| **Coverpkg errors** | Integration test importing `main` | Switch to build tags `//go:build integration` |
---
## 8. Adding New Tests
1. **Unit** create `*_test.go`, run `go test ./...`
2. **Integration** update or add example app + test harness
3. **CI** commit; the workflow auto-discovers tests
If OS-specific, add skip tags:
```go
//go:build !windows
```
---
## 9. Key File Map
| What | Path |
|------|------|
| Unit test example | `internal/colour/colour_test.go` |
| Integration harness | `internal/commands/appimage_test.go` |
| Race guide | `pkg/application/RACE.md` |
| Taskfile test targets | `v3/Taskfile.yaml` |
| CI workflow | `.github/workflows/build-and-test-v3.yml` |
---
Quality isnt an afterthought in Wails v3.
With unit tests, integration suites, race detection, and a cross-platform CI
matrix you can contribute confidently, knowing your changes run green on every
OS we support.
Happy testing!

View file

@ -0,0 +1,62 @@
---
title: Credits
---
{/* Import the auto-generated contributors file */}
import Contributors from "../../assets/contributors.html";
- [Lea Anthony](https://github.com/leaanthony) - Project owner, lead developer
- [Stffabi](https://github.com/stffabi) - Technical lead, developer and
maintainer
- [Travis McLane](https://github.com/tmclane) - Cross-compilation work, MacOS
testing
- [Atterpac](https://github.com/atterpac) - Developer, support guru, powerhouse
- [Simon Thomas](mailto:enquiries@wails.io) - Growth Hacker
- [Lyimmi](https://github.com/Lyimmi) - All things Linux
- [fbbdev](https://github.com/fbbdev) - Bindings Generator guru & core contributor
## Sponsors
<br/>
<a href="https://zsa.io/">
<img
src="/sponsors/zsa.png"
style={{ margin: "auto", width: "400px" }}
alt="ZSA"
/>
</a>
<img
src="https://wails.io/img/sponsors.svg"
style={{ margin: "auto", width: "100%", "max-width": "1600px;" }}
alt="Sponsors"
/>
Special thanks:
<img
src="/sponsors/jetbrains-grayscale.webp"
style={{ margin: "auto", width: "100px" }}
alt="JetBrains"
/>
## Contributors
<Contributors />
## Special Mentions
- [John Chadwick](https://github.com/jchv) - His amazing work on
[go-webview2](https://github.com/jchv/go-webview2) and
[go-winloader](https://github.com/jchv/go-winloader) have made the Windows
version possible.
- [Tad Vizbaras](https://github.com/tadvi) - His winc project was the first step
down the path to a pure Go Wails.
- [Mat Ryer](https://github.com/matryer) - For advice, support and bants.
- [Byron Chris](https://github.com/bh90210) - For his long term contributions to
this project.
- [Dustin Krysak](https://wiki.ubuntu.com/bashfulrobot) - His support and
feedback has been invaluable.
- [Justen Walker](https://github.com/justenwalker/) - For helping wrangle COM
issues which got v2 over the line.
- [Wang, Chi](https://github.com/patr0nus/) - The DeskGap project was a huge
influence on the direction of Wails v2.
- [Serge Zaitsev](https://github.com/zserge) - Whilst Wails does not use the
Webview project, it is still a source of inspiration.

View file

@ -0,0 +1,260 @@
---
title: Frequently Asked Questions
description: Common questions about Wails
sidebar:
order: 99
---
## General
### What is Wails?
Wails is a framework for building desktop applications using Go and web technologies. It provides native OS integration whilst allowing you to build your UI with HTML, CSS, and JavaScript.
### How does Wails compare to Electron?
**Wails:**
- ~10MB memory usage
- ~15MB binary size
- Native performance
- Go backend
**Electron:**
- ~100MB+ memory usage
- ~150MB+ binary size
- Chromium overhead
- Node.js backend
### Is Wails production-ready?
Yes! Wails v3 is suitable for production applications. Many companies use Wails for their desktop applications.
### What platforms does Wails support?
- Windows (7+)
- macOS (10.13+)
- Linux (GTK3 and GTK4)
## Development
### Do I need to know Go?
Basic Go knowledge is helpful but not required. You can start with simple services and learn as you go.
### Can I use my favourite frontend framework?
Yes! Wails works with:
- Vanilla JavaScript
- React
- Vue
- Svelte
- Any framework that builds to HTML/CSS/JS
### How do I call Go functions from JavaScript?
Use bindings:
```go
// Go
func (s *Service) Greet(name string) string {
return "Hello " + name
}
```
```javascript
// JavaScript
import { Greet } from './bindings/myapp/service'
const message = await Greet("World")
```
### Can I use TypeScript?
Yes! Wails generates TypeScript definitions automatically.
### How do I debug my application?
Use your browser's dev tools:
- Right-click → Inspect Element
- Or enable dev tools in window options
## Building & Distribution
### How do I build for production?
```bash
wails3 build
```
Your application will be in `bin/`.
### Can I cross-compile?
Cross-compilation is controlled by setting the `GOOS` and `GOARCH` environment variables. Note that CGo cross-compilation may require additional tooling:
```bash
GOOS=windows GOARCH=amd64 wails3 build
GOOS=linux GOARCH=amd64 wails3 build
```
### How do I create an installer?
Use platform-specific tools:
- Windows: NSIS or WiX
- macOS: Create DMG
- Linux: DEB/RPM packages
See the [Installers Guide](/guides/installers).
### How do I code sign my application?
**macOS:**
```bash
codesign --deep --force --sign "Developer ID" MyApp.app
```
**Windows:**
Use SignTool with your certificate.
## Features
### Can I create multiple windows?
Yes! Wails v3 has native multi-window support:
```go
window1 := app.Window.New()
window2 := app.Window.New()
```
### Does Wails support system tray?
Yes! Create system tray applications:
```go
tray := app.SystemTray.New()
tray.SetIcon(iconBytes)
tray.SetMenu(menu)
```
### Can I use native dialogs?
Yes! Wails provides native dialogs:
```go
path, _ := app.Dialog.OpenFile().
SetTitle("Select File").
PromptForSingleSelection()
```
### Does Wails support auto-updates?
Wails doesn't include auto-update functionality, but you can implement it yourself or use third-party libraries.
## Performance
### Why is my binary large?
Go binaries include the runtime. The default Taskfiles already include `-ldflags "-s -w"` to strip debug symbols. You can further reduce size with UPX compression:
```bash
upx --best bin/myapp
```
### How do I improve performance?
- Paginate large datasets
- Cache expensive operations
- Use events for updates
- Optimise frontend bundle
- Profile your code
See the [Performance Guide](/guides/performance).
### Does Wails support hot reload?
Yes! Use dev mode:
```bash
wails3 dev
```
## Troubleshooting
### My bindings aren't working
Regenerate bindings:
```bash
wails3 generate bindings
```
### Window doesn't appear
Check if you called `Show()`:
```go
window := app.Window.New()
window.Show() // Don't forget this!
```
### Events not firing
Ensure event names match exactly:
```go
// Go
app.Event.Emit("my-event", data)
// JavaScript
import { Events } from '@wailsio/runtime'
Events.On("my-event", handler) // Must match
```
### Build fails
Common fixes:
- Run `go mod tidy`
- Check `wails.json` configuration
- Verify frontend builds: `cd frontend && npm run build`
- Update Wails: `go install github.com/wailsapp/wails/v3/cmd/wails3@latest`
## Migration
### Should I migrate from v2 to v3?
v3 offers:
- Better performance
- Multi-window support
- Improved API
- Better developer experience
See the [Migration Guide](/migration/v2-to-v3).
### Will v2 be maintained?
Yes, v2 will receive critical updates.
### Can I run v2 and v3 side by side?
Yes, they use different import paths.
## Community
### How do I get help?
- [Discord Community](https://discord.gg/JDdSxwjhGf)
- [GitHub Discussions](https://github.com/wailsapp/wails/discussions)
- [GitHub Issues](https://github.com/wailsapp/wails/issues)
### How do I contribute?
See the [Contributing Guide](/contributing).
### Where can I find examples?
- [Official Examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples)
- [Community Examples](https://github.com/topics/wails)
## Still Have Questions?
Ask in [Discord](https://discord.gg/JDdSxwjhGf) or [open a discussion](https://github.com/wailsapp/wails/discussions).

View file

@ -0,0 +1,342 @@
---
title: Advanced Binding
description: Advanced binding techniques including directives, code injection, and custom IDs
sidebar:
order: 3
---
import { FileTree } from "@astrojs/starlight/components";
This guide covers advanced techniques for customizing and optimizing the binding generation process in Wails v3.
## Customizing Generated Code with Directives
### Injecting Custom Code
The `//wails:inject` directive allows you to inject custom JavaScript/TypeScript code into the generated bindings:
```go
//wails:inject console.log("Hello from Wails!");
type MyService struct {}
func (s *MyService) Greet(name string) string {
return "Hello, " + name
}
```
This will inject the specified code into the generated JavaScript/TypeScript file for the `MyService` service.
You can also use conditional injection to target specific output formats:
```go
//wails:inject j*:console.log("Hello JS!"); // JavaScript only
//wails:inject t*:console.log("Hello TS!"); // TypeScript only
```
### Including Additional Files
The `//wails:include` directive allows you to include additional files with the generated bindings:
```go
//wails:include js/*.js
package mypackage
```
This directive is typically used in package documentation comments to include additional JavaScript/TypeScript files with the generated bindings.
### Marking Internal Types and Methods
The `//wails:internal` directive marks a type or method as internal, preventing it from being exported to the frontend:
```go
//wails:internal
type InternalModel struct {
Field string
}
//wails:internal
func (s *MyService) InternalMethod() {}
```
This is useful for types and methods that are only used internally by your Go code and should not be exposed to the frontend.
### Ignoring Methods
The `//wails:ignore` directive completely ignores a method during binding generation:
```go
//wails:ignore
func (s *MyService) IgnoredMethod() {}
```
This is similar to `//wails:internal`, but it completely ignores the method rather than marking it as internal.
### Custom Method IDs
The `//wails:id` directive specifies a custom ID for a method, overriding the default hash-based ID:
```go
//wails:id 42
func (s *MyService) CustomIDMethod() {}
```
This can be useful for maintaining compatibility when refactoring code.
## Working with Complex Types
### Nested Structs
The binding generator handles nested structs automatically:
```go
type Address struct {
Street string
City string
State string
Zip string
}
type Person struct {
Name string
Address Address
}
func (s *MyService) GetPerson() Person {
return Person{
Name: "John Doe",
Address: Address{
Street: "123 Main St",
City: "Anytown",
State: "CA",
Zip: "12345",
},
}
}
```
The generated JavaScript/TypeScript code will include classes for both `Person` and `Address`.
### Maps and Slices
Maps and slices are also handled automatically:
```go
type Person struct {
Name string
Attributes map[string]string
Friends []string
}
func (s *MyService) GetPerson() Person {
return Person{
Name: "John Doe",
Attributes: map[string]string{
"hair": "brown",
"eyes": "blue",
},
Friends: []string{"Jane", "Bob", "Alice"},
}
}
```
In JavaScript, maps are represented as objects and slices as arrays. In TypeScript, maps are represented as `Record<K, V>` and slices as `T[]`.
### Generic Types
The binding generator supports generic types:
```go
type Result[T any] struct {
Data T
Error string
}
func (s *MyService) GetResult() Result[string] {
return Result[string]{
Data: "Hello, World!",
Error: "",
}
}
```
The generated TypeScript code will include a generic class for `Result`:
```typescript
export class Result<T> {
"Data": T;
"Error": string;
constructor(source: Partial<Result<T>> = {}) {
if (!("Data" in source)) {
this["Data"] = null as any;
}
if (!("Error" in source)) {
this["Error"] = "";
}
Object.assign(this, source);
}
static createFrom<T>(source: string | object = {}): Result<T> {
let parsedSource = typeof source === "string" ? JSON.parse(source) : source;
return new Result<T>(parsedSource as Partial<Result<T>>);
}
}
```
### Interfaces
The binding generator can generate TypeScript interfaces instead of classes using the `-i` flag:
```bash
wails3 generate bindings -ts -i
```
This will generate TypeScript interfaces for all models:
```typescript
export interface Person {
Name: string;
Attributes: Record<string, string>;
Friends: string[];
}
```
## Optimizing Binding Generation
### Using Names Instead of IDs
By default, the binding generator uses hash-based IDs for method calls. You can use the `-names` flag to use names instead:
```bash
wails3 generate bindings -names
```
This will generate code that uses fully qualified method names instead of IDs:
```javascript
export function Greet(name) {
return $Call.ByName("main.MyService.Greet", name);
}
```
This can make the generated code more readable and easier to debug, but it may be slightly less efficient.
### Bundling the Runtime
By default, the generated code imports the Wails runtime from the `@wailsio/runtime` npm package. You can use the `-b` flag to bundle the runtime with the generated code:
```bash
wails3 generate bindings -b
```
This will include the runtime code directly in the generated files, eliminating the need for the npm package.
### Disabling Index Files
If you don't need the index files, you can use the `-noindex` flag to disable their generation:
```bash
wails3 generate bindings -noindex
```
This can be useful if you prefer to import services and models directly from their respective files.
## Real-World Examples
### Authentication Service
Here's an example of an authentication service with custom directives:
```go
package auth
//wails:inject console.log("Auth service initialized");
type AuthService struct {
// Private fields
users map[string]User
}
type User struct {
Username string
Email string
Role string
}
type LoginRequest struct {
Username string
Password string
}
type LoginResponse struct {
Success bool
User User
Token string
Error string
}
// Login authenticates a user
func (s *AuthService) Login(req LoginRequest) LoginResponse {
// Implementation...
}
// GetCurrentUser returns the current user
func (s *AuthService) GetCurrentUser() User {
// Implementation...
}
// Internal helper method - not exported to frontend
//wails:internal
func (s *AuthService) ValidateCredentials(username, password string) bool {
// Implementation...
}
```
### Data Processing Service
Here's an example of a data processing service with generic types:
```go
package data
type ProcessingResult[T any] struct {
Data T
Error string
}
type DataService struct {}
// Process processes data and returns a result
func (s *DataService) Process(data string) ProcessingResult[map[string]int] {
// Implementation...
}
// ProcessBatch processes multiple data items
func (s *DataService) ProcessBatch(data []string) ProcessingResult[[]map[string]int] {
// Implementation...
}
// Internal helper method - not exported to frontend
//wails:internal
func (s *DataService) ParseData(data string) (map[string]int, error) {
// Implementation...
}
```
### Conditional Code Injection
Here's an example of conditional code injection for different output formats:
```go
//wails:inject j*:/**
//wails:inject j*: * @param {string} arg
//wails:inject j*: * @returns {Promise<void>}
//wails:inject j*: */
//wails:inject j*:export async function CustomMethod(arg) {
//wails:inject t*:export async function CustomMethod(arg: string): Promise<void> {
//wails:inject await InternalMethod("Hello " + arg + "!");
//wails:inject }
type Service struct{}
```
This injects different code for JavaScript and TypeScript outputs, providing appropriate type annotations for each language.

View file

@ -0,0 +1,689 @@
---
title: Bindings Best Practices
description: Design patterns and best practices for Go-JavaScript bindings
sidebar:
order: 4
---
## Bindings Best Practices
Follow **proven patterns** for binding design to create clean, performant, and secure bindings. This guide covers API design principles, performance optimisation, security patterns, error handling, and testing strategies for maintainable applications.
## API Design Principles
### 1. Single Responsibility
Each service should have one clear purpose:
```go
// ❌ Bad: God object
type AppService struct {
// Does everything
}
func (a *AppService) SaveFile(path string, data []byte) error
func (a *AppService) GetUser(id int) (*User, error)
func (a *AppService) SendEmail(to, subject, body string) error
func (a *AppService) ProcessPayment(amount float64) error
// ✅ Good: Focused services
type FileService struct{}
func (f *FileService) Save(path string, data []byte) error
type UserService struct{}
func (u *UserService) GetByID(id int) (*User, error)
type EmailService struct{}
func (e *EmailService) Send(to, subject, body string) error
type PaymentService struct{}
func (p *PaymentService) Process(amount float64) error
```
### 2. Clear Method Names
Use descriptive, action-oriented names:
```go
// ❌ Bad: Unclear names
func (s *Service) Do(x string) error
func (s *Service) Handle(data interface{}) interface{}
func (s *Service) Process(input map[string]interface{}) bool
// ✅ Good: Clear names
func (s *FileService) SaveDocument(path string, content string) error
func (s *UserService) AuthenticateUser(email, password string) (*User, error)
func (s *OrderService) CreateOrder(items []Item) (*Order, error)
```
### 3. Consistent Return Types
Always return errors explicitly:
```go
// ❌ Bad: Inconsistent error handling
func (s *Service) GetData() interface{} // How to handle errors?
func (s *Service) SaveData(data string) // Silent failures?
// ✅ Good: Explicit errors
func (s *Service) GetData() (Data, error)
func (s *Service) SaveData(data string) error
```
### 4. Input Validation
Validate all input on the Go side:
```go
// ❌ Bad: No validation
func (s *UserService) CreateUser(email, password string) (*User, error) {
user := &User{Email: email, Password: password}
return s.db.Create(user)
}
// ✅ Good: Validate first
func (s *UserService) CreateUser(email, password string) (*User, error) {
// Validate email
if !isValidEmail(email) {
return nil, errors.New("invalid email address")
}
// Validate password
if len(password) < 8 {
return nil, errors.New("password must be at least 8 characters")
}
// Hash password
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return nil, err
}
user := &User{
Email: email,
PasswordHash: string(hash),
}
return s.db.Create(user)
}
```
## Performance Patterns
### 1. Batch Operations
Reduce bridge calls by batching:
```go
// ❌ Bad: N calls
// JavaScript
for (const item of items) {
await ProcessItem(item) // N bridge calls
}
// ✅ Good: 1 call
// Go
func (s *Service) ProcessItems(items []Item) ([]Result, error) {
results := make([]Result, len(items))
for i, item := range items {
results[i] = s.processItem(item)
}
return results, nil
}
// JavaScript
const results = await ProcessItems(items) // 1 bridge call
```
### 2. Pagination
Don't return huge datasets:
```go
// ❌ Bad: Returns everything
func (s *Service) GetAllUsers() ([]User, error) {
return s.db.FindAll() // Could be millions
}
// ✅ Good: Paginated
type PageRequest struct {
Page int `json:"page"`
PageSize int `json:"pageSize"`
}
type PageResponse struct {
Items []User `json:"items"`
TotalItems int `json:"totalItems"`
TotalPages int `json:"totalPages"`
Page int `json:"page"`
}
func (s *Service) GetUsers(req PageRequest) (*PageResponse, error) {
// Validate
if req.Page < 1 {
req.Page = 1
}
if req.PageSize < 1 || req.PageSize > 100 {
req.PageSize = 20
}
// Get total
total, err := s.db.Count()
if err != nil {
return nil, err
}
// Get page
offset := (req.Page - 1) * req.PageSize
users, err := s.db.Find(offset, req.PageSize)
if err != nil {
return nil, err
}
return &PageResponse{
Items: users,
TotalItems: total,
TotalPages: (total + req.PageSize - 1) / req.PageSize,
Page: req.Page,
}, nil
}
```
### 3. Caching
Cache expensive operations:
```go
type CachedService struct {
cache map[string]interface{}
mu sync.RWMutex
ttl time.Duration
}
func (s *CachedService) GetData(key string) (interface{}, error) {
// Check cache
s.mu.RLock()
if data, ok := s.cache[key]; ok {
s.mu.RUnlock()
return data, nil
}
s.mu.RUnlock()
// Fetch data
data, err := s.fetchData(key)
if err != nil {
return nil, err
}
// Cache it
s.mu.Lock()
s.cache[key] = data
s.mu.Unlock()
// Schedule expiry
go func() {
time.Sleep(s.ttl)
s.mu.Lock()
delete(s.cache, key)
s.mu.Unlock()
}()
return data, nil
}
```
### 4. Streaming with Events
Use events for streaming data:
```go
// ❌ Bad: Polling
func (s *Service) GetProgress() int {
return s.progress
}
// JavaScript polls
setInterval(async () => {
const progress = await GetProgress()
updateUI(progress)
}, 100)
// ✅ Good: Events
func (s *Service) ProcessLargeFile(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
total := 0
processed := 0
// Count lines
for scanner.Scan() {
total++
}
// Process
file.Seek(0, 0)
scanner = bufio.NewScanner(file)
for scanner.Scan() {
s.processLine(scanner.Text())
processed++
// Emit progress
s.app.Event.Emit("progress", map[string]interface{}{
"processed": processed,
"total": total,
"percent": int(float64(processed) / float64(total) * 100),
})
}
return scanner.Err()
}
// JavaScript listens
import { Events } from '@wailsio/runtime'
Events.On("progress", (data) => {
updateProgress(data.percent)
})
```
## Security Patterns
### 1. Input Sanitisation
Always sanitise user input:
```go
import (
"html"
"strings"
)
func (s *Service) SaveComment(text string) error {
// Sanitise
text = strings.TrimSpace(text)
text = html.EscapeString(text)
// Validate length
if len(text) == 0 {
return errors.New("comment cannot be empty")
}
if len(text) > 1000 {
return errors.New("comment too long")
}
return s.db.SaveComment(text)
}
```
### 2. Authentication
Protect sensitive operations:
```go
type AuthService struct {
sessions map[string]*Session
mu sync.RWMutex
}
func (a *AuthService) Login(email, password string) (string, error) {
user, err := a.db.FindByEmail(email)
if err != nil {
return "", errors.New("invalid credentials")
}
if !a.verifyPassword(user.PasswordHash, password) {
return "", errors.New("invalid credentials")
}
// Create session
token := generateToken()
a.mu.Lock()
a.sessions[token] = &Session{
UserID: user.ID,
ExpiresAt: time.Now().Add(24 * time.Hour),
}
a.mu.Unlock()
return token, nil
}
func (a *AuthService) requireAuth(token string) (*Session, error) {
a.mu.RLock()
session, ok := a.sessions[token]
a.mu.RUnlock()
if !ok {
return nil, errors.New("not authenticated")
}
if time.Now().After(session.ExpiresAt) {
return nil, errors.New("session expired")
}
return session, nil
}
// Protected method
func (a *AuthService) DeleteAccount(token string) error {
session, err := a.requireAuth(token)
if err != nil {
return err
}
return a.db.DeleteUser(session.UserID)
}
```
### 3. Rate Limiting
Prevent abuse:
```go
type RateLimiter struct {
requests map[string][]time.Time
mu sync.Mutex
limit int
window time.Duration
}
func (r *RateLimiter) Allow(key string) bool {
r.mu.Lock()
defer r.mu.Unlock()
now := time.Now()
// Clean old requests
if requests, ok := r.requests[key]; ok {
var recent []time.Time
for _, t := range requests {
if now.Sub(t) < r.window {
recent = append(recent, t)
}
}
r.requests[key] = recent
}
// Check limit
if len(r.requests[key]) >= r.limit {
return false
}
// Add request
r.requests[key] = append(r.requests[key], now)
return true
}
// Usage
func (s *Service) SendEmail(to, subject, body string) error {
if !s.rateLimiter.Allow(to) {
return errors.New("rate limit exceeded")
}
return s.emailer.Send(to, subject, body)
}
```
## Error Handling Patterns
### 1. Descriptive Errors
Provide context in errors:
```go
// ❌ Bad: Generic errors
func (s *Service) LoadFile(path string) ([]byte, error) {
return os.ReadFile(path) // "no such file or directory"
}
// ✅ Good: Contextual errors
func (s *Service) LoadFile(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to load file %s: %w", path, err)
}
return data, nil
}
```
### 2. Error Types
Use typed errors for specific handling:
```go
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Message)
}
type NotFoundError struct {
Resource string
ID interface{}
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s not found: %v", e.Resource, e.ID)
}
// Usage
func (s *UserService) GetUser(id int) (*User, error) {
if id <= 0 {
return nil, &ValidationError{
Field: "id",
Message: "must be positive",
}
}
user, err := s.db.Find(id)
if err == sql.ErrNoRows {
return nil, &NotFoundError{
Resource: "User",
ID: id,
}
}
if err != nil {
return nil, fmt.Errorf("database error: %w", err)
}
return user, nil
}
```
### 3. Error Recovery
Handle errors gracefully:
```go
func (s *Service) ProcessWithRetry(data string) error {
maxRetries := 3
for attempt := 1; attempt <= maxRetries; attempt++ {
err := s.process(data)
if err == nil {
return nil
}
// Log attempt
s.app.Logger.Warn("Process failed",
"attempt", attempt,
"error", err)
// Don't retry on validation errors
if _, ok := err.(*ValidationError); ok {
return err
}
// Wait before retry
if attempt < maxRetries {
time.Sleep(time.Duration(attempt) * time.Second)
}
}
return fmt.Errorf("failed after %d attempts", maxRetries)
}
```
## Testing Patterns
### 1. Unit Testing
Test services in isolation:
```go
func TestUserService_CreateUser(t *testing.T) {
// Setup
db := &MockDB{}
service := &UserService{db: db}
// Test valid input
user, err := service.CreateUser("test@example.com", "password123")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Email != "test@example.com" {
t.Errorf("expected email test@example.com, got %s", user.Email)
}
// Test invalid email
_, err = service.CreateUser("invalid", "password123")
if err == nil {
t.Error("expected error for invalid email")
}
// Test short password
_, err = service.CreateUser("test@example.com", "short")
if err == nil {
t.Error("expected error for short password")
}
}
```
### 2. Integration Testing
Test with real dependencies:
```go
func TestUserService_Integration(t *testing.T) {
// Setup real database
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatal(err)
}
defer db.Close()
// Create schema
_, err = db.Exec(`CREATE TABLE users (...)`)
if err != nil {
t.Fatal(err)
}
// Test service
service := &UserService{db: db}
user, err := service.CreateUser("test@example.com", "password123")
if err != nil {
t.Fatal(err)
}
// Verify in database
var count int
db.QueryRow("SELECT COUNT(*) FROM users WHERE email = ?",
user.Email).Scan(&count)
if count != 1 {
t.Errorf("expected 1 user, got %d", count)
}
}
```
### 3. Mock Services
Create testable interfaces:
```go
type UserRepository interface {
Create(user *User) error
FindByEmail(email string) (*User, error)
Update(user *User) error
Delete(id int) error
}
type UserService struct {
repo UserRepository
}
// Mock for testing
type MockUserRepository struct {
users map[string]*User
}
func (m *MockUserRepository) Create(user *User) error {
m.users[user.Email] = user
return nil
}
// Test with mock
func TestUserService_WithMock(t *testing.T) {
mock := &MockUserRepository{
users: make(map[string]*User),
}
service := &UserService{repo: mock}
// Test
user, err := service.CreateUser("test@example.com", "password123")
if err != nil {
t.Fatal(err)
}
// Verify mock was called
if len(mock.users) != 1 {
t.Error("expected 1 user in mock")
}
}
```
## Best Practices Summary
### ✅ Do
- **Single responsibility** - One service, one purpose
- **Clear naming** - Descriptive method names
- **Validate input** - Always on Go side
- **Return errors** - Explicit error handling
- **Batch operations** - Reduce bridge calls
- **Use events** - For streaming data
- **Sanitise input** - Prevent injection
- **Test thoroughly** - Unit and integration tests
- **Document methods** - Comments become JSDoc
- **Version your API** - Plan for changes
### ❌ Don't
- **Don't create god objects** - Keep services focused
- **Don't trust frontend** - Validate everything
- **Don't return huge datasets** - Use pagination
- **Don't block** - Use goroutines for long operations
- **Don't ignore errors** - Handle all error cases
- **Don't skip testing** - Test early and often
- **Don't hardcode** - Use configuration
- **Don't expose internals** - Keep implementation private
## Next Steps
- [Methods](/features/bindings/methods) - Learn method binding basics
- [Services](/features/bindings/services) - Understand service architecture
- [Models](/features/bindings/models) - Bind complex data structures
- [Events](/features/events/system) - Use events for pub/sub
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [binding examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/binding).

View file

@ -0,0 +1,650 @@
---
title: Method Bindings
description: Call Go methods from JavaScript with type safety
sidebar:
order: 1
---
import { FileTree, Card, CardGrid } from "@astrojs/starlight/components";
## Type-Safe Go-JavaScript Bindings
Wails **automatically generates type-safe JavaScript/TypeScript bindings** for your Go methods. Write Go code, run one command, and get fully-typed frontend functions with no HTTP overhead, no manual work, and zero boilerplate.
## Quick Start
**1. Write Go service:**
```go
type GreetService struct{}
func (g *GreetService) Greet(name string) string {
return "Hello, " + name + "!"
}
```
**2. Register service:**
```go
app := application.New(application.Options{
Services: []application.Service{
application.NewService(&GreetService{}),
},
})
```
**3. Generate bindings:**
```bash
wails3 generate bindings
```
**4. Use in JavaScript:**
```javascript
import { Greet } from './bindings/myapp/greetservice'
const message = await Greet("World")
console.log(message) // "Hello, World!"
```
**That's it!** Type-safe Go-to-JavaScript calls.
## Creating Services
### Basic Service
```go
package main
import "github.com/wailsapp/wails/v3/pkg/application"
type CalculatorService struct{}
func (c *CalculatorService) Add(a, b int) int {
return a + b
}
func (c *CalculatorService) Subtract(a, b int) int {
return a - b
}
func (c *CalculatorService) Multiply(a, b int) int {
return a * b
}
func (c *CalculatorService) Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
```
**Register:**
```go
app := application.New(application.Options{
Services: []application.Service{
application.NewService(&CalculatorService{}),
},
})
```
**Key points:**
- Only **exported methods** (PascalCase) are bound
- Methods can return values or `(value, error)`
- Services are **singletons** (one instance per application)
### Service with State
```go
type CounterService struct {
count int
mu sync.RWMutex
}
func (c *CounterService) Increment() int {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
return c.count
}
func (c *CounterService) Decrement() int {
c.mu.Lock()
defer c.mu.Unlock()
c.count--
return c.count
}
func (c *CounterService) GetCount() int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.count
}
func (c *CounterService) Reset() {
c.mu.Lock()
defer c.mu.Unlock()
c.count = 0
}
```
**Important:** Services are shared across all windows. Use mutexes for thread safety.
### Service with Dependencies
```go
type DatabaseService struct {
db *sql.DB
}
func NewDatabaseService(db *sql.DB) *DatabaseService {
return &DatabaseService{db: db}
}
func (d *DatabaseService) GetUser(id int) (*User, error) {
var user User
err := d.db.QueryRow("SELECT * FROM users WHERE id = ?", id).Scan(&user)
return &user, err
}
```
**Register with dependencies:**
```go
db, _ := sql.Open("sqlite3", "app.db")
app := application.New(application.Options{
Services: []application.Service{
application.NewService(NewDatabaseService(db)),
},
})
```
## Generating Bindings
### Basic Generation
```bash
wails3 generate bindings
```
**Output:**
```
INFO 347 Packages, 3 Services, 12 Methods, 0 Enums, 0 Models in 1.98s
INFO Output directory: /myproject/frontend/bindings
```
**Generated structure:**
<FileTree>
- frontend/bindings
- myapp
- calculatorservice.js
- counterservice.js
- databaseservice.js
- index.js
</FileTree>
### TypeScript Generation
```bash
wails3 generate bindings -ts
```
**Generates `.ts` files** with full TypeScript types.
### Custom Output Directory
```bash
wails3 generate bindings -o ./src/bindings
```
### Watch Mode (Development)
```bash
wails3 dev
```
**Automatically regenerates bindings** when Go code changes.
## Using Bindings
### JavaScript
**Generated binding:**
```javascript
// frontend/bindings/myapp/calculatorservice.js
import { Call as $Call, CancellablePromise as $CancellablePromise } from "@wailsio/runtime";
/**
* @param {number} a
* @param {number} b
* @returns {$CancellablePromise<number>}
*/
export function Add(a, b) {
return $Call.ByID(1411160069, a, b);
}
```
**Usage:**
```javascript
import { Add, Subtract, Multiply, Divide } from './bindings/myapp/calculatorservice'
// Simple calls
const sum = await Add(5, 3) // 8
const diff = await Subtract(10, 4) // 6
const product = await Multiply(7, 6) // 42
// Error handling
try {
const result = await Divide(10, 0)
} catch (error) {
console.error("Error:", error) // "division by zero"
}
```
### TypeScript
**Generated binding:**
```typescript
// frontend/bindings/myapp/calculatorservice.ts
import { Call as $Call, CancellablePromise as $CancellablePromise } from "@wailsio/runtime";
export function Add(a: number, b: number): $CancellablePromise<number> {
return $Call.ByID(1411160069, a, b);
}
export function Subtract(a: number, b: number): $CancellablePromise<number> {
return $Call.ByID(1411160070, a, b);
}
```
**Usage:**
```typescript
import { Add, Divide } from './bindings/myapp/calculatorservice'
const sum: number = await Add(5, 3)
try {
const result = await Divide(10, 0)
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message)
}
}
```
**Benefits:**
- Full type checking
- IDE autocomplete
- Compile-time errors
- Better refactoring
### Index Files
**Generated index:**
```javascript
// frontend/bindings/myapp/index.js
export * as CalculatorService from './calculatorservice.js'
export * as CounterService from './counterservice.js'
export * as DatabaseService from './databaseservice.js'
```
**Simplified imports:**
```javascript
import { CalculatorService } from './bindings/myapp'
const sum = await CalculatorService.Add(5, 3)
```
## Type Mapping
### Primitive Types
| Go Type | JavaScript/TypeScript |
|---------|----------------------|
| `string` | `string` |
| `bool` | `boolean` |
| `int`, `int8`, `int16`, `int32`, `int64` | `number` |
| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | `number` |
| `float32`, `float64` | `number` |
| `byte` | `number` |
| `rune` | `number` |
### Complex Types
| Go Type | JavaScript/TypeScript |
|---------|----------------------|
| `[]T` | `T[]` |
| `[N]T` | `T[]` |
| `map[string]T` | `Record<string, T>` |
| `map[K]V` | `Map<K, V>` |
| `struct` | `class` (with fields) |
| `time.Time` | `Date` |
| `*T` | `T` (pointers transparent) |
| `interface{}` | `any` |
| `error` | Exception (thrown) |
### Unsupported Types
These types **cannot** be passed across the bridge:
- `chan T` (channels)
- `func()` (functions)
- Complex interfaces (except `interface{}`)
- Unexported fields (lowercase)
**Workaround:** Use IDs or handles:
```go
// ❌ Can't return file handle
func OpenFile(path string) (*os.File, error)
// ✅ Return file ID instead
var files = make(map[string]*os.File)
func OpenFile(path string) (string, error) {
file, err := os.Open(path)
if err != nil {
return "", err
}
id := generateID()
files[id] = file
return id, nil
}
func ReadFile(id string) ([]byte, error) {
file := files[id]
return io.ReadAll(file)
}
func CloseFile(id string) error {
file := files[id]
delete(files, id)
return file.Close()
}
```
## Error Handling
### Go Side
```go
func (d *DatabaseService) GetUser(id int) (*User, error) {
if id <= 0 {
return nil, errors.New("invalid user ID")
}
var user User
err := d.db.QueryRow("SELECT * FROM users WHERE id = ?", id).Scan(&user)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("user %d not found", id)
}
if err != nil {
return nil, fmt.Errorf("database error: %w", err)
}
return &user, nil
}
```
### JavaScript Side
```javascript
import { GetUser } from './bindings/myapp/databaseservice'
try {
const user = await GetUser(123)
console.log("User:", user)
} catch (error) {
console.error("Error:", error)
// Error: "user 123 not found"
}
```
**Error types:**
- Go `error` → JavaScript exception
- Error message preserved
- Stack trace available
## Performance
### Call Overhead
**Typical call:** &lt;1ms
```
JavaScript → Bridge → Go → Bridge → JavaScript
↓ ↓ ↓ ↓ ↓
&lt;0.1ms &lt;0.1ms [varies] &lt;0.1ms &lt;0.1ms
```
**Compared to alternatives:**
- HTTP/REST: 5-50ms
- IPC: 1-10ms
- Wails: &lt;1ms
### Optimisation Tips
**✅ Batch operations:**
```javascript
// ❌ Slow: N calls
for (const item of items) {
await ProcessItem(item)
}
// ✅ Fast: 1 call
await ProcessItems(items)
```
**✅ Cache results:**
```javascript
// ❌ Repeated calls
const config1 = await GetConfig()
const config2 = await GetConfig()
// ✅ Cache
const config = await GetConfig()
// Use config multiple times
```
**✅ Use events for streaming:**
```go
func ProcessLargeFile(path string) error {
// Emit progress events
for line := range lines {
app.Event.Emit("progress", line)
}
return nil
}
```
## Complete Example
**Go:**
```go
package main
import (
"fmt"
"github.com/wailsapp/wails/v3/pkg/application"
)
type TodoService struct {
todos []Todo
}
type Todo struct {
ID int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
func (t *TodoService) GetAll() []Todo {
return t.todos
}
func (t *TodoService) Add(title string) Todo {
todo := Todo{
ID: len(t.todos) + 1,
Title: title,
Completed: false,
}
t.todos = append(t.todos, todo)
return todo
}
func (t *TodoService) Toggle(id int) error {
for i := range t.todos {
if t.todos[i].ID == id {
t.todos[i].Completed = !t.todos[i].Completed
return nil
}
}
return fmt.Errorf("todo %d not found", id)
}
func (t *TodoService) Delete(id int) error {
for i := range t.todos {
if t.todos[i].ID == id {
t.todos = append(t.todos[:i], t.todos[i+1:]...)
return nil
}
}
return fmt.Errorf("todo %d not found", id)
}
func main() {
app := application.New(application.Options{
Services: []application.Service{
application.NewService(&TodoService{}),
},
})
app.Window.New()
app.Run()
}
```
**JavaScript:**
```javascript
import { GetAll, Add, Toggle, Delete } from './bindings/myapp/todoservice'
class TodoApp {
async loadTodos() {
const todos = await GetAll()
this.renderTodos(todos)
}
async addTodo(title) {
try {
const todo = await Add(title)
this.loadTodos()
} catch (error) {
console.error("Failed to add todo:", error)
}
}
async toggleTodo(id) {
try {
await Toggle(id)
this.loadTodos()
} catch (error) {
console.error("Failed to toggle todo:", error)
}
}
async deleteTodo(id) {
try {
await Delete(id)
this.loadTodos()
} catch (error) {
console.error("Failed to delete todo:", error)
}
}
renderTodos(todos) {
const list = document.getElementById('todo-list')
list.innerHTML = todos.map(todo => `
<div class="todo ${todo.Completed ? 'completed' : ''}">
<input type="checkbox"
${todo.Completed ? 'checked' : ''}
onchange="app.toggleTodo(${todo.ID})">
<span>${todo.Title}</span>
<button onclick="app.deleteTodo(${todo.ID})">Delete</button>
</div>
`).join('')
}
}
const app = new TodoApp()
app.loadTodos()
```
## Best Practices
### ✅ Do
- **Keep methods simple** - Single responsibility
- **Return errors** - Don't panic
- **Use thread-safe state** - Mutexes for shared data
- **Batch operations** - Reduce bridge calls
- **Cache on Go side** - Avoid repeated work
- **Document methods** - Comments become JSDoc
### ❌ Don't
- **Don't block** - Use goroutines for long operations
- **Don't return channels** - Use events instead
- **Don't return functions** - Not supported
- **Don't ignore errors** - Always handle them
- **Don't use unexported fields** - Won't be bound
## Next Steps
<CardGrid>
<Card title="Services" icon="puzzle">
Deep dive into the service system.
[Learn More →](/features/bindings/services)
</Card>
<Card title="Models" icon="document">
Bind complex data structures.
[Learn More →](/features/bindings/models)
</Card>
<Card title="Go-Frontend Bridge" icon="rocket">
Understand the bridge mechanism.
[Learn More →](/concepts/bridge)
</Card>
<Card title="Events" icon="star">
Use events for pub/sub communication.
[Learn More →](/features/events/system)
</Card>
</CardGrid>
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [binding examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/binding).

View file

@ -0,0 +1,741 @@
---
title: Data Models
description: Bind complex data structures between Go and JavaScript
sidebar:
order: 3
---
import { Card, CardGrid } from "@astrojs/starlight/components";
## Data Model Bindings
Wails **automatically generates JavaScript/TypeScript classes** from Go structs, providing full type safety when passing complex data between backend and frontend. Write Go structs, generate bindings, and get fully-typed frontend models complete with constructors, type annotations, and JSDoc comments.
## Quick Start
**Go struct:**
```go
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"createdAt"`
}
func (s *UserService) GetUser(id int) (*User, error) {
// Return user
}
```
**Generate:**
```bash
wails3 generate bindings
```
**JavaScript:**
```javascript
import { GetUser } from './bindings/myapp/userservice'
import { User } from './bindings/myapp/models'
const user = await GetUser(1)
console.log(user.Name) // Type-safe!
```
**That's it!** Full type safety across the bridge.
## Defining Models
### Basic Struct
```go
type Person struct {
Name string
Age int
}
```
**Generated JavaScript:**
```javascript
export class Person {
/** @type {string} */
Name = ""
/** @type {number} */
Age = 0
constructor(source = {}) {
Object.assign(this, source)
}
static createFrom(source = {}) {
return new Person(source)
}
}
```
### With JSON Tags
```go
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"createdAt"`
}
```
**Generated JavaScript:**
```javascript
export class User {
/** @type {number} */
id = 0
/** @type {string} */
name = ""
/** @type {string} */
email = ""
/** @type {Date} */
createdAt = new Date()
constructor(source = {}) {
Object.assign(this, source)
}
}
```
**JSON tags control field names** in JavaScript.
### With Comments
```go
// User represents an application user
type User struct {
// Unique identifier
ID int `json:"id"`
// Full name of the user
Name string `json:"name"`
// Email address (must be unique)
Email string `json:"email"`
}
```
**Generated JavaScript:**
```javascript
/**
* User represents an application user
*/
export class User {
/**
* Unique identifier
* @type {number}
*/
id = 0
/**
* Full name of the user
* @type {string}
*/
name = ""
/**
* Email address (must be unique)
* @type {string}
*/
email = ""
}
```
**Comments become JSDoc!** Your IDE shows them.
### Nested Structs
```go
type Address struct {
Street string `json:"street"`
City string `json:"city"`
Country string `json:"country"`
}
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Address Address `json:"address"`
}
```
**Generated JavaScript:**
```javascript
export class Address {
/** @type {string} */
street = ""
/** @type {string} */
city = ""
/** @type {string} */
country = ""
}
export class User {
/** @type {number} */
id = 0
/** @type {string} */
name = ""
/** @type {Address} */
address = new Address()
}
```
**Usage:**
```javascript
const user = new User({
id: 1,
name: "Alice",
address: new Address({
street: "123 Main St",
city: "Springfield",
country: "USA"
})
})
```
### Arrays and Slices
```go
type Team struct {
Name string `json:"name"`
Members []string `json:"members"`
}
```
**Generated JavaScript:**
```javascript
export class Team {
/** @type {string} */
name = ""
/** @type {string[]} */
members = []
}
```
**Usage:**
```javascript
const team = new Team({
name: "Engineering",
members: ["Alice", "Bob", "Charlie"]
})
```
### Maps
```go
type Config struct {
Settings map[string]string `json:"settings"`
}
```
**Generated JavaScript:**
```javascript
export class Config {
/** @type {Record<string, string>} */
settings = {}
}
```
**Usage:**
```javascript
const config = new Config({
settings: {
theme: "dark",
language: "en"
}
})
```
## Type Mapping
### Primitive Types
| Go Type | JavaScript Type |
|---------|----------------|
| `string` | `string` |
| `bool` | `boolean` |
| `int`, `int8`, `int16`, `int32`, `int64` | `number` |
| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | `number` |
| `float32`, `float64` | `number` |
| `byte` | `number` |
| `rune` | `number` |
### Special Types
| Go Type | JavaScript Type |
|---------|----------------|
| `time.Time` | `Date` |
| `[]byte` | `Uint8Array` |
| `*T` | `T` (pointers transparent) |
| `interface{}` | `any` |
### Collections
| Go Type | JavaScript Type |
|---------|----------------|
| `[]T` | `T[]` |
| `[N]T` | `T[]` |
| `map[string]T` | `Record<string, T>` |
| `map[K]V` | `Map<K, V>` |
### Unsupported Types
- `chan T` (channels)
- `func()` (functions)
- Complex interfaces (except `interface{}`)
- Unexported fields (lowercase)
## Using Models
### Creating Instances
```javascript
import { User } from './bindings/myapp/models'
// Empty instance
const user1 = new User()
// With data
const user2 = new User({
id: 1,
name: "Alice",
email: "alice@example.com"
})
// From JSON string
const user3 = User.createFrom('{"id":1,"name":"Alice"}')
// From object
const user4 = User.createFrom({ id: 1, name: "Alice" })
```
### Passing to Go
```javascript
import { CreateUser } from './bindings/myapp/userservice'
import { User } from './bindings/myapp/models'
const user = new User({
name: "Bob",
email: "bob@example.com"
})
const created = await CreateUser(user)
console.log("Created user:", created.id)
```
### Receiving from Go
```javascript
import { GetUser } from './bindings/myapp/userservice'
const user = await GetUser(1)
// user is already a User instance
console.log(user.name)
console.log(user.email)
console.log(user.createdAt.toISOString())
```
### Updating Models
```javascript
import { GetUser, UpdateUser } from './bindings/myapp/userservice'
// Get user
const user = await GetUser(1)
// Modify
user.name = "Alice Smith"
user.email = "alice.smith@example.com"
// Save
await UpdateUser(user)
```
## TypeScript Support
### Generated TypeScript
```bash
wails3 generate bindings -ts
```
**Generated:**
```typescript
/**
* User represents an application user
*/
export class User {
/**
* Unique identifier
*/
id: number = 0
/**
* Full name of the user
*/
name: string = ""
/**
* Email address (must be unique)
*/
email: string = ""
/**
* Account creation date
*/
createdAt: Date = new Date()
constructor(source: Partial<User> = {}) {
Object.assign(this, source)
}
static createFrom(source: string | Partial<User> = {}): User {
const parsedSource = typeof source === 'string'
? JSON.parse(source)
: source
return new User(parsedSource)
}
}
```
### Usage in TypeScript
```typescript
import { GetUser, CreateUser } from './bindings/myapp/userservice'
import { User } from './bindings/myapp/models'
async function example() {
// Create user
const newUser = new User({
name: "Alice",
email: "alice@example.com"
})
const created: User = await CreateUser(newUser)
// Get user
const user: User = await GetUser(created.id)
// Type-safe access
console.log(user.name.toUpperCase()) // ✅ string method
console.log(user.id + 1) // ✅ number operation
console.log(user.createdAt.getTime()) // ✅ Date method
}
```
## Advanced Patterns
### Optional Fields
```go
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Nickname *string `json:"nickname,omitempty"`
}
```
**JavaScript:**
```javascript
const user = new User({
id: 1,
name: "Alice",
nickname: "Ally" // Optional
})
// Check if set
if (user.nickname) {
console.log("Nickname:", user.nickname)
}
```
### Enums
```go
type UserRole string
const (
RoleAdmin UserRole = "admin"
RoleUser UserRole = "user"
RoleGuest UserRole = "guest"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Role UserRole `json:"role"`
}
```
**Generated:**
```javascript
export const UserRole = {
Admin: "admin",
User: "user",
Guest: "guest"
}
export class User {
/** @type {string} */
role = UserRole.User
}
```
**Usage:**
```javascript
import { User, UserRole } from './bindings/myapp/models'
const admin = new User({
name: "Admin",
role: UserRole.Admin
})
```
### Validation
```javascript
class User {
validate() {
if (!this.name) {
throw new Error("Name is required")
}
if (!this.email.includes('@')) {
throw new Error("Invalid email")
}
return true
}
}
// Use
const user = new User({ name: "Alice", email: "alice@example.com" })
user.validate() // ✅
const invalid = new User({ name: "", email: "invalid" })
invalid.validate() // ❌ Throws
```
### Serialisation
```javascript
// To JSON
const json = JSON.stringify(user)
// From JSON
const user = User.createFrom(json)
// To plain object
const obj = { ...user }
// From plain object
const user2 = new User(obj)
```
## Complete Example
**Go:**
```go
package main
import (
"time"
"github.com/wailsapp/wails/v3/pkg/application"
)
type Address struct {
Street string `json:"street"`
City string `json:"city"`
Country string `json:"country"`
}
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Address Address `json:"address"`
CreatedAt time.Time `json:"createdAt"`
}
type UserService struct {
users []User
}
func (s *UserService) GetAll() []User {
return s.users
}
func (s *UserService) GetByID(id int) (*User, error) {
for _, user := range s.users {
if user.ID == id {
return &user, nil
}
}
return nil, fmt.Errorf("user %d not found", id)
}
func (s *UserService) Create(user User) User {
user.ID = len(s.users) + 1
user.CreatedAt = time.Now()
s.users = append(s.users, user)
return user
}
func (s *UserService) Update(user User) error {
for i, u := range s.users {
if u.ID == user.ID {
s.users[i] = user
return nil
}
}
return fmt.Errorf("user %d not found", user.ID)
}
func main() {
app := application.New(application.Options{
Services: []application.Service{
application.NewService(&UserService{}),
},
})
app.Window.New()
app.Run()
}
```
**JavaScript:**
```javascript
import { GetAll, GetByID, Create, Update } from './bindings/myapp/userservice'
import { User, Address } from './bindings/myapp/models'
class UserManager {
async loadUsers() {
const users = await GetAll()
this.renderUsers(users)
}
async createUser(name, email, address) {
const user = new User({
name,
email,
address: new Address(address)
})
try {
const created = await Create(user)
console.log("Created user:", created.id)
this.loadUsers()
} catch (error) {
console.error("Failed to create user:", error)
}
}
async updateUser(id, updates) {
try {
const user = await GetByID(id)
Object.assign(user, updates)
await Update(user)
this.loadUsers()
} catch (error) {
console.error("Failed to update user:", error)
}
}
renderUsers(users) {
const list = document.getElementById('users')
list.innerHTML = users.map(user => `
<div class="user">
<h3>${user.name}</h3>
<p>${user.email}</p>
<p>${user.address.city}, ${user.address.country}</p>
<small>Created: ${user.createdAt.toLocaleDateString()}</small>
</div>
`).join('')
}
}
const manager = new UserManager()
manager.loadUsers()
```
## Best Practices
### ✅ Do
- **Use JSON tags** - Control field names
- **Add comments** - They become JSDoc
- **Use time.Time** - Converts to Date
- **Validate on Go side** - Don't trust frontend
- **Keep models simple** - Data containers only
- **Use pointers for optional** - `*string` for nullable
### ❌ Don't
- **Don't add methods to Go structs** - Keep them as data
- **Don't use unexported fields** - Won't be bound
- **Don't use complex interfaces** - Not supported
- **Don't forget JSON tags** - Field names matter
- **Don't nest too deeply** - Keep it simple
## Next Steps
<CardGrid>
<Card title="Method Bindings" icon="rocket">
Learn how to bind Go methods.
[Learn More →](/features/bindings/methods)
</Card>
<Card title="Services" icon="puzzle">
Organise code with services.
[Learn More →](/features/bindings/services)
</Card>
<Card title="Best Practices" icon="approve-check">
Binding design patterns.
[Learn More →](/features/bindings/best-practices)
</Card>
<Card title="Go-Frontend Bridge" icon="star">
Understand the bridge mechanism.
[Learn More →](/concepts/bridge)
</Card>
</CardGrid>
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [binding examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/binding).

View file

@ -0,0 +1,807 @@
---
title: Services
description: Build modular, reusable application components with services
sidebar:
order: 2
---
## Service Architecture
Wails **services** provide a structured way to organise application logic with modular, self-contained components. Services are lifecycle-aware with startup and shutdown hooks, automatically bound to the frontend, dependency-injectable, and fully testable in isolation.
## Quick Start
```go
type GreetService struct {
prefix string
}
func NewGreetService(prefix string) *GreetService {
return &GreetService{prefix: prefix}
}
func (g *GreetService) Greet(name string) string {
return g.prefix + name + "!"
}
// Register
app := application.New(application.Options{
Services: []application.Service{
application.NewService(NewGreetService("Hello, ")),
},
})
```
**That's it!** `Greet` is now callable from JavaScript.
## Creating Services
### Basic Service
```go
type CalculatorService struct{}
func (c *CalculatorService) Add(a, b int) int {
return a + b
}
func (c *CalculatorService) Subtract(a, b int) int {
return a - b
}
```
**Register:**
```go
app := application.New(application.Options{
Services: []application.Service{
application.NewService(&CalculatorService{}),
},
})
```
**Key points:**
- Only **exported methods** (PascalCase) are bound
- Services are **singletons** (one instance)
- Methods can return `(value, error)`
### Service with State
```go
type CounterService struct {
count int
mu sync.RWMutex
}
func (c *CounterService) Increment() int {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
return c.count
}
func (c *CounterService) GetCount() int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.count
}
func (c *CounterService) Reset() {
c.mu.Lock()
defer c.mu.Unlock()
c.count = 0
}
```
**Important:** Services are shared across all windows. Always use mutexes for thread safety.
### Service with Dependencies
```go
type UserService struct {
db *sql.DB
logger *slog.Logger
}
func NewUserService(db *sql.DB, logger *slog.Logger) *UserService {
return &UserService{
db: db,
logger: logger,
}
}
func (u *UserService) GetUser(id int) (*User, error) {
u.logger.Info("Getting user", "id", id)
var user User
err := u.db.QueryRow("SELECT * FROM users WHERE id = ?", id).Scan(&user)
if err != nil {
return nil, fmt.Errorf("failed to get user: %w", err)
}
return &user, nil
}
```
**Register with dependencies:**
```go
db, _ := sql.Open("sqlite3", "app.db")
logger := slog.Default()
app := application.New(application.Options{
Services: []application.Service{
application.NewService(NewUserService(db, logger)),
},
})
```
## Service Lifecycle
### ServiceStartup
Called when application starts:
```go
func (u *UserService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
u.logger.Info("UserService starting up")
// Initialise resources
if err := u.db.Ping(); err != nil {
return fmt.Errorf("database not available: %w", err)
}
// Run migrations
if err := u.runMigrations(); err != nil {
return fmt.Errorf("migrations failed: %w", err)
}
// Start background tasks
go u.backgroundSync(ctx)
return nil
}
```
**Use cases:**
- Initialise resources
- Validate configuration
- Run migrations
- Start background tasks
- Connect to external services
**Important:**
- Services start in **registration order**
- Return error to **prevent app startup**
- Use `ctx` for cancellation
### ServiceName
Optional interface to provide a custom name for logging and debugging:
```go
func (u *UserService) ServiceName() string {
return "User Management Service"
}
```
If a non-empty name is provided via `ServiceOptions.Name`, it takes precedence over this method. If neither is provided, the type name is used.
### ServiceShutdown
Called when application shuts down:
```go
func (u *UserService) ServiceShutdown() error {
u.logger.Info("UserService shutting down")
// Close database
if err := u.db.Close(); err != nil {
return fmt.Errorf("failed to close database: %w", err)
}
// Cleanup resources
u.cleanup()
return nil
}
```
**Use cases:**
- Close connections
- Save state
- Cleanup resources
- Flush buffers
- Cancel background tasks
**Important:**
- Services shutdown in **reverse order**
- Application context already cancelled
- Return error to **log warning** (doesn't prevent shutdown)
### Complete Lifecycle Example
```go
type DatabaseService struct {
db *sql.DB
logger *slog.Logger
cancel context.CancelFunc
}
func NewDatabaseService(logger *slog.Logger) *DatabaseService {
return &DatabaseService{logger: logger}
}
func (d *DatabaseService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
d.logger.Info("Starting database service")
// Open database
db, err := sql.Open("sqlite3", "app.db")
if err != nil {
return fmt.Errorf("failed to open database: %w", err)
}
d.db = db
// Test connection
if err := db.Ping(); err != nil {
return fmt.Errorf("database not available: %w", err)
}
// Start background cleanup
ctx, cancel := context.WithCancel(ctx)
d.cancel = cancel
go d.periodicCleanup(ctx)
return nil
}
func (d *DatabaseService) ServiceShutdown() error {
d.logger.Info("Shutting down database service")
// Cancel background tasks
if d.cancel != nil {
d.cancel()
}
// Close database
if d.db != nil {
if err := d.db.Close(); err != nil {
return fmt.Errorf("failed to close database: %w", err)
}
}
return nil
}
func (d *DatabaseService) periodicCleanup(ctx context.Context) {
ticker := time.NewTicker(1 * time.Hour)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
d.cleanup()
}
}
}
```
## Service Options
`ServiceOptions` configures individual service behaviour:
| Field | Type | Description |
|-------|------|-------------|
| `Name` | `string` | Override the service name for logging and debugging. Defaults to `ServiceName()` interface or type name |
| `Route` | `string` | Mount the service as an HTTP handler at this route prefix (service must implement `http.Handler`) |
| `MarshalError` | `func(error) []byte` | Custom error marshalling for this service's methods. Return nil to fall back to the global handler |
### Custom Name
```go
app := application.New(application.Options{
Services: []application.Service{
application.NewServiceWithOptions(&MyService{}, application.ServiceOptions{
Name: "CustomServiceName",
}),
},
})
```
**Use cases:**
- Multiple instances of same service type
- Clearer logging
- Better debugging
### HTTP Routes
Services can handle HTTP requests:
```go
type FileService struct {
root string
}
func (f *FileService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Serve files from root directory
http.FileServer(http.Dir(f.root)).ServeHTTP(w, r)
}
// Register with route
app := application.New(application.Options{
Services: []application.Service{
application.NewServiceWithOptions(&FileService{root: "./files"}, application.ServiceOptions{
Route: "/files",
}),
},
})
```
**Access:** `http://wails.localhost/files/...`
**Use cases:**
- File serving
- Custom APIs
- WebSocket endpoints
- Media streaming
## Service Patterns
### Repository Pattern
```go
type UserRepository struct {
db *sql.DB
}
func (r *UserRepository) GetByID(id int) (*User, error) {
// Database query
}
func (r *UserRepository) Create(user *User) error {
// Insert user
}
func (r *UserRepository) Update(user *User) error {
// Update user
}
func (r *UserRepository) Delete(id int) error {
// Delete user
}
```
### Service Layer Pattern
```go
type UserService struct {
repo *UserRepository
logger *slog.Logger
}
func (s *UserService) RegisterUser(email, password string) (*User, error) {
// Validate
if !isValidEmail(email) {
return nil, errors.New("invalid email")
}
// Hash password
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return nil, err
}
// Create user
user := &User{
Email: email,
PasswordHash: string(hash),
CreatedAt: time.Now(),
}
if err := s.repo.Create(user); err != nil {
return nil, err
}
s.logger.Info("User registered", "email", email)
return user, nil
}
```
### Factory Pattern
```go
type ServiceFactory struct {
db *sql.DB
logger *slog.Logger
}
func (f *ServiceFactory) CreateUserService() *UserService {
return &UserService{
repo: &UserRepository{db: f.db},
logger: f.logger,
}
}
func (f *ServiceFactory) CreateOrderService() *OrderService {
return &OrderService{
repo: &OrderRepository{db: f.db},
logger: f.logger,
}
}
```
### Event-Driven Pattern
```go
type OrderService struct {
app *application.App
}
func (o *OrderService) CreateOrder(items []Item) (*Order, error) {
order := &Order{
Items: items,
CreatedAt: time.Now(),
}
// Save order
if err := o.saveOrder(order); err != nil {
return nil, err
}
// Emit event
o.app.Event.Emit("order-created", order)
return order, nil
}
```
## Dependency Injection
### Constructor Injection
```go
type EmailService struct {
smtp *smtp.Client
logger *slog.Logger
}
func NewEmailService(smtp *smtp.Client, logger *slog.Logger) *EmailService {
return &EmailService{
smtp: smtp,
logger: logger,
}
}
// Register
smtpClient := createSMTPClient()
logger := slog.Default()
app := application.New(application.Options{
Services: []application.Service{
application.NewService(NewEmailService(smtpClient, logger)),
},
})
```
### Application Injection
```go
type NotificationService struct {
app *application.App
}
func NewNotificationService(app *application.App) *NotificationService {
return &NotificationService{app: app}
}
func (n *NotificationService) Notify(message string) {
// Use application to emit events
n.app.Event.Emit("notification", message)
}
// Register after app creation
app := application.New(application.Options{})
app.RegisterService(application.NewService(NewNotificationService(app)))
```
### Service-to-Service Dependencies
```go
type OrderService struct {
userService *UserService
emailService *EmailService
}
func NewOrderService(userService *UserService, emailService *EmailService) *OrderService {
return &OrderService{
userService: userService,
emailService: emailService,
}
}
// Register in order
userService := &UserService{}
emailService := &EmailService{}
orderService := NewOrderService(userService, emailService)
app := application.New(application.Options{
Services: []application.Service{
application.NewService(userService),
application.NewService(emailService),
application.NewService(orderService),
},
})
```
## Testing Services
### Unit Testing
```go
func TestCalculatorService_Add(t *testing.T) {
calc := &CalculatorService{}
result := calc.Add(2, 3)
if result != 5 {
t.Errorf("expected 5, got %d", result)
}
}
```
### Testing with Dependencies
```go
func TestUserService_GetUser(t *testing.T) {
// Create mock database
db, mock, _ := sqlmock.New()
defer db.Close()
// Set expectations
rows := sqlmock.NewRows([]string{"id", "name"}).
AddRow(1, "Alice")
mock.ExpectQuery("SELECT").WillReturnRows(rows)
// Create service
service := NewUserService(db, slog.Default())
// Test
user, err := service.GetUser(1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Name != "Alice" {
t.Errorf("expected Alice, got %s", user.Name)
}
}
```
### Testing Lifecycle
```go
func TestDatabaseService_Lifecycle(t *testing.T) {
service := NewDatabaseService(slog.Default())
// Test startup
ctx := context.Background()
err := service.ServiceStartup(ctx, application.ServiceOptions{})
if err != nil {
t.Fatalf("startup failed: %v", err)
}
// Test functionality
// ...
// Test shutdown
err = service.ServiceShutdown()
if err != nil {
t.Fatalf("shutdown failed: %v", err)
}
}
```
## Best Practices
### ✅ Do
- **Single responsibility** - One service, one purpose
- **Constructor injection** - Pass dependencies explicitly
- **Thread-safe state** - Use mutexes
- **Return errors** - Don't panic
- **Log important events** - Use structured logging
- **Test in isolation** - Mock dependencies
### ❌ Don't
- **Don't use global state** - Pass dependencies
- **Don't block startup** - Keep ServiceStartup fast
- **Don't ignore shutdown** - Always cleanup
- **Don't create circular dependencies** - Design carefully
- **Don't expose internal methods** - Keep them private
- **Don't forget thread safety** - Services are shared
## Complete Example
```go
package main
import (
"context"
"database/sql"
"fmt"
"log/slog"
"sync"
"time"
"github.com/wailsapp/wails/v3/pkg/application"
_ "github.com/mattn/go-sqlite3"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"createdAt"`
}
type UserService struct {
db *sql.DB
logger *slog.Logger
cache map[int]*User
mu sync.RWMutex
}
func NewUserService(logger *slog.Logger) *UserService {
return &UserService{
logger: logger,
cache: make(map[int]*User),
}
}
func (u *UserService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
u.logger.Info("Starting UserService")
// Open database
db, err := sql.Open("sqlite3", "users.db")
if err != nil {
return fmt.Errorf("failed to open database: %w", err)
}
u.db = db
// Create table
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`)
if err != nil {
return fmt.Errorf("failed to create table: %w", err)
}
// Preload cache
if err := u.loadCache(); err != nil {
return fmt.Errorf("failed to load cache: %w", err)
}
return nil
}
func (u *UserService) ServiceShutdown() error {
u.logger.Info("Shutting down UserService")
if u.db != nil {
return u.db.Close()
}
return nil
}
func (u *UserService) GetUser(id int) (*User, error) {
// Check cache first
u.mu.RLock()
if user, ok := u.cache[id]; ok {
u.mu.RUnlock()
return user, nil
}
u.mu.RUnlock()
// Query database
var user User
err := u.db.QueryRow(
"SELECT id, name, email, created_at FROM users WHERE id = ?",
id,
).Scan(&user.ID, &user.Name, &user.Email, &user.CreatedAt)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("user %d not found", id)
}
if err != nil {
return nil, fmt.Errorf("database error: %w", err)
}
// Update cache
u.mu.Lock()
u.cache[id] = &user
u.mu.Unlock()
return &user, nil
}
func (u *UserService) CreateUser(name, email string) (*User, error) {
result, err := u.db.Exec(
"INSERT INTO users (name, email) VALUES (?, ?)",
name, email,
)
if err != nil {
return nil, fmt.Errorf("failed to create user: %w", err)
}
id, _ := result.LastInsertId()
user := &User{
ID: int(id),
Name: name,
Email: email,
CreatedAt: time.Now(),
}
// Update cache
u.mu.Lock()
u.cache[int(id)] = user
u.mu.Unlock()
u.logger.Info("User created", "id", id, "email", email)
return user, nil
}
func (u *UserService) loadCache() error {
rows, err := u.db.Query("SELECT id, name, email, created_at FROM users")
if err != nil {
return err
}
defer rows.Close()
u.mu.Lock()
defer u.mu.Unlock()
for rows.Next() {
var user User
if err := rows.Scan(&user.ID, &user.Name, &user.Email, &user.CreatedAt); err != nil {
return err
}
u.cache[user.ID] = &user
}
return rows.Err()
}
func main() {
app := application.New(application.Options{
Name: "User Management",
Services: []application.Service{
application.NewService(NewUserService(slog.Default())),
},
})
app.Window.New()
app.Run()
}
```
## Next Steps
- [Method Bindings](/features/bindings/methods) - Learn how to bind Go methods to JavaScript
- [Models](/features/bindings/models) - Bind complex data structures
- [Events](/features/events/system) - Use events for pub/sub communication
- [Best Practices](/features/bindings/best-practices) - Service design patterns and best practices
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [service examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).

View file

@ -0,0 +1,511 @@
---
title: Browser Integration
description: Open URLs and files in the user's default web browser
sidebar:
order: 1
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
Wails provides simple browser integration through the BrowserManager API, allowing your application to open URLs and files in the user's default web browser. This is useful for opening external links, documentation, or files that should be handled by the browser.
## Accessing the Browser Manager
The browser manager is accessed through the `Browser` property on your application instance:
```go
app := application.New(application.Options{
Name: "Browser Integration Demo",
})
// Access the browser manager
browser := app.Browser
```
## Opening URLs
### Open Web URLs
Open URLs in the user's default web browser:
```go
// Open a website
err := app.Browser.OpenURL("https://wails.io")
if err != nil {
app.Logger.Error("Failed to open URL", "error", err)
}
// Open specific pages
err = app.Browser.OpenURL("https://github.com/wailsapp/wails")
if err != nil {
app.Logger.Error("Failed to open GitHub", "error", err)
}
```
### Open Local URLs
Open local development servers or local network resources:
```go
// Open local development server
err := app.Browser.OpenURL("http://localhost:3000")
if err != nil {
app.Logger.Error("Failed to open local server", "error", err)
}
// Open network resource
err = app.Browser.OpenURL("http://192.168.1.100:8080")
if err != nil {
app.Logger.Error("Failed to open network resource", "error", err)
}
```
## Opening Files
### Open HTML Files
Open local HTML files in the browser:
```go
// Open an HTML file
err := app.Browser.OpenFile("/path/to/documentation.html")
if err != nil {
app.Logger.Error("Failed to open HTML file", "error", err)
}
// Open generated reports
reportPath := "/tmp/report.html"
err = app.Browser.OpenFile(reportPath)
if err != nil {
app.Logger.Error("Failed to open report", "error", err)
}
```
### Open Other File Types
Open various file types that browsers can handle:
```go
// Open PDF files
err := app.Browser.OpenFile("/path/to/document.pdf")
if err != nil {
app.Logger.Error("Failed to open PDF", "error", err)
}
// Open image files
err = app.Browser.OpenFile("/path/to/image.png")
if err != nil {
app.Logger.Error("Failed to open image", "error", err)
}
// Open text files
err = app.Browser.OpenFile("/path/to/readme.txt")
if err != nil {
app.Logger.Error("Failed to open text file", "error", err)
}
```
## Common Use Cases
### Help and Documentation
Provide easy access to help resources:
```go
// Create help menu
func setupHelpMenu(app *application.App) {
menu := app.Menu.New()
helpMenu := menu.AddSubmenu("Help")
helpMenu.Add("Online Documentation").OnClick(func(ctx *application.Context) {
err := app.Browser.OpenURL("https://docs.yourapp.com")
if err != nil {
app.Dialog.Error().
SetTitle("Error").
SetMessage("Could not open documentation").
Show()
}
})
helpMenu.Add("GitHub Repository").OnClick(func(ctx *application.Context) {
app.Browser.OpenURL("https://github.com/youruser/yourapp")
})
helpMenu.Add("Report Issue").OnClick(func(ctx *application.Context) {
app.Browser.OpenURL("https://github.com/youruser/yourapp/issues/new")
})
}
```
### External Links in Content
Handle external links from your application content:
```go
func handleExternalLink(app *application.App, url string) {
// Validate the URL before opening
if !isValidURL(url) {
app.Logger.Warn("Invalid URL", "url", url)
return
}
// Optionally confirm with user
dialog := app.Dialog.Question()
dialog.SetTitle("Open External Link")
dialog.SetMessage(fmt.Sprintf("Open %s in your browser?", url))
dialog.AddButton("Open").OnClick(func() {
err := app.Browser.OpenURL(url)
if err != nil {
app.Logger.Error("Failed to open URL", "url", url, "error", err)
}
})
dialog.AddButton("Cancel")
dialog.Show()
}
func isValidURL(url string) bool {
parsed, err := url.Parse(url)
return err == nil && (parsed.Scheme == "http" || parsed.Scheme == "https")
}
```
### Export and View Reports
Generate and open reports in the browser:
```go
import (
"html/template"
"os"
"path/filepath"
)
func generateAndOpenReport(app *application.App, data interface{}) error {
// Create temporary file for the report
tmpDir := os.TempDir()
reportPath := filepath.Join(tmpDir, "report.html")
// Generate HTML report
tmpl := `
<!DOCTYPE html>
<html>
<head>
<title>Application Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.header { border-bottom: 2px solid #333; padding-bottom: 10px; }
.data { margin-top: 20px; }
</style>
</head>
<body>
<div class="header">
<h1>Application Report</h1>
<p>Generated: {{.Timestamp}}</p>
</div>
<div class="data">
<!-- Report content here -->
{{range .Items}}
<p>{{.}}</p>
{{end}}
</div>
</body>
</html>`
// Write report to file
file, err := os.Create(reportPath)
if err != nil {
return err
}
defer file.Close()
t, err := template.New("report").Parse(tmpl)
if err != nil {
return err
}
err = t.Execute(file, data)
if err != nil {
return err
}
// Open in browser
return app.Browser.OpenFile(reportPath)
}
```
### Development Tools
Open development resources during development:
```go
func setupDevelopmentMenu(app *application.App) {
if !app.Env.Info().Debug {
return // Only show in debug mode
}
menu := app.Menu.New()
devMenu := menu.AddSubmenu("Development")
devMenu.Add("Open DevTools").OnClick(func(ctx *application.Context) {
// This would open browser devtools if available
window := app.Window.Current()
if window != nil {
window.OpenDevTools()
}
})
devMenu.Add("View Source").OnClick(func(ctx *application.Context) {
// Open source code repository
app.Browser.OpenURL("https://github.com/youruser/yourapp")
})
devMenu.Add("API Documentation").OnClick(func(ctx *application.Context) {
// Open local API docs
app.Browser.OpenURL("http://localhost:8080/docs")
})
}
```
## Error Handling
### Graceful Error Handling
Always handle potential errors when opening URLs or files:
```go
func openURLWithFallback(app *application.App, url string, fallbackMessage string) {
err := app.Browser.OpenURL(url)
if err != nil {
app.Logger.Error("Failed to open URL", "url", url, "error", err)
// Show fallback dialog with URL
dialog := app.Dialog.Info()
dialog.SetTitle("Unable to Open Link")
dialog.SetMessage(fmt.Sprintf("%s\n\nURL: %s", fallbackMessage, url))
dialog.Show()
}
}
// Usage
openURLWithFallback(app,
"https://docs.example.com",
"Please open the following URL manually in your browser:")
```
### User Feedback
Provide feedback when operations succeed or fail:
```go
func openURLWithFeedback(app *application.App, url string) {
err := app.Browser.OpenURL(url)
if err != nil {
// Show error dialog
app.Dialog.Error().
SetTitle("Browser Error").
SetMessage(fmt.Sprintf("Could not open URL: %s", err.Error())).
Show()
} else {
// Optionally show success notification
app.Logger.Info("URL opened successfully", "url", url)
}
}
```
## Platform Considerations
<Tabs>
<TabItem label="macOS" icon="fa-brands:apple">
On macOS:
- Uses the `open` command to launch the default browser
- Respects user's default browser setting in System Preferences
- May prompt for permission if the application is sandboxed
- Handles `file://` URLs correctly for local files
</TabItem>
<TabItem label="Windows" icon="fa-brands:windows">
On Windows:
- Uses Windows Shell API to open URLs
- Respects default browser setting in Windows Settings
- Handles Windows path formats correctly
- May show security warnings for untrusted URLs
</TabItem>
<TabItem label="Linux" icon="fa-brands:linux">
On Linux:
- Attempts to use `xdg-open` first, falls back to other methods
- Behavior varies by desktop environment
- Respects `BROWSER` environment variable if set
- May require additional packages in minimal installations
</TabItem>
</Tabs>
## Best Practices
1. **Always Handle Errors**: Browser operations can fail for various reasons:
```go
if err := app.Browser.OpenURL(url); err != nil {
app.Logger.Error("Failed to open browser", "error", err)
// Provide fallback or user notification
}
```
2. **Validate URLs**: Ensure URLs are well-formed before opening:
```go
func isValidHTTPURL(str string) bool {
u, err := url.Parse(str)
return err == nil && (u.Scheme == "http" || u.Scheme == "https")
}
```
3. **User Confirmation**: For external links, consider asking user permission:
```go
// Show confirmation dialog before opening external links
confirmAndOpen(app, "https://external-site.com")
```
4. **Secure File Paths**: When opening files, ensure paths are safe:
```go
func openSafeFile(app *application.App, filename string) error {
// Ensure file exists and is readable
if _, err := os.Stat(filename); err != nil {
return err
}
return app.Browser.OpenFile(filename)
}
```
## Complete Example
Here's a complete example showing various browser integration patterns:
```go
package main
import (
"fmt"
"os"
"path/filepath"
"github.com/wailsapp/wails/v3/pkg/application"
)
func main() {
app := application.New(application.Options{
Name: "Browser Integration Demo",
})
// Setup menu with browser actions
setupMenu(app)
// Create main window
window := app.Window.New()
window.SetTitle("Browser Integration")
err := app.Run()
if err != nil {
panic(err)
}
}
func setupMenu(app *application.App) {
menu := app.Menu.New()
// File menu
fileMenu := menu.AddSubmenu("File")
fileMenu.Add("Generate Report").OnClick(func(ctx *application.Context) {
generateHTMLReport(app)
})
// Help menu
helpMenu := menu.AddSubmenu("Help")
helpMenu.Add("Documentation").OnClick(func(ctx *application.Context) {
openWithConfirmation(app, "https://docs.example.com")
})
helpMenu.Add("Support").OnClick(func(ctx *application.Context) {
openWithConfirmation(app, "https://support.example.com")
})
app.Menu.Set(menu)
}
func openWithConfirmation(app *application.App, url string) {
dialog := app.Dialog.Question()
dialog.SetTitle("Open External Link")
dialog.SetMessage(fmt.Sprintf("Open %s in your browser?", url))
dialog.AddButton("Open").OnClick(func() {
if err := app.Browser.OpenURL(url); err != nil {
showError(app, "Failed to open URL", err)
}
})
dialog.AddButton("Cancel")
dialog.Show()
}
func generateHTMLReport(app *application.App) {
// Create temporary HTML file
tmpDir := os.TempDir()
reportPath := filepath.Join(tmpDir, "demo_report.html")
html := `
<!DOCTYPE html>
<html>
<head>
<title>Demo Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.header { color: #333; border-bottom: 1px solid #ccc; }
</style>
</head>
<body>
<div class="header">
<h1>Application Report</h1>
<p>This is a sample report generated by the application.</p>
</div>
<div class="content">
<h2>Report Details</h2>
<p>This report was generated to demonstrate browser integration.</p>
</div>
</body>
</html>`
err := os.WriteFile(reportPath, []byte(html), 0644)
if err != nil {
showError(app, "Failed to create report", err)
return
}
// Open in browser
err = app.Browser.OpenFile(reportPath)
if err != nil {
showError(app, "Failed to open report", err)
}
}
func showError(app *application.App, message string, err error) {
app.Dialog.Error().
SetTitle("Error").
SetMessage(fmt.Sprintf("%s: %v", message, err)).
Show()
}
```
:::tip[Pro Tip]
Consider providing fallback mechanisms for when browser operations fail, such as copying URLs to clipboard or showing them in a dialog for manual opening.
:::
:::danger[Warning]
Always validate URLs and file paths before opening them to prevent security issues. Be cautious about opening user-provided URLs without validation.
:::

View file

@ -0,0 +1,493 @@
---
title: Clipboard Operations
description: Copy and paste text with the system clipboard
sidebar:
order: 1
---
import { Card, CardGrid } from "@astrojs/starlight/components";
## Clipboard Operations
Wails provides a **unified clipboard API** that works across all platforms. Copy and paste text with simple, consistent methods on Windows, macOS, and Linux.
## Quick Start
```go
// Copy text to clipboard
app.Clipboard.SetText("Hello, World!")
// Get text from clipboard
text, ok := app.Clipboard.Text()
if ok {
fmt.Println("Clipboard:", text)
}
```
**That's it!** Cross-platform clipboard access.
## Copying Text
### Basic Copy
```go
success := app.Clipboard.SetText("Text to copy")
if !success {
app.Logger.Error("Failed to copy to clipboard")
}
```
**Returns:** `bool` - `true` if successful, `false` otherwise
### Copy from Service
```go
type ClipboardService struct {
app *application.App
}
func (c *ClipboardService) CopyToClipboard(text string) bool {
return c.app.Clipboard.SetText(text)
}
```
**Call from JavaScript:**
```javascript
import { CopyToClipboard } from './bindings/myapp/clipboardservice'
await CopyToClipboard("Text to copy")
```
### Copy with Feedback
```go
func copyWithFeedback(text string) {
if app.Clipboard.SetText(text) {
app.Dialog.Info().
SetTitle("Copied").
SetMessage("Text copied to clipboard!").
Show()
} else {
app.Dialog.Error().
SetTitle("Copy Failed").
SetMessage("Failed to copy to clipboard.").
Show()
}
}
```
## Pasting Text
### Basic Paste
```go
text, ok := app.Clipboard.Text()
if !ok {
app.Logger.Error("Failed to read clipboard")
return
}
fmt.Println("Clipboard text:", text)
```
**Returns:** `(string, bool)` - Text and success flag
### Paste from Service
```go
func (c *ClipboardService) PasteFromClipboard() string {
text, ok := c.app.Clipboard.Text()
if !ok {
return ""
}
return text
}
```
**Call from JavaScript:**
```javascript
import { PasteFromClipboard } from './bindings/myapp/clipboardservice'
const text = await PasteFromClipboard()
console.log("Pasted:", text)
```
### Paste with Validation
```go
func pasteText() (string, error) {
text, ok := app.Clipboard.Text()
if !ok {
return "", errors.New("clipboard empty or unavailable")
}
// Validate
if len(text) == 0 {
return "", errors.New("clipboard is empty")
}
if len(text) > 10000 {
return "", errors.New("clipboard text too large")
}
return text, nil
}
```
## Complete Examples
### Copy Button
**Go:**
```go
type TextService struct {
app *application.App
}
func (t *TextService) CopyText(text string) error {
if !t.app.Clipboard.SetText(text) {
return errors.New("failed to copy")
}
return nil
}
```
**JavaScript:**
```javascript
import { CopyText } from './bindings/myapp/textservice'
async function copyToClipboard(text) {
try {
await CopyText(text)
showNotification("Copied to clipboard!")
} catch (error) {
showError("Failed to copy: " + error)
}
}
// Usage
document.getElementById('copy-btn').addEventListener('click', () => {
const text = document.getElementById('text').value
copyToClipboard(text)
})
```
### Paste and Process
**Go:**
```go
type DataService struct {
app *application.App
}
func (d *DataService) PasteAndProcess() (string, error) {
// Get clipboard text
text, ok := d.app.Clipboard.Text()
if !ok {
return "", errors.New("clipboard unavailable")
}
// Process text
processed := strings.TrimSpace(text)
processed = strings.ToUpper(processed)
return processed, nil
}
```
**JavaScript:**
```javascript
import { PasteAndProcess } from './bindings/myapp/dataservice'
async function pasteAndProcess() {
try {
const result = await PasteAndProcess()
document.getElementById('output').value = result
} catch (error) {
showError("Failed to paste: " + error)
}
}
```
### Copy Multiple Formats
```go
type CopyService struct {
app *application.App
}
func (c *CopyService) CopyAsPlainText(text string) bool {
return c.app.Clipboard.SetText(text)
}
func (c *CopyService) CopyAsJSON(data interface{}) bool {
jsonBytes, err := json.MarshalIndent(data, "", " ")
if err != nil {
return false
}
return c.app.Clipboard.SetText(string(jsonBytes))
}
func (c *CopyService) CopyAsCSV(rows [][]string) bool {
var buf bytes.Buffer
writer := csv.NewWriter(&buf)
for _, row := range rows {
if err := writer.Write(row); err != nil {
return false
}
}
writer.Flush()
return c.app.Clipboard.SetText(buf.String())
}
```
### Clipboard Monitor
```go
type ClipboardMonitor struct {
app *application.App
lastText string
ticker *time.Ticker
stopChan chan bool
}
func NewClipboardMonitor(app *application.App) *ClipboardMonitor {
return &ClipboardMonitor{
app: app,
stopChan: make(chan bool),
}
}
func (cm *ClipboardMonitor) Start() {
cm.ticker = time.NewTicker(1 * time.Second)
go func() {
for {
select {
case <-cm.ticker.C:
cm.checkClipboard()
case <-cm.stopChan:
return
}
}
}()
}
func (cm *ClipboardMonitor) Stop() {
if cm.ticker != nil {
cm.ticker.Stop()
}
cm.stopChan <- true
}
func (cm *ClipboardMonitor) checkClipboard() {
text, ok := cm.app.Clipboard.Text()
if !ok {
return
}
if text != cm.lastText {
cm.lastText = text
cm.app.Event.Emit("clipboard-changed", text)
}
}
```
### Copy with History
```go
type ClipboardHistory struct {
app *application.App
history []string
maxSize int
}
func NewClipboardHistory(app *application.App) *ClipboardHistory {
return &ClipboardHistory{
app: app,
history: make([]string, 0),
maxSize: 10,
}
}
func (ch *ClipboardHistory) Copy(text string) bool {
if !ch.app.Clipboard.SetText(text) {
return false
}
// Add to history
ch.history = append([]string{text}, ch.history...)
// Limit size
if len(ch.history) > ch.maxSize {
ch.history = ch.history[:ch.maxSize]
}
return true
}
func (ch *ClipboardHistory) GetHistory() []string {
return ch.history
}
func (ch *ClipboardHistory) RestoreFromHistory(index int) bool {
if index < 0 || index >= len(ch.history) {
return false
}
return ch.app.Clipboard.SetText(ch.history[index])
}
```
## Frontend Integration
### Using Browser Clipboard API
For simple text, you can use the browser's clipboard API:
```javascript
// Copy
async function copyText(text) {
try {
await navigator.clipboard.writeText(text)
console.log("Copied!")
} catch (error) {
console.error("Copy failed:", error)
}
}
// Paste
async function pasteText() {
try {
const text = await navigator.clipboard.readText()
return text
} catch (error) {
console.error("Paste failed:", error)
return ""
}
}
```
**Note:** Browser clipboard API requires HTTPS or localhost, and user permission.
### Using Wails Clipboard
For system-wide clipboard access:
```javascript
import { CopyToClipboard, PasteFromClipboard } from './bindings/myapp/clipboardservice'
// Copy
async function copy(text) {
const success = await CopyToClipboard(text)
if (success) {
console.log("Copied!")
}
}
// Paste
async function paste() {
const text = await PasteFromClipboard()
return text
}
```
## Best Practices
### ✅ Do
- **Check return values** - Handle failures gracefully
- **Provide feedback** - Let users know copy succeeded
- **Validate pasted text** - Check format and size
- **Use appropriate method** - Browser API vs Wails API
- **Handle empty clipboard** - Check before using
- **Trim whitespace** - Clean pasted text
### ❌ Don't
- **Don't ignore failures** - Always check success
- **Don't copy sensitive data** - Clipboard is shared
- **Don't assume format** - Validate pasted data
- **Don't poll too frequently** - If monitoring clipboard
- **Don't copy large data** - Use files instead
- **Don't forget security** - Sanitise pasted content
## Platform Differences
### macOS
- Uses NSPasteboard
- Supports rich text (future)
- System-wide clipboard
- Clipboard history (system feature)
### Windows
- Uses Windows Clipboard API
- Supports multiple formats (future)
- System-wide clipboard
- Clipboard history (Windows 10+)
### Linux
- Uses X11/Wayland clipboard
- Primary and clipboard selections
- Varies by desktop environment
- May require clipboard manager
## Limitations
### Current Version
- **Text only** - Images not yet supported
- **No format detection** - Plain text only
- **No clipboard events** - Must poll for changes
- **No clipboard history** - Implement yourself
### Future Features
- Image support
- Rich text support
- Multiple formats
- Clipboard change events
- Clipboard history API
## Next Steps
<CardGrid>
<Card title="Bindings" icon="rocket">
Call Go functions from JavaScript.
[Learn More →](/features/bindings/methods)
</Card>
<Card title="Events" icon="star">
Use events for clipboard notifications.
[Learn More →](/features/events/system)
</Card>
<Card title="dialogs" icon="information">
Show copy/paste feedback.
[Learn More →](/features/dialogs/message)
</Card>
<Card title="Services" icon="puzzle">
Organise clipboard code.
[Learn More →](/features/bindings/services)
</Card>
</CardGrid>
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [clipboard examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).

View file

@ -0,0 +1,626 @@
---
title: Custom dialogs
sidebar:
order: 4
---
import { Card, CardGrid } from "@astrojs/starlight/components";
## Custom dialogs
Create **custom dialog windows** using regular Wails windows with dialog-like behaviour. Build custom forms, complex input validation, branded appearance, and rich content (images, videos) whilst maintaining familiar dialog patterns.
## Quick Start
```go
// Create custom dialog window
dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Custom dialog",
Width: 400,
Height: 300,
AlwaysOnTop: true,
Frameless: true,
Hidden: true,
})
// Load custom UI
dialog.SetURL("http://wails.localhost/dialog.html")
// Show as modal
dialog.Show()
dialog.Focus()
```
**That's it!** Custom UI with dialog behaviour.
## Creating Custom dialogs
### Basic Custom dialog
```go
type Customdialog struct {
window *application.WebviewWindow
result chan string
}
func NewCustomdialog(app *application.App) *Customdialog {
dialog := &Customdialog{
result: make(chan string, 1),
}
dialog.window = app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Custom dialog",
Width: 400,
Height: 300,
AlwaysOnTop: true,
Resizable: false,
Hidden: true,
})
return dialog
}
func (d *Customdialog) Show() string {
d.window.Show()
d.window.Focus()
// Wait for result
return <-d.result
}
func (d *Customdialog) Close(result string) {
d.result <- result
d.window.Close()
}
```
### Modal dialog
```go
func ShowModaldialog(parent *application.WebviewWindow, title string) string {
// Create dialog
dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: title,
Width: 400,
Height: 200,
Parent: parent,
AlwaysOnTop: true,
Resizable: false,
})
// Disable parent
parent.SetEnabled(false)
// Re-enable parent on close
dialog.OnClose(func() bool {
parent.SetEnabled(true)
parent.Focus()
return true
})
dialog.Show()
return waitForResult(dialog)
}
```
### Form dialog
```go
type Formdialog struct {
window *application.WebviewWindow
data map[string]interface{}
done chan bool
}
func NewFormdialog(app *application.App) *Formdialog {
fd := &Formdialog{
data: make(map[string]interface{}),
done: make(chan bool, 1),
}
fd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Enter Information",
Width: 500,
Height: 400,
Frameless: true,
Hidden: true,
})
return fd
}
func (fd *Formdialog) Show() (map[string]interface{}, bool) {
fd.window.Show()
fd.window.Focus()
ok := <-fd.done
return fd.data, ok
}
func (fd *Formdialog) Submit(data map[string]interface{}) {
fd.data = data
fd.done <- true
fd.window.Close()
}
func (fd *Formdialog) Cancel() {
fd.done <- false
fd.window.Close()
}
```
## dialog Patterns
### Confirmation dialog
```go
func ShowConfirmdialog(message string) bool {
dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Confirm",
Width: 400,
Height: 150,
AlwaysOnTop: true,
Frameless: true,
})
// Pass message to dialog
dialog.OnReady(func() {
dialog.EmitEvent("set-message", message)
})
result := make(chan bool, 1)
// Handle responses
app.Event.On("confirm-yes", func(e *application.CustomEvent) {
result <- true
dialog.Close()
})
app.Event.On("confirm-no", func(e *application.CustomEvent) {
result <- false
dialog.Close()
})
dialog.Show()
return <-result
}
```
**Frontend (HTML/JS):**
```html
<div class="dialog">
<h2 id="message"></h2>
<div class="buttons">
<button onclick="confirm(true)">Yes</button>
<button onclick="confirm(false)">No</button>
</div>
</div>
<script>
import { Events } from '@wailsio/runtime'
Events.On("set-message", (message) => {
document.getElementById("message").textContent = message
})
function confirm(result) {
Events.Emit(result ? "confirm-yes" : "confirm-no")
}
</script>
```
### Input dialog
```go
func ShowInputdialog(prompt string, defaultValue string) (string, bool) {
dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Input",
Width: 400,
Height: 150,
Frameless: true,
})
result := make(chan struct {
value string
ok bool
}, 1)
dialog.OnReady(func() {
dialog.EmitEvent("set-prompt", map[string]string{
"prompt": prompt,
"default": defaultValue,
})
})
app.Event.On("input-submit", func(e *application.CustomEvent) {
result <- struct {
value string
ok bool
}{e.Data.(string), true}
dialog.Close()
})
app.Event.On("input-cancel", func(e *application.CustomEvent) {
result <- struct {
value string
ok bool
}{"", false}
dialog.Close()
})
dialog.Show()
r := <-result
return r.value, r.ok
}
```
### Progress dialog
```go
type Progressdialog struct {
window *application.WebviewWindow
}
func NewProgressdialog(title string) *Progressdialog {
pd := &Progressdialog{}
pd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: title,
Width: 400,
Height: 150,
Frameless: true,
})
return pd
}
func (pd *Progressdialog) Show() {
pd.window.Show()
}
func (pd *Progressdialog) UpdateProgress(current, total int, message string) {
pd.window.EmitEvent("progress-update", map[string]interface{}{
"current": current,
"total": total,
"message": message,
})
}
func (pd *Progressdialog) Close() {
pd.window.Close()
}
```
**Usage:**
```go
func processFiles(files []string) {
progress := NewProgressdialog("Processing Files")
progress.Show()
for i, file := range files {
progress.UpdateProgress(i+1, len(files),
fmt.Sprintf("Processing %s...", filepath.Base(file)))
processFile(file)
}
progress.Close()
}
```
## Complete Examples
### Login dialog
**Go:**
```go
type Logindialog struct {
window *application.WebviewWindow
result chan struct {
username string
password string
ok bool
}
}
func NewLogindialog(app *application.App) *Logindialog {
ld := &Logindialog{
result: make(chan struct {
username string
password string
ok bool
}, 1),
}
ld.window = app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Login",
Width: 400,
Height: 250,
Frameless: true,
})
return ld
}
func (ld *Logindialog) Show() (string, string, bool) {
ld.window.Show()
ld.window.Focus()
r := <-ld.result
return r.username, r.password, r.ok
}
func (ld *Logindialog) Submit(username, password string) {
ld.result <- struct {
username string
password string
ok bool
}{username, password, true}
ld.window.Close()
}
func (ld *Logindialog) Cancel() {
ld.result <- struct {
username string
password string
ok bool
}{"", "", false}
ld.window.Close()
}
```
**Frontend:**
```html
<div class="login-dialog">
<h2>Login</h2>
<form id="login-form">
<input type="text" id="username" placeholder="Username" required>
<input type="password" id="password" placeholder="Password" required>
<div class="buttons">
<button type="submit">Login</button>
<button type="button" onclick="cancel()">Cancel</button>
</div>
</form>
</div>
<script>
import { Emit } from '@wailsio/runtime'
document.getElementById('login-form').addEventListener('submit', (e) => {
e.preventDefault()
const username = document.getElementById('username').value
const password = document.getElementById('password').value
Emit('login-submit', { username, password })
})
function cancel() {
Emit('login-cancel')
}
</script>
```
### Settings dialog
**Go:**
```go
type Settingsdialog struct {
window *application.WebviewWindow
settings map[string]interface{}
done chan bool
}
func NewSettingsdialog(app *application.App, current map[string]interface{}) *Settingsdialog {
sd := &Settingsdialog{
settings: current,
done: make(chan bool, 1),
}
sd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Settings",
Width: 600,
Height: 500,
})
sd.window.OnReady(func() {
sd.window.EmitEvent("load-settings", current)
})
return sd
}
func (sd *Settingsdialog) Show() (map[string]interface{}, bool) {
sd.window.Show()
ok := <-sd.done
return sd.settings, ok
}
func (sd *Settingsdialog) Save(settings map[string]interface{}) {
sd.settings = settings
sd.done <- true
sd.window.Close()
}
func (sd *Settingsdialog) Cancel() {
sd.done <- false
sd.window.Close()
}
```
### Wizard dialog
```go
type Wizarddialog struct {
window *application.WebviewWindow
currentStep int
data map[string]interface{}
done chan bool
}
func NewWizarddialog(app *application.App) *Wizarddialog {
wd := &Wizarddialog{
currentStep: 0,
data: make(map[string]interface{}),
done: make(chan bool, 1),
}
wd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Setup Wizard",
Width: 600,
Height: 400,
Resizable: false,
})
return wd
}
func (wd *Wizarddialog) Show() (map[string]interface{}, bool) {
wd.window.Show()
ok := <-wd.done
return wd.data, ok
}
func (wd *Wizarddialog) NextStep(stepData map[string]interface{}) {
// Merge step data
for k, v := range stepData {
wd.data[k] = v
}
wd.currentStep++
wd.window.EmitEvent("next-step", wd.currentStep)
}
func (wd *Wizarddialog) PreviousStep() {
if wd.currentStep > 0 {
wd.currentStep--
wd.window.EmitEvent("previous-step", wd.currentStep)
}
}
func (wd *Wizarddialog) Finish(finalData map[string]interface{}) {
for k, v := range finalData {
wd.data[k] = v
}
wd.done <- true
wd.window.Close()
}
func (wd *Wizarddialog) Cancel() {
wd.done <- false
wd.window.Close()
}
```
## Best Practices
### Do
- **Use appropriate window options** - AlwaysOnTop, Frameless, etc.
- **Handle cancellation** - Always provide a way to cancel
- **Validate input** - Check data before accepting
- **Provide feedback** - Loading states, errors
- **Use events for communication** - Clean separation
- **Clean up resources** - Close windows, remove listeners
### Don't
- **Don't block the main thread** - Use channels for results
- **Don't forget to close** - Memory leaks
- **Don't skip validation** - Always validate input
- **Don't ignore errors** - Handle all error cases
- **Don't make it too complex** - Keep dialogs simple
- **Don't forget accessibility** - Keyboard navigation
## Styling Custom dialogs
### Modern dialog Style
```css
.dialog {
display: flex;
flex-direction: column;
height: 100vh;
background: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.dialog-header {
--wails-draggable: drag;
padding: 16px;
background: #f5f5f5;
border-bottom: 1px solid #e0e0e0;
}
.dialog-content {
flex: 1;
padding: 24px;
overflow: auto;
}
.dialog-footer {
padding: 16px;
background: #f5f5f5;
border-top: 1px solid #e0e0e0;
display: flex;
justify-content: flex-end;
gap: 8px;
}
button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
button.primary {
background: #007aff;
color: white;
}
button.secondary {
background: #e0e0e0;
color: #333;
}
```
## Next Steps
<CardGrid>
<Card title="Message dialogs" icon="information">
Standard info, warning, error dialogs.
[Learn More →](/features/dialogs/message)
</Card>
<Card title="File dialogs" icon="document">
Open, save, folder selection.
[Learn More →](/features/dialogs/file)
</Card>
<Card title="Windows" icon="laptop">
Learn about window management.
[Learn More →](/features/windows/basics)
</Card>
<Card title="Events" icon="star">
Use events for dialog communication.
[Learn More →](/features/events/system)
</Card>
</CardGrid>
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [custom dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs).

View file

@ -0,0 +1,591 @@
---
title: File dialogs
description: Open, save, and folder selection dialogs
sidebar:
order: 3
---
import { Card, CardGrid } from "@astrojs/starlight/components";
## File dialogs
Wails provides **native file dialogs** with platform-appropriate appearance for opening files, saving files, and selecting folders. Simple API with file type filtering, multiple selection support, and default locations.
## Open File dialog
Select files to open:
```go
path, err := app.Dialog.OpenFile().
SetTitle("Select Image").
AddFilter("Images", "*.png;*.jpg;*.gif").
AddFilter("All Files", "*.*").
PromptForSingleSelection()
if err != nil {
return
}
openFile(path)
```
**Use cases:**
- Open documents
- Import files
- Load images
- Select configuration files
### Single File Selection
```go
path, err := app.Dialog.OpenFile().
SetTitle("Open Document").
AddFilter("Text Files", "*.txt").
AddFilter("All Files", "*.*").
PromptForSingleSelection()
if err != nil {
// User cancelled or error occurred
return
}
// Use selected file
data, _ := os.ReadFile(path)
```
### Multiple File Selection
```go
paths, err := app.Dialog.OpenFile().
SetTitle("Select Images").
AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif").
PromptForMultipleSelection()
if err != nil {
return
}
// Process all selected files
for _, path := range paths {
processFile(path)
}
```
### With Default Directory
```go
path, err := app.Dialog.OpenFile().
SetTitle("Open File").
SetDirectory("/Users/me/Documents").
PromptForSingleSelection()
```
## Save File dialog
Choose where to save:
```go
path, err := app.Dialog.SaveFile().
SetTitle("Save Document").
SetFilename("document.txt").
AddFilter("Text Files", "*.txt").
AddFilter("All Files", "*.*").
PromptForSingleSelection()
if err != nil {
return
}
saveFile(path, data)
```
**Use cases:**
- Save documents
- Export data
- Create new files
- Save as...
### With Default Filename
```go
path, err := app.Dialog.SaveFile().
SetTitle("Export Data").
SetFilename("export.csv").
AddFilter("CSV Files", "*.csv").
PromptForSingleSelection()
```
### With Default Directory
```go
path, err := app.Dialog.SaveFile().
SetTitle("Save File").
SetDirectory("/Users/me/Documents").
SetFilename("untitled.txt").
PromptForSingleSelection()
```
### Overwrite Confirmation
```go
path, err := app.Dialog.SaveFile().
SetTitle("Save File").
SetFilename("document.txt").
PromptForSingleSelection()
if err != nil {
return
}
// Check if file exists
if _, err := os.Stat(path); err == nil {
dialog := app.Dialog.Question().
SetTitle("Confirm Overwrite").
SetMessage("File already exists. Overwrite?")
overwriteBtn := dialog.AddButton("Overwrite")
cancelBtn := dialog.AddButton("Cancel")
cancelBtn.SetAsDefault()
cancelBtn.SetAsCancel()
overwriteBtn.OnClick(func() {
saveFile(path, data)
})
dialog.Show()
return
}
saveFile(path, data)
```
## Select Folder dialog
Choose a directory:
```go
path, err := app.Dialog.OpenFile().
CanChooseDirectories(true).
CanChooseFiles(false).
SetTitle("Select Output Folder").
PromptForSingleSelection()
if err != nil {
return
}
exportToFolder(path)
```
**Use cases:**
- Choose output directory
- Select workspace
- Pick backup location
- Choose installation directory
### With Default Directory
```go
path, err := app.Dialog.OpenFile().
CanChooseDirectories(true).
CanChooseFiles(false).
SetTitle("Select Folder").
SetDirectory("/Users/me/Documents").
PromptForSingleSelection()
```
## File Filters
### Basic Filters
```go
path, _ := app.Dialog.OpenFile().
AddFilter("Text Files", "*.txt").
AddFilter("All Files", "*.*").
PromptForSingleSelection()
```
### Multiple Extensions
```go
path, _ := app.Dialog.OpenFile().
AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif;*.bmp").
AddFilter("Documents", "*.txt;*.doc;*.docx;*.pdf").
AddFilter("All Files", "*.*").
PromptForSingleSelection()
```
### Platform-Specific Patterns
**Windows:**
```go
// Semicolon-separated
// Pattern: "*.png;*.jpg;*.gif"
```
**macOS/Linux:**
```go
// Space or comma-separated (both work)
// Pattern: "*.png *.jpg *.gif"
// or
// Pattern: "*.png,*.jpg,*.gif"
```
**Cross-platform (recommended):**
```go
// Use semicolons (works on all platforms)
// Pattern: "*.png;*.jpg;*.gif"
```
## Complete Examples
### Open Image File
```go
func openImage() (image.Image, error) {
path, err := app.Dialog.OpenFile().
SetTitle("Select Image").
AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif;*.bmp").
PromptForSingleSelection()
if err != nil {
return nil, err
}
// Open and decode image
file, err := os.Open(path)
if err != nil {
app.Dialog.Error().
SetTitle("Open Failed").
SetMessage(err.Error()).
Show()
return nil, err
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
app.Dialog.Error().
SetTitle("Invalid Image").
SetMessage("Could not decode image file.").
Show()
return nil, err
}
return img, nil
}
```
### Save Document with Validation
```go
func saveDocument(content string) error {
path, err := app.Dialog.SaveFile().
SetTitle("Save Document").
SetFilename("document.txt").
AddFilter("Text Files", "*.txt").
AddFilter("Markdown Files", "*.md").
AddFilter("All Files", "*.*").
PromptForSingleSelection()
if err != nil {
return err
}
// Validate extension
ext := filepath.Ext(path)
if ext != ".txt" && ext != ".md" {
dialog := app.Dialog.Question().
SetTitle("Confirm Extension").
SetMessage(fmt.Sprintf("Save as %s file?", ext))
saveBtn := dialog.AddButton("Save")
cancelBtn := dialog.AddButton("Cancel")
cancelBtn.SetAsCancel()
confirmed := make(chan bool, 1)
saveBtn.OnClick(func() {
confirmed <- true
})
cancelBtn.OnClick(func() {
confirmed <- false
})
dialog.Show()
if !<-confirmed {
return errors.New("cancelled")
}
}
// Save file
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
app.Dialog.Error().
SetTitle("Save Failed").
SetMessage(err.Error()).
Show()
return err
}
app.Dialog.Info().
SetTitle("Saved").
SetMessage("Document saved successfully!").
Show()
return nil
}
```
### Batch File Processing
```go
func processMultipleFiles() {
paths, err := app.Dialog.OpenFile().
SetTitle("Select Files to Process").
AddFilter("Images", "*.png;*.jpg").
PromptForMultipleSelection()
if err != nil || len(paths) == 0 {
return
}
// Confirm processing
dialog := app.Dialog.Question().
SetTitle("Confirm Processing").
SetMessage(fmt.Sprintf("Process %d file(s)?", len(paths)))
processBtn := dialog.AddButton("Process")
cancelBtn := dialog.AddButton("Cancel")
cancelBtn.SetAsCancel()
processBtn.OnClick(func() {
// Process files
var errs []error
for i, path := range paths {
if err := processFile(path); err != nil {
errs = append(errs, err)
}
// Update progress
app.Event.Emit("progress", map[string]interface{}{
"current": i + 1,
"total": len(paths),
})
}
// Show results
if len(errs) > 0 {
app.Dialog.Warning().
SetTitle("Processing Complete").
SetMessage(fmt.Sprintf("Processed %d files with %d errors.",
len(paths), len(errs))).
Show()
} else {
app.Dialog.Info().
SetTitle("Success").
SetMessage(fmt.Sprintf("Processed %d files successfully!", len(paths))).
Show()
}
})
dialog.Show()
}
```
### Export with Folder Selection
```go
func exportData(data []byte) {
// Select output folder
folder, err := app.Dialog.OpenFile().
CanChooseDirectories(true).
CanChooseFiles(false).
SetTitle("Select Export Folder").
SetDirectory(getDefaultExportFolder()).
PromptForSingleSelection()
if err != nil {
return
}
// Generate filename
filename := fmt.Sprintf("export_%s.csv",
time.Now().Format("2006-01-02_15-04-05"))
path := filepath.Join(folder, filename)
// Save file
if err := os.WriteFile(path, data, 0644); err != nil {
app.Dialog.Error().
SetTitle("Export Failed").
SetMessage(err.Error()).
Show()
return
}
// Show success with option to open folder
dialog := app.Dialog.Question().
SetTitle("Export Complete").
SetMessage(fmt.Sprintf("Exported to %s", filename))
openFolderBtn := dialog.AddButton("Open Folder")
okBtn := dialog.AddButton("OK")
okBtn.SetAsDefault()
openFolderBtn.OnClick(func() {
openFolder(folder)
})
dialog.Show()
}
```
### Import with Validation
```go
func importConfiguration() error {
path, err := app.Dialog.OpenFile().
SetTitle("Import Configuration").
AddFilter("JSON Files", "*.json").
AddFilter("YAML Files", "*.yaml;*.yml").
PromptForSingleSelection()
if err != nil {
return err
}
// Read file
data, err := os.ReadFile(path)
if err != nil {
app.Dialog.Error().
SetTitle("Read Failed").
SetMessage(err.Error()).
Show()
return err
}
// Validate configuration
config, err := parseConfig(data)
if err != nil {
app.Dialog.Error().
SetTitle("Invalid Configuration").
SetMessage("File is not a valid configuration.").
Show()
return err
}
// Confirm import
dialog := app.Dialog.Question().
SetTitle("Confirm Import").
SetMessage("Import this configuration?")
importBtn := dialog.AddButton("Import")
cancelBtn := dialog.AddButton("Cancel")
cancelBtn.SetAsCancel()
importResult := make(chan bool, 1)
importBtn.OnClick(func() {
importResult <- true
})
cancelBtn.OnClick(func() {
importResult <- false
})
dialog.Show()
if !<-importResult {
return errors.New("cancelled")
}
// Apply configuration
if err := applyConfig(config); err != nil {
app.Dialog.Error().
SetTitle("Import Failed").
SetMessage(err.Error()).
Show()
return err
}
app.Dialog.Info().
SetTitle("Success").
SetMessage("Configuration imported successfully!").
Show()
return nil
}
```
## Best Practices
### Do
- **Provide file filters** - Help users find files
- **Set appropriate titles** - Clear context
- **Use default directories** - Start in logical location
- **Validate selections** - Check file types
- **Handle cancellation** - User might cancel
- **Show confirmation** - For destructive actions
- **Provide feedback** - Success/error messages
### Don't
- **Don't skip validation** - Check file types
- **Don't ignore errors** - Handle cancellation
- **Don't use generic filters** - Be specific
- **Don't forget "All Files"** - Always include as option
- **Don't hardcode paths** - Use user's home directory
- **Don't assume file exists** - Check before opening
## Platform Differences
### macOS
- Native NSOpenPanel/NSSavePanel
- Sheet-style when attached to window
- Follows system theme
- Supports Quick Look preview
- Tags and favourites integration
### Windows
- Native File Open/Save dialogs
- Follows system theme
- Recent files integration
- Network location support
### Linux
- GTK file chooser
- Varies by desktop environment
- Follows desktop theme
- Recent files support
## Next Steps
<CardGrid>
<Card title="Message dialogs" icon="information">
Info, warning, and error dialogs.
[Learn More →](/features/dialogs/message)
</Card>
<Card title="Custom dialogs" icon="puzzle">
Create custom dialog windows.
[Learn More →](/features/dialogs/custom)
</Card>
<Card title="Bindings" icon="rocket">
Call Go functions from JavaScript.
[Learn More →](/features/bindings/methods)
</Card>
<Card title="Events" icon="star">
Use events for progress updates.
[Learn More →](/features/events/system)
</Card>
</CardGrid>
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [file dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs).

View file

@ -0,0 +1,518 @@
---
title: Message dialogs
description: Display information, warnings, errors, and questions
sidebar:
order: 2
---
import { Card, CardGrid } from "@astrojs/starlight/components";
## Message dialogs
Wails provides **native message dialogs** with platform-appropriate appearance: info, warning, error, and question dialogs with customisable titles, messages, and buttons. Simple API, native behaviour, accessible by default.
## Information dialog
Display informational messages:
```go
app.Dialog.Info().
SetTitle("Success").
SetMessage("File saved successfully!").
Show()
```
**Use cases:**
- Success confirmations
- Completion notices
- Informational messages
- Status updates
**Example - Save confirmation:**
```go
func saveFile(path string, data []byte) error {
if err := os.WriteFile(path, data, 0644); err != nil {
return err
}
app.Dialog.Info().
SetTitle("File Saved").
SetMessage(fmt.Sprintf("Saved to %s", filepath.Base(path))).
Show()
return nil
}
```
## Warning dialog
Show warnings:
```go
app.Dialog.Warning().
SetTitle("Warning").
SetMessage("This action cannot be undone.").
Show()
```
**Use cases:**
- Non-critical warnings
- Deprecation notices
- Caution messages
- Potential issues
**Example - Disk space warning:**
```go
func checkDiskSpace() {
available := getDiskSpace()
if available < 100*1024*1024 { // Less than 100MB
app.Dialog.Warning().
SetTitle("Low Disk Space").
SetMessage(fmt.Sprintf("Only %d MB available.", available/(1024*1024))).
Show()
}
}
```
## Error dialog
Display errors:
```go
app.Dialog.Error().
SetTitle("Error").
SetMessage("Failed to connect to server.").
Show()
```
**Use cases:**
- Error messages
- Failure notifications
- Exception handling
- Critical issues
**Example - Network error:**
```go
func fetchData(url string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
app.Dialog.Error().
SetTitle("Network Error").
SetMessage(fmt.Sprintf("Failed to connect: %v", err)).
Show()
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
```
## Question dialog
Ask users questions using button callbacks:
```go
dialog := app.Dialog.Question().
SetTitle("Confirm").
SetMessage("Save changes before closing?")
saveBtn := dialog.AddButton("Save")
dontSaveBtn := dialog.AddButton("Don't Save")
cancelBtn := dialog.AddButton("Cancel")
saveBtn.SetAsDefault()
cancelBtn.SetAsCancel()
saveBtn.OnClick(func() {
saveChanges()
})
dontSaveBtn.OnClick(func() {
// Continue without saving
})
cancelBtn.OnClick(func() {
// Don't close
})
dialog.Show()
```
**Use cases:**
- Confirm actions
- Yes/No questions
- Multiple choice
- User decisions
**Example - Unsaved changes:**
```go
func closeDocument() {
if !hasUnsavedChanges() {
closeWindow()
return
}
dialog := app.Dialog.Question().
SetTitle("Unsaved Changes").
SetMessage("Do you want to save your changes?")
saveBtn := dialog.AddButton("Save")
dontSaveBtn := dialog.AddButton("Don't Save")
cancelBtn := dialog.AddButton("Cancel")
saveBtn.SetAsDefault()
cancelBtn.SetAsCancel()
saveBtn.OnClick(func() {
saveDocument()
closeWindow()
})
dontSaveBtn.OnClick(func() {
closeWindow()
})
// Cancel button does nothing - dialog just closes
dialog.Show()
}
```
## dialog Options
### Title and Message
```go
dialog := app.Dialog.Info().
SetTitle("Operation Complete").
SetMessage("All files have been processed successfully.")
```
**Best practices:**
- **Title:** Short, descriptive (2-5 words)
- **Message:** Clear, specific, actionable
- **Avoid jargon:** Use plain language
### Buttons
**Single button (Info/Warning/Error):**
```go
// Default "OK" button
app.Dialog.Info().
SetMessage("Done!").
Show()
```
**Multiple buttons (Question) with callbacks:**
```go
dialog := app.Dialog.Question().
SetMessage("Choose an action")
opt1Btn := dialog.AddButton("Option 1")
opt2Btn := dialog.AddButton("Option 2")
opt3Btn := dialog.AddButton("Option 3")
opt1Btn.OnClick(func() {
// handle option 1
})
opt2Btn.OnClick(func() {
// handle option 2
})
opt3Btn.OnClick(func() {
// handle option 3
})
dialog.Show()
```
**Default button:**
```go
dialog := app.Dialog.Question().
SetMessage("Delete file?")
deleteBtn := dialog.AddButton("Delete")
cancelBtn := dialog.AddButton("Cancel")
cancelBtn.SetAsDefault() // Safe option
cancelBtn.SetAsCancel()
deleteBtn.OnClick(func() {
// handle delete
})
dialog.Show()
```
**Best practices:**
- **1-3 buttons:** Don't overwhelm users
- **Clear labels:** "Save" not "OK"
- **Safe default:** Non-destructive action
- **Order matters:** Most likely action first (except Cancel)
### Window Attachment
Attach to specific window:
```go
dialog := app.Dialog.Question().
SetMessage("Window-specific question").
AttachToWindow(window)
dialog.Show()
```
**Benefits:**
- dialog appears on correct window
- Parent window disabled whilst shown
- Better multi-window UX
## Complete Examples
### Confirm Destructive Action
```go
func deleteFiles(paths []string) {
// Confirm deletion
message := fmt.Sprintf("Delete %d file(s)?", len(paths))
if len(paths) == 1 {
message = fmt.Sprintf("Delete %s?", filepath.Base(paths[0]))
}
dialog := app.Dialog.Question().
SetTitle("Confirm Delete").
SetMessage(message)
deleteBtn := dialog.AddButton("Delete")
cancelBtn := dialog.AddButton("Cancel")
cancelBtn.SetAsDefault()
cancelBtn.SetAsCancel()
deleteBtn.OnClick(func() {
// Perform deletion
var errs []error
for _, path := range paths {
if err := os.Remove(path); err != nil {
errs = append(errs, err)
}
}
// Show result
if len(errs) > 0 {
app.Dialog.Error().
SetTitle("Delete Failed").
SetMessage(fmt.Sprintf("Failed to delete %d file(s)", len(errs))).
Show()
} else {
app.Dialog.Info().
SetTitle("Delete Complete").
SetMessage(fmt.Sprintf("Deleted %d file(s)", len(paths))).
Show()
}
})
dialog.Show()
}
```
### Error Handling with Retry
```go
func connectToServer(url string, attempts int) {
if err := tryConnect(url); err == nil {
return
}
if attempts >= 3 {
app.Dialog.Error().
SetTitle("Connection Failed").
SetMessage("Could not connect after 3 attempts.").
Show()
return
}
dialog := app.Dialog.Question().
SetTitle("Connection Failed").
SetMessage("Failed to connect. Retry?")
retryBtn := dialog.AddButton("Retry")
cancelBtn := dialog.AddButton("Cancel")
cancelBtn.SetAsCancel()
retryBtn.OnClick(func() {
connectToServer(url, attempts+1)
})
dialog.Show()
}
```
### Multi-Step Workflow
```go
func exportAndEmail() {
// Step 1: Confirm export
dialog := app.Dialog.Question().
SetTitle("Export and Email").
SetMessage("Export data and send via email?")
continueBtn := dialog.AddButton("Continue")
cancelBtn := dialog.AddButton("Cancel")
cancelBtn.SetAsCancel()
continueBtn.OnClick(func() {
// Step 2: Export data
data, err := exportData()
if err != nil {
app.Dialog.Error().
SetTitle("Export Failed").
SetMessage(err.Error()).
Show()
return
}
// Step 3: Send email
if err := sendEmail(data); err != nil {
app.Dialog.Error().
SetTitle("Email Failed").
SetMessage(err.Error()).
Show()
return
}
// Step 4: Success
app.Dialog.Info().
SetTitle("Success").
SetMessage("Data exported and emailed successfully!").
Show()
})
dialog.Show()
}
```
### Validation with Feedback
```go
func validateAndSave(data string) {
// Validate
if err := validate(data); err != nil {
app.Dialog.Warning().
SetTitle("Validation Warning").
SetMessage(err.Error()).
Show()
dialog := app.Dialog.Question().
SetMessage("Save anyway?")
saveBtn := dialog.AddButton("Save")
cancelBtn := dialog.AddButton("Cancel")
cancelBtn.SetAsDefault()
cancelBtn.SetAsCancel()
saveBtn.OnClick(func() {
doSave(data)
})
dialog.Show()
return
}
doSave(data)
}
func doSave(data string) {
if err := save(data); err != nil {
app.Dialog.Error().
SetTitle("Save Failed").
SetMessage(err.Error()).
Show()
return
}
app.Dialog.Info().
SetTitle("Saved").
SetMessage("Data saved successfully!").
Show()
}
```
## Best Practices
### Do
- **Be specific** - "File saved to Documents" not "Success"
- **Use appropriate type** - Error for errors, Warning for warnings
- **Provide context** - Include relevant details
- **Use clear button labels** - "Delete" not "OK"
- **Set safe defaults** - Non-destructive action
- **Handle cancellation** - User might close dialog
### Don't
- **Don't overuse** - Interrupts workflow
- **Don't use for frequent updates** - Use notifications instead
- **Don't use generic messages** - "Error" tells nothing
- **Don't block unnecessarily** - Consider async alternatives
- **Don't use technical jargon** - Plain language
## Platform Differences
### macOS
- Sheet-style when attached to window
- Standard keyboard shortcuts (⌘. for Cancel)
- Follows system theme automatically
- Accessibility built-in
### Windows
- Modal dialogs
- TaskDialog appearance
- Esc for Cancel
- Follows system theme
### Linux
- GTK dialogs
- Varies by desktop environment
- Follows desktop theme
- Standard keyboard navigation
## Next Steps
<CardGrid>
<Card title="File dialogs" icon="document">
Open, save, and folder selection.
[Learn More →](/features/dialogs/file)
</Card>
<Card title="Custom dialogs" icon="puzzle">
Create custom dialog windows.
[Learn More →](/features/dialogs/custom)
</Card>
<Card title="Notifications" icon="bell">
Non-intrusive notifications.
[Learn More →](/features/notifications)
</Card>
<Card title="Events" icon="star">
Use events for non-blocking communication.
[Learn More →](/features/events/system)
</Card>
</CardGrid>
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs).

View file

@ -0,0 +1,494 @@
---
title: dialogs Overview
description: Display native system dialogs in your application
sidebar:
order: 1
---
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
## Native dialogs
Wails provides **native system dialogs** that work across all platforms: message dialogs (info, warning, error, question), file dialogs (open, save, folder), and custom dialog windows with platform-native appearance and behaviour.
## Quick Start
```go
// Information dialog
app.Dialog.Info().
SetTitle("Success").
SetMessage("File saved successfully!").
Show()
// Question dialog with button callbacks
dialog := app.Dialog.Question().
SetTitle("Confirm").
SetMessage("Delete this file?")
deleteBtn := dialog.AddButton("Delete")
cancelBtn := dialog.AddButton("Cancel")
cancelBtn.SetAsDefault()
cancelBtn.SetAsCancel()
deleteBtn.OnClick(func() {
deleteFile()
})
dialog.Show()
// File open dialog
path, _ := app.Dialog.OpenFile().
SetTitle("Select Image").
AddFilter("Images", "*.png;*.jpg").
PromptForSingleSelection()
```
**That's it!** Native dialogs with minimal code.
## dialog Types
### Information dialog
Display simple messages:
```go
app.Dialog.Info().
SetTitle("Welcome").
SetMessage("Welcome to our application!").
Show()
```
**Use cases:**
- Success messages
- Informational notices
- Completion confirmations
### Warning dialog
Show warnings:
```go
app.Dialog.Warning().
SetTitle("Warning").
SetMessage("This action cannot be undone.").
Show()
```
**Use cases:**
- Non-critical warnings
- Deprecation notices
- Caution messages
### Error dialog
Display errors:
```go
app.Dialog.Error().
SetTitle("Error").
SetMessage("Failed to save file: " + err.Error()).
Show()
```
**Use cases:**
- Error messages
- Failure notifications
- Exception handling
### Question dialog
Ask users questions:
```go
dialog := app.Dialog.Question().
SetTitle("Confirm Delete").
SetMessage("Are you sure you want to delete this file?")
deleteBtn := dialog.AddButton("Delete")
cancelBtn := dialog.AddButton("Cancel")
cancelBtn.SetAsDefault()
cancelBtn.SetAsCancel()
deleteBtn.OnClick(func() {
deleteFile()
})
dialog.Show()
```
**Use cases:**
- Confirm actions
- Yes/No questions
- Multiple choice
## File dialogs
### Open File dialog
Select files to open:
```go
path, err := app.Dialog.OpenFile().
SetTitle("Select Image").
AddFilter("Images", "*.png;*.jpg;*.gif").
AddFilter("All Files", "*.*").
PromptForSingleSelection()
if err == nil {
openFile(path)
}
```
**Multiple selection:**
```go
paths, err := app.Dialog.OpenFile().
SetTitle("Select Images").
AddFilter("Images", "*.png;*.jpg").
PromptForMultipleSelection()
for _, path := range paths {
processFile(path)
}
```
### Save File dialog
Choose where to save:
```go
path, err := app.Dialog.SaveFile().
SetTitle("Save Document").
SetFilename("document.txt").
AddFilter("Text Files", "*.txt").
AddFilter("All Files", "*.*").
PromptForSingleSelection()
if err == nil {
saveFile(path)
}
```
### Select Folder dialog
Choose a directory:
```go
path, err := app.Dialog.OpenFile().
CanChooseDirectories(true).
CanChooseFiles(false).
SetTitle("Select Output Folder").
PromptForSingleSelection()
if err == nil {
exportToFolder(path)
}
```
## dialog Options
### Title and Message
```go
dialog := app.Dialog.Info().
SetTitle("Success").
SetMessage("Operation completed successfully!")
```
### Buttons
**Single button (Info/Warning/Error):**
```go
// Default "OK" button
app.Dialog.Info().
SetMessage("Done!").
Show()
```
**Multiple buttons (Question) with callbacks:**
```go
dialog := app.Dialog.Question().
SetMessage("Choose action")
opt1Btn := dialog.AddButton("Option 1")
opt2Btn := dialog.AddButton("Option 2")
opt3Btn := dialog.AddButton("Option 3")
opt1Btn.OnClick(func() {
// handle option 1
})
opt2Btn.OnClick(func() {
// handle option 2
})
opt3Btn.OnClick(func() {
// handle option 3
})
dialog.Show()
```
**Default button:**
```go
dialog := app.Dialog.Question().
SetMessage("Delete file?")
deleteBtn := dialog.AddButton("Delete")
cancelBtn := dialog.AddButton("Cancel")
cancelBtn.SetAsDefault() // Safe option highlighted by default
cancelBtn.SetAsCancel()
deleteBtn.OnClick(func() {
// handle delete
})
dialog.Show()
```
### Window Attachment
Attach dialog to specific window:
```go
dialog := app.Dialog.Info().
SetMessage("Window-specific message").
AttachToWindow(window)
dialog.Show()
```
**Behaviour:**
- dialog appears centred on parent window
- Parent window disabled whilst dialog shown
- dialog moves with parent window (macOS)
## Platform Behaviour
<Tabs syncKey="platform">
<TabItem label="macOS" icon="apple">
**macOS dialogs:**
- Native NSAlert appearance
- Follow system theme (light/dark)
- Support keyboard navigation
- Standard shortcuts (⌘. for Cancel)
- Accessibility features built-in
- Sheet-style when attached to window
**Example:**
```go
// Appears as sheet on macOS
app.Dialog.Question().
SetMessage("Save changes?").
AttachToWindow(window).
Show()
```
</TabItem>
<TabItem label="Windows" icon="seti:windows">
**Windows dialogs:**
- Native TaskDialog appearance
- Follow system theme
- Support keyboard navigation
- Standard shortcuts (Esc for Cancel)
- Accessibility features built-in
- Modal to parent window
**Example:**
```go
// Modal dialog on Windows
app.Dialog.Error().
SetTitle("Error").
SetMessage("Operation failed").
Show()
```
</TabItem>
<TabItem label="Linux" icon="linux">
**Linux dialogs:**
- GTK dialog appearance
- Follow desktop theme
- Support keyboard navigation
- Desktop environment integration
- Varies by DE (GNOME, KDE, etc.)
**Example:**
```go
// GTK dialog on Linux
app.Dialog.Info().
SetMessage("Update complete").
Show()
```
</TabItem>
</Tabs>
## Common Patterns
### Confirm Before Destructive Action
```go
func deleteFile(path string) {
dialog := app.Dialog.Question().
SetTitle("Confirm Delete").
SetMessage(fmt.Sprintf("Delete %s?", filepath.Base(path)))
deleteBtn := dialog.AddButton("Delete")
cancelBtn := dialog.AddButton("Cancel")
cancelBtn.SetAsDefault()
cancelBtn.SetAsCancel()
deleteBtn.OnClick(func() {
os.Remove(path)
})
dialog.Show()
}
```
### Error Handling with dialog
```go
func saveDocument(path string, data []byte) {
if err := os.WriteFile(path, data, 0644); err != nil {
app.Dialog.Error().
SetTitle("Save Failed").
SetMessage(fmt.Sprintf("Could not save file: %v", err)).
Show()
return
}
app.Dialog.Info().
SetTitle("Success").
SetMessage("File saved successfully!").
Show()
}
```
### File Selection with Validation
```go
func selectImageFile() (string, error) {
path, err := app.Dialog.OpenFile().
SetTitle("Select Image").
AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif").
PromptForSingleSelection()
if err != nil {
return "", err
}
// Validate file
if !isValidImage(path) {
app.Dialog.Error().
SetTitle("Invalid File").
SetMessage("Selected file is not a valid image.").
Show()
return "", errors.New("invalid image")
}
return path, nil
}
```
### Multi-Step dialog Flow
```go
func exportData() {
// Step 1: Confirm export
dialog := app.Dialog.Question().
SetTitle("Export Data").
SetMessage("Export all data to CSV?")
exportBtn := dialog.AddButton("Export")
cancelBtn := dialog.AddButton("Cancel")
cancelBtn.SetAsCancel()
exportBtn.OnClick(func() {
// Step 2: Select destination
path, err := app.Dialog.SaveFile().
SetTitle("Save Export").
SetFilename("export.csv").
AddFilter("CSV Files", "*.csv").
PromptForSingleSelection()
if err != nil {
return
}
// Step 3: Perform export
if err := performExport(path); err != nil {
app.Dialog.Error().
SetTitle("Export Failed").
SetMessage(err.Error()).
Show()
return
}
// Step 4: Success
app.Dialog.Info().
SetTitle("Export Complete").
SetMessage("Data exported successfully!").
Show()
})
dialog.Show()
}
```
## Best Practices
### Do
- **Use native dialogs** - Better UX than custom
- **Provide clear messages** - Be specific
- **Set appropriate titles** - Context matters
- **Use default buttons wisely** - Safe option as default
- **Handle cancellation** - User might cancel
- **Validate file selections** - Check file types
### Don't
- **Don't overuse dialogs** - Interrupts workflow
- **Don't use for frequent messages** - Use notifications
- **Don't forget error handling** - User might cancel
- **Don't block unnecessarily** - Consider alternatives
- **Don't use generic messages** - Be specific
- **Don't ignore platform differences** - Test on all platforms
## Next Steps
<CardGrid>
<Card title="Message dialogs" icon="information">
Info, warning, and error dialogs.
[Learn More →](/features/dialogs/message)
</Card>
<Card title="File dialogs" icon="document">
Open, save, and folder selection.
[Learn More →](/features/dialogs/file)
</Card>
<Card title="Custom dialogs" icon="puzzle">
Create custom dialog windows.
[Learn More →](/features/dialogs/custom)
</Card>
<Card title="Windows" icon="laptop">
Learn about window management.
[Learn More →](/features/windows/basics)
</Card>
</CardGrid>
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs).

View file

@ -0,0 +1,630 @@
---
title: Environment
sidebar:
order: 59
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
Wails provides comprehensive environment information through the EnvironmentManager API. This allows your application to detect system properties, theme preferences, and integrate with the operating system's file manager.
## Accessing the Environment Manager
The environment manager is accessed through the `Env` property on your application instance:
```go
app := application.New(application.Options{
Name: "Environment Demo",
})
// Access the environment manager
env := app.Env
```
## System Information
### Get Environment Information
Retrieve comprehensive information about the runtime environment:
```go
envInfo := app.Env.Info()
app.Logger.Info("Environment information",
"os", envInfo.OS, // "windows", "darwin", "linux"
"arch", envInfo.Arch, // "amd64", "arm64", etc.
"debug", envInfo.Debug, // Debug mode flag
)
// Operating system details
if envInfo.OSInfo != nil {
app.Logger.Info("OS details",
"name", envInfo.OSInfo.Name,
"version", envInfo.OSInfo.Version,
)
}
// Platform-specific information
for key, value := range envInfo.PlatformInfo {
app.Logger.Info("Platform info", "key", key, "value", value)
}
```
### Environment Structure
The environment information includes several important fields:
```go
type EnvironmentInfo struct {
OS string // Operating system: "windows", "darwin", "linux"
Arch string // Architecture: "amd64", "arm64", "386", etc.
Debug bool // Whether running in debug mode
OSInfo *operatingsystem.OS // Detailed OS information
PlatformInfo map[string]any // Platform-specific details
}
```
## Theme Detection
### Dark Mode Detection
Detect whether the system is using dark mode:
```go
if app.Env.IsDarkMode() {
app.Logger.Info("System is in dark mode")
// Apply dark theme to your application
applyDarkTheme()
} else {
app.Logger.Info("System is in light mode")
// Apply light theme to your application
applyLightTheme()
}
```
### Theme Change Monitoring
Listen for theme changes to update your application dynamically:
```go
import "github.com/wailsapp/wails/v3/pkg/events"
// Listen for theme changes
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
if app.Env.IsDarkMode() {
app.Logger.Info("Switched to dark mode")
updateApplicationTheme("dark")
} else {
app.Logger.Info("Switched to light mode")
updateApplicationTheme("light")
}
})
func updateApplicationTheme(theme string) {
// Update your application's theme
// This could emit an event to the frontend
app.Event.Emit("theme:changed", theme)
}
```
### Accent Colour
Get the system accent colour:
```go
accentColor := app.Env.GetAccentColor()
app.Logger.Info("System accent colour", "color", accentColor)
// Returns a CSS-compatible colour string, e.g. "rgb(0,122,255)"
```
## File Manager Integration
### Open File Manager
Open the system's file manager at a specific location:
```go
// Open file manager at a directory
err := app.Env.OpenFileManager("/Users/username/Documents", false)
if err != nil {
app.Logger.Error("Failed to open file manager", "error", err)
}
// Open file manager and select a specific file
err = app.Env.OpenFileManager("/Users/username/Documents/report.pdf", true)
if err != nil {
app.Logger.Error("Failed to open file manager with selection", "error", err)
}
```
### Common Use Cases
Show files or folders in the file manager from your application:
```go
func showInFileManager(app *application.App, path string) {
err := app.Env.OpenFileManager(path, true)
if err != nil {
// Fallback: try opening just the directory
dir := filepath.Dir(path)
err = app.Env.OpenFileManager(dir, false)
if err != nil {
app.Logger.Error("Could not open file manager", "path", path, "error", err)
// Show error to user
app.Dialog.Error().
SetTitle("File Manager Error").
SetMessage("Could not open file manager").
Show()
}
}
}
// Usage examples
func setupFileMenu(app *application.App) {
menu := app.Menu.New()
fileMenu := menu.AddSubmenu("File")
fileMenu.Add("Show Downloads Folder").OnClick(func(ctx *application.Context) {
homeDir, _ := os.UserHomeDir()
downloadsDir := filepath.Join(homeDir, "Downloads")
showInFileManager(app, downloadsDir)
})
fileMenu.Add("Show Application Data").OnClick(func(ctx *application.Context) {
configDir, _ := os.UserConfigDir()
appDir := filepath.Join(configDir, "MyApp")
showInFileManager(app, appDir)
})
}
```
## Platform-Specific Behavior
### Adaptive Application Behavior
Use environment information to adapt your application's behavior:
```go
func configureForPlatform(app *application.App) {
envInfo := app.Env.Info()
switch envInfo.OS {
case "darwin":
configureMacOS(app)
case "windows":
configureWindows(app)
case "linux":
configureLinux(app)
}
// Adapt to architecture
if envInfo.Arch == "arm64" {
app.Logger.Info("Running on ARM architecture")
// Potentially optimize for ARM
}
}
func configureMacOS(app *application.App) {
app.Logger.Info("Configuring for macOS")
// macOS-specific configuration
menu := app.Menu.New()
menu.AddRole(application.AppMenu) // Add standard macOS app menu
// Handle dark mode
if app.Env.IsDarkMode() {
setMacOSDarkTheme()
}
}
func configureWindows(app *application.App) {
app.Logger.Info("Configuring for Windows")
// Windows-specific configuration
// Set up Windows-style menus, key bindings, etc.
}
func configureLinux(app *application.App) {
app.Logger.Info("Configuring for Linux")
// Linux-specific configuration
// Adapt to different desktop environments
}
```
## Debug Mode Handling
### Development vs Production
Use debug mode information to enable development features:
```go
func setupApplicationMode(app *application.App) {
envInfo := app.Env.Info()
if envInfo.Debug {
app.Logger.Info("Running in debug mode")
setupDevelopmentFeatures(app)
} else {
app.Logger.Info("Running in production mode")
setupProductionFeatures(app)
}
}
func setupDevelopmentFeatures(app *application.App) {
// Enable development-only features
menu := app.Menu.New()
// Add development menu
devMenu := menu.AddSubmenu("Development")
devMenu.Add("Reload Application").OnClick(func(ctx *application.Context) {
// Reload the application
window := app.Window.Current()
if window != nil {
window.Reload()
}
})
devMenu.Add("Open DevTools").OnClick(func(ctx *application.Context) {
window := app.Window.Current()
if window != nil {
window.OpenDevTools()
}
})
devMenu.Add("Show Environment").OnClick(func(ctx *application.Context) {
showEnvironmentDialog(app)
})
}
func setupProductionFeatures(app *application.App) {
// Production-only features
// Disable debug logging, enable analytics, etc.
}
```
## Environment Information Dialog
### Display System Information
Create a dialog showing environment information:
```go
func showEnvironmentDialog(app *application.App) {
envInfo := app.Env.Info()
details := fmt.Sprintf(`Environment Information:
Operating System: %s
Architecture: %s
Debug Mode: %t
Dark Mode: %t
Platform Information:`,
envInfo.OS,
envInfo.Arch,
envInfo.Debug,
app.Env.IsDarkMode())
// Add platform-specific details
for key, value := range envInfo.PlatformInfo {
details += fmt.Sprintf("\n%s: %v", key, value)
}
if envInfo.OSInfo != nil {
details += fmt.Sprintf("\n\nOS Details:\nName: %s\nVersion: %s",
envInfo.OSInfo.Name,
envInfo.OSInfo.Version)
}
dialog := app.Dialog.Info()
dialog.SetTitle("Environment Information")
dialog.SetMessage(details)
dialog.Show()
}
```
## Platform Considerations
<Tabs>
<TabItem label="macOS" icon="fa-brands:apple">
On macOS:
- Dark mode detection uses system appearance settings
- File manager operations use Finder
- Platform info includes macOS version details
- Architecture may be "arm64" on Apple Silicon Macs
```go
if envInfo.OS == "darwin" {
// macOS-specific handling
if envInfo.Arch == "arm64" {
app.Logger.Info("Running on Apple Silicon")
}
}
```
</TabItem>
<TabItem label="Windows" icon="fa-brands:windows">
On Windows:
- Dark mode detection uses Windows theme settings
- File manager operations use Windows Explorer
- Platform info includes Windows version details
- May include additional Windows-specific information
```go
if envInfo.OS == "windows" {
// Windows-specific handling
for key, value := range envInfo.PlatformInfo {
if key == "windows_version" {
app.Logger.Info("Windows version", "version", value)
}
}
}
```
</TabItem>
<TabItem label="Linux" icon="fa-brands:linux">
On Linux:
- Dark mode detection varies by desktop environment
- File manager operations use system default file manager
- Platform info includes distribution details
- Behavior may vary between different Linux distributions
```go
if envInfo.OS == "linux" {
// Linux-specific handling
if distro, ok := envInfo.PlatformInfo["distribution"]; ok {
app.Logger.Info("Linux distribution", "distro", distro)
}
}
```
</TabItem>
</Tabs>
## Best Practices
1. **Cache Environment Information**: Environment info rarely changes during runtime:
```go
type App struct {
envInfo *application.EnvironmentInfo
}
func (a *App) getEnvInfo() application.EnvironmentInfo {
if a.envInfo == nil {
info := a.app.Env.Info()
a.envInfo = &info
}
return *a.envInfo
}
```
2. **Handle Theme Changes**: Listen for system theme changes:
```go
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
updateTheme(app.Env.IsDarkMode())
})
```
3. **Graceful File Manager Failures**: Always handle file manager errors:
```go
func openFileManagerSafely(app *application.App, path string) {
err := app.Env.OpenFileManager(path, false)
if err != nil {
// Provide fallback or user notification
app.Logger.Warn("Could not open file manager", "path", path)
}
}
```
4. **Platform-Specific Features**: Use environment info to enable platform features:
```go
envInfo := app.Env.Info()
if envInfo.OS == "darwin" {
// Enable macOS-specific features
}
```
## Complete Example
Here's a complete example demonstrating environment management:
```go
package main
import (
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)
func main() {
app := application.New(application.Options{
Name: "Environment Demo",
})
// Setup application based on environment
setupForEnvironment(app)
// Monitor theme changes
monitorThemeChanges(app)
// Create menu with environment features
setupEnvironmentMenu(app)
// Create main window
window := app.Window.New()
window.SetTitle("Environment Demo")
err := app.Run()
if err != nil {
panic(err)
}
}
func setupForEnvironment(app *application.App) {
envInfo := app.Env.Info()
app.Logger.Info("Application environment",
"os", envInfo.OS,
"arch", envInfo.Arch,
"debug", envInfo.Debug,
"darkMode", app.Env.IsDarkMode(),
)
// Configure for platform
switch envInfo.OS {
case "darwin":
app.Logger.Info("Configuring for macOS")
// macOS-specific setup
case "windows":
app.Logger.Info("Configuring for Windows")
// Windows-specific setup
case "linux":
app.Logger.Info("Configuring for Linux")
// Linux-specific setup
}
// Apply initial theme
if app.Env.IsDarkMode() {
applyDarkTheme(app)
} else {
applyLightTheme(app)
}
}
func monitorThemeChanges(app *application.App) {
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
if app.Env.IsDarkMode() {
app.Logger.Info("System switched to dark mode")
applyDarkTheme(app)
} else {
app.Logger.Info("System switched to light mode")
applyLightTheme(app)
}
})
}
func setupEnvironmentMenu(app *application.App) {
menu := app.Menu.New()
// Add platform-specific app menu
if runtime.GOOS == "darwin" {
menu.AddRole(application.AppMenu)
}
// Tools menu
toolsMenu := menu.AddSubmenu("Tools")
toolsMenu.Add("Show Environment Info").OnClick(func(ctx *application.Context) {
showEnvironmentInfo(app)
})
toolsMenu.Add("Open Downloads Folder").OnClick(func(ctx *application.Context) {
openDownloadsFolder(app)
})
toolsMenu.Add("Toggle Theme").OnClick(func(ctx *application.Context) {
// This would typically be handled by the system
// but shown here for demonstration
toggleTheme(app)
})
app.Menu.Set(menu)
}
func showEnvironmentInfo(app *application.App) {
envInfo := app.Env.Info()
message := fmt.Sprintf(`Environment Information:
Operating System: %s
Architecture: %s
Debug Mode: %t
Dark Mode: %t
Platform Details:`,
envInfo.OS,
envInfo.Arch,
envInfo.Debug,
app.Env.IsDarkMode())
for key, value := range envInfo.PlatformInfo {
message += fmt.Sprintf("\n%s: %v", key, value)
}
if envInfo.OSInfo != nil {
message += fmt.Sprintf("\n\nOS Information:\nName: %s\nVersion: %s",
envInfo.OSInfo.Name,
envInfo.OSInfo.Version)
}
dialog := app.Dialog.Info()
dialog.SetTitle("Environment Information")
dialog.SetMessage(message)
dialog.Show()
}
func openDownloadsFolder(app *application.App) {
homeDir, err := os.UserHomeDir()
if err != nil {
app.Logger.Error("Could not get home directory", "error", err)
return
}
downloadsDir := filepath.Join(homeDir, "Downloads")
err = app.Env.OpenFileManager(downloadsDir, false)
if err != nil {
app.Logger.Error("Could not open Downloads folder", "error", err)
app.Dialog.Error().
SetTitle("File Manager Error").
SetMessage("Could not open Downloads folder").
Show()
}
}
func applyDarkTheme(app *application.App) {
app.Logger.Info("Applying dark theme")
// Emit theme change to frontend
app.Event.Emit("theme:apply", "dark")
}
func applyLightTheme(app *application.App) {
app.Logger.Info("Applying light theme")
// Emit theme change to frontend
app.Event.Emit("theme:apply", "light")
}
func toggleTheme(app *application.App) {
// This is just for demonstration
// Real theme changes should come from the system
currentlyDark := app.Env.IsDarkMode()
if currentlyDark {
applyLightTheme(app)
} else {
applyDarkTheme(app)
}
}
```
:::tip[Pro Tip]
Use environment information to provide platform-appropriate user experiences. For example, use Command key shortcuts on macOS and Control key shortcuts on Windows/Linux.
:::
:::danger[Warning]
Environment information is generally stable during application runtime, but theme preferences can change. Always listen for theme change events to keep your UI synchronized.
:::

View file

@ -0,0 +1,585 @@
---
title: Event System
description: Communicate between components with the event system
sidebar:
order: 1
---
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
## Event System
Wails provides a **unified event system** for pub/sub communication. Emit events from anywhere, listen from anywhere—Go to JavaScript, JavaScript to Go, window to window—enabling decoupled architecture with typed events and lifecycle hooks.
## Quick Start
**Go (emit):**
```go
app.Event.Emit("user-logged-in", map[string]interface{}{
"userId": 123,
"name": "Alice",
})
```
**JavaScript (listen):**
```javascript
import { Events } from '@wailsio/runtime'
Events.On("user-logged-in", (event) => {
console.log(`User ${event.data.name} logged in`)
})
```
**That's it!** Cross-language pub/sub.
## Event Types
### Custom Events
Your application-specific events:
```go
// Emit from Go
app.Event.Emit("order-created", order)
app.Event.Emit("payment-processed", payment)
app.Event.Emit("notification", message)
```
```javascript
// Listen in JavaScript
Events.On("order-created", handleOrder)
Events.On("payment-processed", handlePayment)
Events.On("notification", showNotification)
```
### System Events
Built-in OS and application events:
```go
import "github.com/wailsapp/wails/v3/pkg/events"
// Theme changes
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) {
if e.Context().IsDarkMode() {
app.Logger.Info("Dark mode enabled")
}
})
// Application lifecycle
app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) {
app.Logger.Info("Application started")
})
```
### Window Events
Window-specific events:
```go
window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
app.Logger.Info("Window focused")
})
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
app.Logger.Info("Window closing")
})
```
## Emitting Events
### From Go
**Basic emit:**
```go
app.Event.Emit("event-name", data)
```
**With different data types:**
```go
// String
app.Event.Emit("message", "Hello")
// Number
app.Event.Emit("count", 42)
// Struct
app.Event.Emit("user", User{ID: 1, Name: "Alice"})
// Map
app.Event.Emit("config", map[string]interface{}{
"theme": "dark",
"fontSize": 14,
})
// Array
app.Event.Emit("items", []string{"a", "b", "c"})
```
**To specific window:**
```go
window.EmitEvent("window-specific-event", data)
```
### From JavaScript
```javascript
import { Events } from '@wailsio/runtime'
// Emit to Go
Events.Emit("button-clicked", { buttonId: "submit" })
// Emit to all windows
Events.Emit("broadcast-message", "Hello everyone")
```
## Listening to Events
### In Go
**Application events:**
```go
app.Event.On("custom-event", func(e *application.CustomEvent) {
data := e.Data
// Handle event
})
```
**With type assertion:**
```go
app.Event.On("user-updated", func(e *application.CustomEvent) {
user := e.Data.(User)
app.Logger.Info("User updated", "name", user.Name)
})
```
**Multiple handlers:**
```go
// All handlers will be called
app.Event.On("order-created", logOrder)
app.Event.On("order-created", sendEmail)
app.Event.On("order-created", updateInventory)
```
### In JavaScript
**Basic listener:**
```javascript
import { Events } from '@wailsio/runtime'
Events.On("event-name", (event) => {
console.log("Event received:", event.data)
})
```
**With cleanup:**
```javascript
const unsubscribe = Events.On("event-name", handleEvent)
// Later, stop listening
unsubscribe()
```
**Multiple handlers:**
```javascript
Events.On("data-updated", updateUI)
Events.On("data-updated", saveToCache)
Events.On("data-updated", logChange)
```
## System Events
### Application Events
**Common events (cross-platform):**
```go
import "github.com/wailsapp/wails/v3/pkg/events"
// Application started
app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) {
app.Logger.Info("App started")
})
// Theme changed
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) {
isDark := e.Context().IsDarkMode()
app.Event.Emit("theme-changed", isDark)
})
// File opened
app.Event.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(e *application.ApplicationEvent) {
filePath := e.Context().Filename()
openFile(filePath)
})
```
**Platform-specific events:**
<Tabs syncKey="platform">
<TabItem label="macOS" icon="apple">
```go
// Application became active
app.Event.OnApplicationEvent(events.Mac.ApplicationDidBecomeActive, func(e *application.ApplicationEvent) {
app.Logger.Info("App became active")
})
// Application will terminate
app.Event.OnApplicationEvent(events.Mac.ApplicationWillTerminate, func(e *application.ApplicationEvent) {
cleanup()
})
```
</TabItem>
<TabItem label="Windows" icon="seti:windows">
```go
// Power status changed
app.Event.OnApplicationEvent(events.Windows.APMPowerStatusChange, func(e *application.ApplicationEvent) {
app.Logger.Info("Power status changed")
})
// System suspending
app.Event.OnApplicationEvent(events.Windows.APMSuspend, func(e *application.ApplicationEvent) {
saveState()
})
```
</TabItem>
<TabItem label="Linux" icon="linux">
```go
// Application startup
app.Event.OnApplicationEvent(events.Linux.ApplicationStartup, func(e *application.ApplicationEvent) {
app.Logger.Info("App starting")
})
// Theme changed
app.Event.OnApplicationEvent(events.Linux.SystemThemeChanged, func(e *application.ApplicationEvent) {
updateTheme()
})
```
</TabItem>
</Tabs>
### Window Events
**Common window events:**
```go
// Window focus
window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
app.Logger.Info("Window focused")
})
// Window lost focus
window.OnWindowEvent(events.Common.WindowLostFocus, func(e *application.WindowEvent) {
app.Logger.Info("Window lost focus")
})
// Window closing - use a hook to cancel if needed
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
if hasUnsavedChanges() {
e.Cancel() // Prevent close
}
})
// Window closing - use a listener for cleanup (cannot cancel)
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
cleanup()
})
```
## Event Hooks
Hooks run **before** standard listeners and can **cancel** events:
```go
// Hook - runs first, can cancel
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
if hasUnsavedChanges() {
result := showConfirmdialog("Unsaved changes. Close anyway?")
if result != "yes" {
e.Cancel() // Prevent window close
}
}
})
// Standard listener - runs after hooks
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
app.Logger.Info("Window closing")
})
```
**Key differences:**
| Feature | Hooks | Standard Listeners |
|---------|-------|-------------------|
| Execution order | First, in registration order | After hooks, no guaranteed order |
| Blocking | Synchronous, blocks next hook | Asynchronous, non-blocking |
| Can cancel | Yes | No (already propagated) |
| Use case | Control flow, validation | Logging, side effects |
## Event Patterns
### Pub/Sub Pattern
```go
// Publisher (service)
type OrderService struct {
app *application.App
}
func (o *OrderService) CreateOrder(items []Item) (*Order, error) {
order := &Order{Items: items}
if err := o.saveOrder(order); err != nil {
return nil, err
}
// Publish event
o.app.Event.Emit("order-created", order)
return order, nil
}
// Subscribers
app.Event.On("order-created", func(e *application.CustomEvent) {
order := e.Data.(*Order)
sendConfirmationEmail(order)
})
app.Event.On("order-created", func(e *application.CustomEvent) {
order := e.Data.(*Order)
updateInventory(order)
})
app.Event.On("order-created", func(e *application.CustomEvent) {
order := e.Data.(*Order)
logOrder(order)
})
```
### Request/Response Pattern
```javascript
// Frontend requests data
Events.Emit("get-user-data", { userId: 123 })
```
```go
// Backend responds
app.Event.On("get-user-data", func(e *application.CustomEvent) {
data := e.Data.(map[string]interface{})
userId := int(data["userId"].(float64))
user := getUserFromDB(userId)
// Send response
app.Event.Emit("user-data-response", user)
})
```
```javascript
// Frontend receives response
Events.On("user-data-response", (event) => {
displayUser(event.data)
})
```
**Note:** For request/response, **bindings are better**. Use events for notifications.
### Broadcast Pattern
```go
// Broadcast to all windows
app.Event.Emit("global-notification", "System update available")
```
```javascript
// Each window handles it
Events.On("global-notification", (event) => {
showNotification(event.data)
})
```
### Event Aggregation
```go
type EventAggregator struct {
events []Event
mu sync.Mutex
}
func (ea *EventAggregator) Add(event Event) {
ea.mu.Lock()
defer ea.mu.Unlock()
ea.events = append(ea.events, event)
// Emit batch every 100 events
if len(ea.events) >= 100 {
app.Event.Emit("event-batch", ea.events)
ea.events = nil
}
}
```
## Complete Example
**Go:**
```go
package main
import (
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)
type NotificationService struct {
app *application.App
}
func (n *NotificationService) Notify(message string) {
// Emit to all windows
n.app.Event.Emit("notification", map[string]interface{}{
"message": message,
"timestamp": time.Now(),
})
}
func main() {
app := application.New(application.Options{
Name: "Event Demo",
})
notifService := &NotificationService{app: app}
// System events
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) {
isDark := e.Context().IsDarkMode()
app.Event.Emit("theme-changed", isDark)
})
// Custom events from frontend
app.Event.On("user-action", func(e *application.CustomEvent) {
data := e.Data.(map[string]interface{})
action := data["action"].(string)
app.Logger.Info("User action", "action", action)
// Respond
notifService.Notify("Action completed: " + action)
})
// Window events
window := app.Window.New()
window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
app.Event.Emit("window-focused", window.Name())
})
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
// Confirm before close
app.Event.Emit("confirm-close", nil)
e.Cancel() // Wait for confirmation
})
app.Run()
}
```
**JavaScript:**
```javascript
import { Events } from '@wailsio/runtime'
// Listen for notifications
Events.On("notification", (event) => {
showNotification(event.data.message)
})
// Listen for theme changes
Events.On("theme-changed", (event) => {
document.body.classList.toggle('dark', event.data)
})
// Listen for window focus
Events.On("window-focused", (event) => {
console.log(`Window ${event.data} focused`)
})
// Handle close confirmation
Events.On("confirm-close", () => {
if (confirm("Close window?")) {
Events.Emit("close-confirmed", true)
}
})
// Emit user actions
document.getElementById('button').addEventListener('click', () => {
Events.Emit("user-action", { action: "button-clicked" })
})
```
## Best Practices
### ✅ Do
- **Use events for notifications** - One-way communication
- **Use bindings for requests** - Two-way communication
- **Keep event names consistent** - Use kebab-case
- **Document event data** - What fields are included?
- **Unsubscribe when done** - Prevent memory leaks
- **Use hooks for validation** - Control event flow
### ❌ Don't
- **Don't use events for RPC** - Use bindings instead
- **Don't emit too frequently** - Batch if needed
- **Don't block in handlers** - Keep them fast
- **Don't forget to unsubscribe** - Memory leaks
- **Don't use events for large data** - Use bindings
- **Don't create event loops** - A emits B, B emits A
## Next Steps
<CardGrid>
<Card title="Custom Events" icon="star">
Create your own event types.
[Learn More →](/features/events/custom)
</Card>
<Card title="Event Patterns" icon="puzzle">
Common event patterns and best practices.
[Learn More →](/features/events/patterns)
</Card>
<Card title="Bindings" icon="rocket">
Use bindings for request/response.
[Learn More →](/features/bindings/methods)
</Card>
<Card title="Window Events" icon="laptop">
Handle window lifecycle events.
[Learn More →](/features/windows/events)
</Card>
</CardGrid>
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [event examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/events).

View file

@ -0,0 +1,434 @@
---
title: Keyboard Shortcuts
description: Register global keyboard shortcuts for quick access to functionality
sidebar:
order: 1
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
Wails provides a powerful key binding system that allows you to register global keyboard shortcuts that work across all windows in your application. This enables users to quickly access functionality without navigating through menus.
## Accessing the Key Binding Manager
The key binding manager is accessed through the `KeyBindings` property on your application instance:
```go
app := application.New(application.Options{
Name: "Keyboard Shortcuts Demo",
})
// Access the key binding manager
keyBindings := app.KeyBinding
```
## Adding Key Bindings
### Basic Key Binding
Register a simple keyboard shortcut:
```go
app.KeyBinding.Add("Ctrl+S", func(window application.Window) {
// Handle save action
app.Logger.Info("Save shortcut triggered")
// Perform save operation...
})
```
### Multiple Key Bindings
Register multiple shortcuts for common operations:
```go
// File operations
app.KeyBinding.Add("Ctrl+N", func(window application.Window) {
// New file
window.EmitEvent("file:new", nil)
})
app.KeyBinding.Add("Ctrl+O", func(window application.Window) {
// Open file
dialog := app.Dialog.OpenFile()
if file, err := dialog.PromptForSingleSelection(); err == nil {
window.EmitEvent("file:open", file)
}
})
app.KeyBinding.Add("Ctrl+S", func(window application.Window) {
// Save file
window.EmitEvent("file:save", nil)
})
// Edit operations
app.KeyBinding.Add("Ctrl+Z", func(window application.Window) {
// Undo
window.EmitEvent("edit:undo", nil)
})
app.KeyBinding.Add("Ctrl+Y", func(window application.Window) {
// Redo (Windows/Linux)
window.EmitEvent("edit:redo", nil)
})
app.KeyBinding.Add("Cmd+Shift+Z", func(window application.Window) {
// Redo (macOS)
window.EmitEvent("edit:redo", nil)
})
```
## Key Binding Accelerators
### Accelerator Format
Key bindings use a standard accelerator format with modifiers and keys:
```go
// Modifier keys
"Ctrl+S" // Control + S
"Cmd+S" // Command + S (macOS)
"Alt+F4" // Alt + F4
"Shift+Ctrl+Z" // Shift + Control + Z
// Function keys
"F1" // F1 key
"Ctrl+F5" // Control + F5
// Special keys
"Escape" // Escape key
"Enter" // Enter key
"Space" // Spacebar
"Tab" // Tab key
"Backspace" // Backspace key
"Delete" // Delete key
// Arrow keys
"Up" // Up arrow
"Down" // Down arrow
"Left" // Left arrow
"Right" // Right arrow
```
### Platform-Specific Accelerators
Handle platform differences for common shortcuts:
```go
import "runtime"
// Cross-platform save shortcut
if runtime.GOOS == "darwin" {
app.KeyBinding.Add("Cmd+S", saveHandler)
} else {
app.KeyBinding.Add("Ctrl+S", saveHandler)
}
// Or register both
app.KeyBinding.Add("Ctrl+S", saveHandler)
app.KeyBinding.Add("Cmd+S", saveHandler)
```
## Managing Key Bindings
### Removing Key Bindings
Remove key bindings when they're no longer needed:
```go
// Remove a specific key binding
app.KeyBinding.Remove("Ctrl+S")
// Example: Temporary key binding for a modal
app.KeyBinding.Add("Escape", func(window application.Window) {
// Close modal
window.EmitEvent("modal:close", nil)
// Remove this temporary binding
app.KeyBinding.Remove("Escape")
})
```
### Getting All Key Bindings
Retrieve all registered key bindings:
```go
allBindings := app.KeyBinding.GetAll()
for _, binding := range allBindings {
app.Logger.Info("Key binding", "accelerator", binding.Accelerator)
}
```
## Advanced Usage
### Context-Aware Key Bindings
Make key bindings context-aware by checking application state:
```go
app.KeyBinding.Add("Ctrl+S", func(window application.Window) {
// Check current application state
if isEditMode() {
// Save document
saveDocument()
} else if isInSettings() {
// Save settings
saveSettings()
} else {
app.Logger.Info("Save not available in current context")
}
})
```
### Window-Specific Actions
Key bindings receive the active window, allowing window-specific behavior:
```go
app.KeyBinding.Add("F11", func(window application.Window) {
// Toggle fullscreen for the active window
window.ToggleFullscreen()
})
app.KeyBinding.Add("Ctrl+W", func(window application.Window) {
// Close the active window
window.Close()
})
```
### Dynamic Key Binding Management
Dynamically add and remove key bindings based on application state:
```go
func enableEditMode() {
// Add edit-specific key bindings
app.KeyBinding.Add("Ctrl+B", func(window application.Window) {
window.EmitEvent("format:bold", nil)
})
app.KeyBinding.Add("Ctrl+I", func(window application.Window) {
window.EmitEvent("format:italic", nil)
})
app.KeyBinding.Add("Ctrl+U", func(window application.Window) {
window.EmitEvent("format:underline", nil)
})
}
func disableEditMode() {
// Remove edit-specific key bindings
app.KeyBinding.Remove("Ctrl+B")
app.KeyBinding.Remove("Ctrl+I")
app.KeyBinding.Remove("Ctrl+U")
}
```
## Platform Considerations
<Tabs>
<TabItem label="macOS" icon="fa-brands:apple">
On macOS:
- Use `Cmd` instead of `Ctrl` for standard shortcuts
- `Cmd+Q` is typically reserved for quitting the application
- `Cmd+H` hides the application
- `Cmd+M` minimizes windows
- Consider standard macOS keyboard shortcuts
Common macOS patterns:
```go
app.KeyBinding.Add("Cmd+N", newFileHandler) // New
app.KeyBinding.Add("Cmd+O", openFileHandler) // Open
app.KeyBinding.Add("Cmd+S", saveFileHandler) // Save
app.KeyBinding.Add("Cmd+Z", undoHandler) // Undo
app.KeyBinding.Add("Cmd+Shift+Z", redoHandler) // Redo
app.KeyBinding.Add("Cmd+C", copyHandler) // Copy
app.KeyBinding.Add("Cmd+V", pasteHandler) // Paste
```
</TabItem>
<TabItem label="Windows" icon="fa-brands:windows">
On Windows:
- Use `Ctrl` for standard shortcuts
- `Alt+F4` closes applications
- `F1` typically opens help
- Consider Windows keyboard conventions
Common Windows patterns:
```go
app.KeyBinding.Add("Ctrl+N", newFileHandler) // New
app.KeyBinding.Add("Ctrl+O", openFileHandler) // Open
app.KeyBinding.Add("Ctrl+S", saveFileHandler) // Save
app.KeyBinding.Add("Ctrl+Z", undoHandler) // Undo
app.KeyBinding.Add("Ctrl+Y", redoHandler) // Redo
app.KeyBinding.Add("Ctrl+C", copyHandler) // Copy
app.KeyBinding.Add("Ctrl+V", pasteHandler) // Paste
app.KeyBinding.Add("F1", helpHandler) // Help
```
</TabItem>
<TabItem label="Linux" icon="fa-brands:linux">
On Linux:
- Generally follows Windows conventions with `Ctrl`
- May vary by desktop environment
- Consider GNOME/KDE standard shortcuts
- Some desktop environments reserve certain shortcuts
Common Linux patterns:
```go
app.KeyBinding.Add("Ctrl+N", newFileHandler) // New
app.KeyBinding.Add("Ctrl+O", openFileHandler) // Open
app.KeyBinding.Add("Ctrl+S", saveFileHandler) // Save
app.KeyBinding.Add("Ctrl+Z", undoHandler) // Undo
app.KeyBinding.Add("Ctrl+Shift+Z", redoHandler) // Redo
app.KeyBinding.Add("Ctrl+C", copyHandler) // Copy
app.KeyBinding.Add("Ctrl+V", pasteHandler) // Paste
```
</TabItem>
</Tabs>
## Best Practices
1. **Use Standard Shortcuts**: Follow platform conventions for common operations:
```go
// Cross-platform save
if runtime.GOOS == "darwin" {
app.KeyBinding.Add("Cmd+S", saveHandler)
} else {
app.KeyBinding.Add("Ctrl+S", saveHandler)
}
```
2. **Provide Visual Feedback**: Let users know when shortcuts are triggered:
```go
app.KeyBinding.Add("Ctrl+S", func(window application.Window) {
saveDocument()
// Show brief notification
window.EmitEvent("notification:show", "Document saved")
})
```
3. **Handle Conflicts**: Be careful not to override important system shortcuts:
```go
// Avoid overriding system shortcuts like:
// Ctrl+Alt+Del (Windows)
// Cmd+Space (macOS Spotlight)
// Alt+Tab (Window switching)
```
4. **Document Shortcuts**: Provide help or documentation for available shortcuts:
```go
app.KeyBinding.Add("F1", func(window application.Window) {
// Show help dialog with available shortcuts
showKeyboardShortcutsHelp()
})
```
5. **Clean Up**: Remove temporary key bindings when they're no longer needed:
```go
func enterEditMode() {
app.KeyBinding.Add("Escape", exitEditModeHandler)
}
func exitEditModeHandler(window application.Window) {
exitEditMode()
app.KeyBinding.Remove("Escape") // Clean up temporary binding
}
```
## Complete Example
Here's a complete example of a text editor with keyboard shortcuts:
```go
package main
import (
"runtime"
"github.com/wailsapp/wails/v3/pkg/application"
)
func main() {
app := application.New(application.Options{
Name: "Text Editor with Shortcuts",
})
// File operations
if runtime.GOOS == "darwin" {
app.KeyBinding.Add("Cmd+N", func(window application.Window) {
window.EmitEvent("file:new", nil)
})
app.KeyBinding.Add("Cmd+O", func(window application.Window) {
openFile(app, window)
})
app.KeyBinding.Add("Cmd+S", func(window application.Window) {
window.EmitEvent("file:save", nil)
})
} else {
app.KeyBinding.Add("Ctrl+N", func(window application.Window) {
window.EmitEvent("file:new", nil)
})
app.KeyBinding.Add("Ctrl+O", func(window application.Window) {
openFile(app, window)
})
app.KeyBinding.Add("Ctrl+S", func(window application.Window) {
window.EmitEvent("file:save", nil)
})
}
// View operations
app.KeyBinding.Add("F11", func(window application.Window) {
window.ToggleFullscreen()
})
app.KeyBinding.Add("F1", func(window application.Window) {
showKeyboardShortcuts(window)
})
// Create main window
window := app.Window.New()
window.SetTitle("Text Editor")
err := app.Run()
if err != nil {
panic(err)
}
}
func openFile(app *application.App, window application.Window) {
dialog := app.Dialog.OpenFile()
dialog.AddFilter("Text Files", "*.txt;*.md")
if file, err := dialog.PromptForSingleSelection(); err == nil {
window.EmitEvent("file:open", file)
}
}
func showKeyboardShortcuts(window application.Window) {
shortcuts := `
Keyboard Shortcuts:
- Ctrl/Cmd+N: New file
- Ctrl/Cmd+O: Open file
- Ctrl/Cmd+S: Save file
- F11: Toggle fullscreen
- F1: Show this help
`
window.EmitEvent("help:show", shortcuts)
}
```
:::tip[Pro Tip]
Test your key bindings on all target platforms to ensure they work correctly and don't conflict with system shortcuts.
:::
:::danger[Warning]
Be careful not to override critical system shortcuts. Some key combinations are reserved by the operating system and cannot be captured by applications.
:::

View file

@ -0,0 +1,644 @@
---
title: Application Menus
description: Create native menu bars for your desktop application
sidebar:
order: 1
---
import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components";
## The Problem
Professional desktop applications need menu bars—File, Edit, View, Help. But menus work differently on each platform:
- **macOS**: Global menu bar at top of screen
- **Windows**: Menu bar in window title bar
- **Linux**: Varies by desktop environment
Building platform-appropriate menus manually is tedious and error-prone.
## The Wails Solution
Wails provides a **unified API** that creates platform-native menus automatically. Write once, get native behaviour on all platforms.
{/* VISUAL PLACEHOLDER: Menu Bar Comparison
Description: Three screenshots side-by-side showing the same Wails menu on:
1. macOS - Global menu bar at top of screen with app name
2. Windows - Menu bar in window title bar
3. Linux (GNOME) - Menu bar in window
All showing identical menu structure: File, Edit, View, Tools, Help
Style: Clean screenshots with subtle borders, labels indicating platform
*/}
## Quick Start
```go
package main
import (
"runtime"
"github.com/wailsapp/wails/v3/pkg/application"
)
func main() {
app := application.New(application.Options{
Name: "My App",
})
// Create menu
menu := application.NewMenu()
// Add standard menus (platform-appropriate)
if runtime.GOOS == "darwin" {
menu.AddRole(application.AppMenu) // macOS only
}
menu.AddRole(application.FileMenu)
menu.AddRole(application.EditMenu)
menu.AddRole(application.WindowMenu)
menu.AddRole(application.HelpMenu)
// Set the menu
app.Menu.Set(menu)
// Create window and run
app.Window.New()
app.Run()
}
```
**That's it!** You now have platform-native menus with standard items.
## Creating Menus
### Basic Menu Creation
```go
// Create a new menu
menu := application.NewMenu()
// Add a top-level menu
fileMenu := menu.AddSubmenu("File")
// Add menu items
fileMenu.Add("New").OnClick(func(ctx *application.Context) {
// Handle New
})
fileMenu.Add("Open").OnClick(func(ctx *application.Context) {
// Handle Open
})
fileMenu.AddSeparator()
fileMenu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()
})
```
### Setting the Menu
Platform-specific API:
<Tabs syncKey="platform">
<TabItem label="macOS" icon="apple">
**Global menu bar** (one per application):
```go
app.Menu.Set(menu)
```
The menu appears at the top of the screen and persists even when all windows are closed.
</TabItem>
<TabItem label="Windows" icon="seti:windows">
**Per-window menu bar**:
```go
window.SetMenu(menu)
```
Each window can have its own menu. The menu appears in the window's title bar.
</TabItem>
<TabItem label="Linux" icon="linux">
**Per-window menu bar** (usually):
```go
window.SetMenu(menu)
```
Behaviour varies by desktop environment. Some (like Unity) support global menus.
</TabItem>
</Tabs>
**Cross-platform helper:**
```go
func setMenuForPlatform(app *application.App, window application.Window, menu *application.Menu) {
if runtime.GOOS == "darwin" {
app.Menu.Set(menu)
} else {
window.SetMenu(menu)
}
}
```
## Menu Roles
Wails provides **predefined menu roles** that create platform-appropriate menu structures automatically.
### Available Roles
| Role | Description | Platform Notes |
|------|-------------|----------------|
| `AppMenu` | Application menu with About, Services, Hide/Show, and Quit | **macOS only** |
| `FileMenu` | File menu with Close Window (macOS) or Quit (Windows/Linux) | All platforms |
| `EditMenu` | Text editing (Undo, Redo, Cut, Copy, Paste, etc.) | All platforms |
| `ViewMenu` | View menu with Reload, Zoom, and Fullscreen controls | All platforms |
| `WindowMenu` | Window management (Minimise, Zoom, etc.) | All platforms |
| `ServicesMenu` | macOS Services submenu | **macOS only** |
| `SpeechMenu` | Speech menu with Start/Stop Speaking | **macOS only** |
| `HelpMenu` | Help and information | All platforms |
### Using Roles
```go
menu := application.NewMenu()
// macOS: Add application menu
if runtime.GOOS == "darwin" {
menu.AddRole(application.AppMenu)
}
// All platforms: Add standard menus
menu.AddRole(application.FileMenu)
menu.AddRole(application.EditMenu)
menu.AddRole(application.WindowMenu)
menu.AddRole(application.HelpMenu)
```
**What you get:**
<Tabs syncKey="platform">
<TabItem label="macOS" icon="apple">
**AppMenu** (with app name):
- About [App Name]
- ---
- Services
- ---
- Hide [App Name] (⌘H)
- Hide Others (⌥⌘H)
- Unhide
- ---
- Quit [App Name] (⌘Q)
**FileMenu**:
- Close Window (⌘W)
**EditMenu**:
- Undo (⌘Z)
- Redo (⇧⌘Z)
- ---
- Cut (⌘X)
- Copy (⌘C)
- Paste (⌘V)
- Paste and Match Style
- Delete
- Select All (⌘A)
- ---
- Speech
**WindowMenu**:
- Minimise (⌘M)
- Zoom
- ---
- Bring All to Front
**HelpMenu**:
- Learn More
</TabItem>
<TabItem label="Windows" icon="seti:windows">
**FileMenu**:
- Quit
**EditMenu**:
- Undo (Ctrl+Z)
- Redo (Ctrl+Y)
- ---
- Cut (Ctrl+X)
- Copy (Ctrl+C)
- Paste (Ctrl+V)
- Delete
- ---
- Select All (Ctrl+A)
**WindowMenu**:
- Minimise
- Zoom
- Close Window
**HelpMenu**:
- Learn More
</TabItem>
<TabItem label="Linux" icon="linux">
Similar to Windows, but keyboard shortcuts may vary by desktop environment.
</TabItem>
</Tabs>
### Customising Role Menus
Add items to role menus by finding the submenu after adding the role:
```go
menu.AddRole(application.FileMenu)
// Find the File submenu and add custom items
fileMenu := menu.FindByLabel("File").GetSubmenu()
fileMenu.Add("Import...").OnClick(handleImport)
fileMenu.Add("Export...").OnClick(handleExport)
```
Note: `AddRole()` returns the receiver `*Menu` (for chaining), not the role submenu. Use `FindByLabel()` to access the submenu.
## Custom Menus
Create your own menus for application-specific features:
```go
// Add a custom top-level menu
toolsMenu := menu.AddSubmenu("Tools")
// Add items
toolsMenu.Add("Settings").OnClick(func(ctx *application.Context) {
showSettingsWindow()
})
toolsMenu.AddSeparator()
// Add checkbox
toolsMenu.AddCheckbox("Dark Mode", false).OnClick(func(ctx *application.Context) {
isDark := ctx.ClickedMenuItem().Checked()
setTheme(isDark)
})
// Add radio group
toolsMenu.AddRadio("Small", true).OnClick(handleFontSize)
toolsMenu.AddRadio("Medium", false).OnClick(handleFontSize)
toolsMenu.AddRadio("Large", false).OnClick(handleFontSize)
// Add submenu
advancedMenu := toolsMenu.AddSubmenu("Advanced")
advancedMenu.Add("Configure...").OnClick(showAdvancedSettings)
```
**For more menu item types**, see [Menu Reference](/features/menus/reference).
## Dynamic Menus
Update menus based on application state:
### Enable/Disable Items
```go
var saveMenuItem *application.MenuItem
func createMenu() {
menu := application.NewMenu()
fileMenu := menu.AddSubmenu("File")
saveMenuItem = fileMenu.Add("Save")
saveMenuItem.SetEnabled(false) // Initially disabled
saveMenuItem.OnClick(handleSave)
app.Menu.Set(menu)
}
func onDocumentChanged() {
saveMenuItem.SetEnabled(hasUnsavedChanges())
menu.Update() // Important!
}
```
:::caution[Always Call menu.Update()]
After changing menu state (enable/disable, label, checked), **always call `menu.Update()`**. This is especially critical on Windows where menus are reconstructed.
See [Menu Reference](/features/menus/reference#enabled-state) for details.
:::
### Change Labels
```go
updateMenuItem := menu.Add("Check for Updates")
updateMenuItem.OnClick(func(ctx *application.Context) {
updateMenuItem.SetLabel("Checking...")
menu.Update()
checkForUpdates()
updateMenuItem.SetLabel("Check for Updates")
menu.Update()
})
```
### Rebuild Menus
For major changes, rebuild the entire menu:
```go
func rebuildFileMenu() {
menu := application.NewMenu()
fileMenu := menu.AddSubmenu("File")
fileMenu.Add("New").OnClick(handleNew)
fileMenu.Add("Open").OnClick(handleOpen)
// Add recent files dynamically
if hasRecentFiles() {
recentMenu := fileMenu.AddSubmenu("Open Recent")
for _, file := range getRecentFiles() {
filePath := file // Capture for closure
recentMenu.Add(filepath.Base(file)).OnClick(func(ctx *application.Context) {
openFile(filePath)
})
}
recentMenu.AddSeparator()
recentMenu.Add("Clear Recent").OnClick(clearRecentFiles)
}
fileMenu.AddSeparator()
fileMenu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()
})
app.Menu.Set(menu)
}
```
## Window Control from Menus
Menu items can control windows:
```go
viewMenu := menu.AddSubmenu("View")
// Toggle fullscreen
viewMenu.Add("Toggle Fullscreen").OnClick(func(ctx *application.Context) {
window, _ := app.Window.GetByName("main")
window.ToggleFullscreen()
})
// Zoom controls
viewMenu.Add("Zoom In").SetAccelerator("CmdOrCtrl++").OnClick(func(ctx *application.Context) {
// Increase zoom
})
viewMenu.Add("Zoom Out").SetAccelerator("CmdOrCtrl+-").OnClick(func(ctx *application.Context) {
// Decrease zoom
})
viewMenu.Add("Reset Zoom").SetAccelerator("CmdOrCtrl+0").OnClick(func(ctx *application.Context) {
// Reset zoom
})
```
**Get the active window:**
```go
menuItem.OnClick(func(ctx *application.Context) {
window := app.Window.Current()
// Use window
})
```
## Platform-Specific Considerations
### macOS
**Menu bar behaviour:**
- Appears at **top of screen** (global)
- Persists when all windows closed
- First menu is **always the application menu**
- Use `menu.AddRole(application.AppMenu)` for standard items
**Standard locations:**
- **About**: Application menu
- **Quit**: Application menu (⌘Q)
- **Help**: Help menu
**Example:**
```go
if runtime.GOOS == "darwin" {
menu.AddRole(application.AppMenu) // Adds About, Services, Hide/Show, Quit
// Don't add Quit to File menu on macOS
// Don't add About to Help menu on macOS
}
```
### Windows
**Menu bar behaviour:**
- Appears in **window title bar**
- Each window has its own menu
- No application menu
**Standard locations:**
- **Exit**: File menu (Alt+F4)
- **Settings**: Tools or Edit menu
- **About**: Help menu
**Example:**
```go
if runtime.GOOS == "windows" {
menu.AddRole(application.FileMenu)
// Exit is added automatically
menu.AddRole(application.HelpMenu)
// About is added automatically
}
```
### Linux
**Menu bar behaviour:**
- Usually per-window (like Windows)
- Some DEs support global menus (Unity, GNOME with extension)
- Appearance varies by desktop environment
**Best practice:** Follow Windows conventions, test on target DEs.
## Complete Example
Here's a production-ready menu structure:
```go
package main
import (
"runtime"
"github.com/wailsapp/wails/v3/pkg/application"
)
func main() {
app := application.New(application.Options{
Name: "My Application",
})
// Create and set menu
createMenu(app)
// Create main window
app.Window.New()
app.Run()
}
func createMenu(app *application.App) {
menu := application.NewMenu()
// Platform-specific application menu (macOS only)
if runtime.GOOS == "darwin" {
menu.AddRole(application.AppMenu)
}
// File menu
menu.AddRole(application.FileMenu)
fileMenu := menu.FindByLabel("File").GetSubmenu()
fileMenu.Add("Import...").SetAccelerator("CmdOrCtrl+I").OnClick(handleImport)
fileMenu.Add("Export...").SetAccelerator("CmdOrCtrl+E").OnClick(handleExport)
// Edit menu
menu.AddRole(application.EditMenu)
// View menu
viewMenu := menu.AddSubmenu("View")
viewMenu.Add("Toggle Fullscreen").SetAccelerator("F11").OnClick(toggleFullscreen)
viewMenu.AddSeparator()
viewMenu.AddCheckbox("Show Sidebar", true).OnClick(toggleSidebar)
viewMenu.AddCheckbox("Show Toolbar", true).OnClick(toggleToolbar)
// Tools menu
toolsMenu := menu.AddSubmenu("Tools")
// Settings location varies by platform convention
if runtime.GOOS != "darwin" {
toolsMenu.Add("Settings").SetAccelerator("CmdOrCtrl+,").OnClick(showSettings)
}
toolsMenu.AddSeparator()
toolsMenu.AddCheckbox("Dark Mode", false).OnClick(toggleDarkMode)
// Window menu
menu.AddRole(application.WindowMenu)
// Help menu
menu.AddRole(application.HelpMenu)
helpMenu := menu.FindByLabel("Help").GetSubmenu()
helpMenu.Add("Documentation").OnClick(openDocumentation)
// About location varies by platform
if runtime.GOOS == "darwin" {
// On macOS, About is in Application menu (added by AppMenu role)
} else {
helpMenu.AddSeparator()
helpMenu.Add("About").OnClick(showAbout)
}
// Set the menu
app.Menu.Set(menu)
}
func handleImport(ctx *application.Context) {
// Implementation
}
func handleExport(ctx *application.Context) {
// Implementation
}
func toggleFullscreen(ctx *application.Context) {
// Use a reference to the window captured during menu setup
}
func toggleSidebar(ctx *application.Context) {
// Implementation
}
func toggleToolbar(ctx *application.Context) {
// Implementation
}
func showSettings(ctx *application.Context) {
// Implementation
}
func toggleDarkMode(ctx *application.Context) {
isDark := ctx.ClickedMenuItem().Checked()
// Apply theme
}
func openDocumentation(ctx *application.Context) {
// Open browser
}
func showAbout(ctx *application.Context) {
// Show about dialog
}
```
## Best Practices
### ✅ Do
- **Use menu roles** for standard menus (File, Edit, etc.)
- **Follow platform conventions** for menu structure
- **Add keyboard shortcuts** to common actions
- **Call menu.Update()** after changing menu state
- **Test on all platforms** - behaviour varies
- **Keep menus shallow** - 2-3 levels maximum
- **Use clear labels** - "Save Project" not "Save"
### ❌ Don't
- **Don't hardcode platform shortcuts** - Use `CmdOrCtrl`
- **Don't put Quit in File menu on macOS** - It's in Application menu
- **Don't put About in Help menu on macOS** - It's in Application menu
- **Don't forget menu.Update()** - Menus won't work properly
- **Don't nest too deeply** - Users get lost
- **Don't use jargon** - Keep labels user-friendly
## Next Steps
<CardGrid>
<Card title="Menu Reference" icon="document">
Complete reference for menu item types and properties.
[Learn More →](/features/menus/reference)
</Card>
<Card title="Context Menus" icon="puzzle">
Create right-click context menus.
[Learn More →](/features/menus/context)
</Card>
<Card title="System Tray Menus" icon="star">
Add system tray/menu bar integration.
[Learn More →](/features/menus/systray)
</Card>
<Card title="Menu Patterns" icon="open-book">
Common menu patterns and best practices.
[Learn More →](/guides/patterns/menus)
</Card>
</CardGrid>
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [menu example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/menu).

View file

@ -0,0 +1,719 @@
---
title: Context Menus
description: Create right-click context menus for your application
sidebar:
order: 2
---
import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components";
## The Problem
Users expect right-click menus with context-specific actions. Different elements need different menus:
- **Text**: Cut, Copy, Paste
- **Images**: Save, Copy, Open
- **Custom elements**: Application-specific actions
Building context menus manually means handling mouse events, positioning, and platform differences.
## The Wails Solution
Wails provides **declarative context menus** using CSS properties. Associate menus with HTML elements, pass data, and handle clicks—all with native platform behaviour.
## Quick Start
**Go code:**
```go
// Create and register context menu (auto-registered by name)
contextMenu := application.NewContextMenu("editor-menu")
contextMenu.Add("Cut").OnClick(handleCut)
contextMenu.Add("Copy").OnClick(handleCopy)
contextMenu.Add("Paste").OnClick(handlePaste)
contextMenu.Update()
```
**HTML:**
```html
<textarea style="--custom-contextmenu: editor-menu">
Right-click me!
</textarea>
```
**That's it!** Right-clicking the textarea shows your custom menu.
## Creating Context Menus
### Basic Context Menu
```go
// Create menu (auto-registered by the name passed to NewContextMenu)
contextMenu := application.NewContextMenu("text-menu")
// Add items
contextMenu.Add("Cut").SetAccelerator("CmdOrCtrl+X").OnClick(func(ctx *application.Context) {
// Handle cut
})
contextMenu.Add("Copy").SetAccelerator("CmdOrCtrl+C").OnClick(func(ctx *application.Context) {
// Handle copy
})
contextMenu.Add("Paste").SetAccelerator("CmdOrCtrl+V").OnClick(func(ctx *application.Context) {
// Handle paste
})
contextMenu.Update()
```
**Menu name:** Must be unique. Used to associate menu with HTML elements.
### With Submenus
```go
contextMenu := application.NewContextMenu("image-menu")
// Add regular items
contextMenu.Add("Open").OnClick(handleOpen)
contextMenu.Add("Delete").OnClick(handleDelete)
contextMenu.AddSeparator()
// Add submenu
exportMenu := contextMenu.AddSubmenu("Export As")
exportMenu.Add("PNG").OnClick(exportPNG)
exportMenu.Add("JPEG").OnClick(exportJPEG)
exportMenu.Add("SVG").OnClick(exportSVG)
contextMenu.Update()
```
### With Checkboxes and Radio Groups
```go
contextMenu := application.NewContextMenu("view-menu")
// Checkbox
contextMenu.AddCheckbox("Show Grid", true).OnClick(func(ctx *application.Context) {
showGrid := ctx.ClickedMenuItem().Checked()
// Toggle grid
})
contextMenu.AddSeparator()
// Radio group
contextMenu.AddRadio("Small", false).OnClick(handleSize)
contextMenu.AddRadio("Medium", true).OnClick(handleSize)
contextMenu.AddRadio("Large", false).OnClick(handleSize)
contextMenu.Update()
```
**For all menu item types**, see [Menu Reference](/features/menus/reference).
## Associating with HTML Elements
Use CSS custom properties to attach context menus:
### Basic Association
```html
<div style="--custom-contextmenu: menu-id">
Right-click me!
</div>
```
**CSS property:** `--custom-contextmenu: <menu-id>`
### With Context Data
Pass data from HTML to Go:
```html
<div style="--custom-contextmenu: file-menu; --custom-contextmenu-data: file-123">
Right-click this file
</div>
```
**Go handler:**
```go
contextMenu := application.NewContextMenu("file-menu")
contextMenu.Add("Open").OnClick(func(ctx *application.Context) {
fileID := ctx.ContextMenuData() // "file-123"
openFile(fileID)
})
contextMenu.Update()
```
**CSS properties:**
- `--custom-contextmenu: <menu-id>` - Which menu to show
- `--custom-contextmenu-data: <data>` - Data to pass to handlers
### Dynamic Data
Generate data dynamically in JavaScript:
```html
<div id="file-item" style="--custom-contextmenu: file-menu">
File.txt
</div>
<script>
// Set data dynamically
const fileItem = document.getElementById('file-item')
fileItem.style.setProperty('--custom-contextmenu-data', 'file-' + fileId)
</script>
```
### Multiple Elements, Same Menu
```html
<div class="file-item" style="--custom-contextmenu: file-menu; --custom-contextmenu-data: file-1">
Document.pdf
</div>
<div class="file-item" style="--custom-contextmenu: file-menu; --custom-contextmenu-data: file-2">
Image.png
</div>
<div class="file-item" style="--custom-contextmenu: file-menu; --custom-contextmenu-data: file-3">
Video.mp4
</div>
```
**One menu, different data for each element.**
## Context Data
### Accessing Context Data
```go
contextMenu.Add("Process").OnClick(func(ctx *application.Context) {
data := ctx.ContextMenuData() // Get data from HTML
// Use the data
processItem(data)
})
```
**Data type:** Always `string`. Parse as needed.
### Passing Complex Data
Use JSON for complex data:
```html
<div style="--custom-contextmenu: item-menu; --custom-contextmenu-data: {&quot;id&quot;:123,&quot;type&quot;:&quot;image&quot;}">
Image.png
</div>
```
**Go handler:**
```go
import "encoding/json"
type ItemData struct {
ID int `json:"id"`
Type string `json:"type"`
}
contextMenu.Add("Process").OnClick(func(ctx *application.Context) {
dataStr := ctx.ContextMenuData()
var data ItemData
if err := json.Unmarshal([]byte(dataStr), &data); err != nil {
log.Printf("Invalid data: %v", err)
return
}
processItem(data.ID, data.Type)
})
```
:::caution[Security]
**Always validate context data** from the frontend. Users can manipulate CSS properties, so treat data as untrusted input.
:::
### Validation Example
```go
contextMenu.Add("Delete").OnClick(func(ctx *application.Context) {
fileID := ctx.ContextMenuData()
// Validate
if !isValidFileID(fileID) {
log.Printf("Invalid file ID: %s", fileID)
return
}
// Check permissions
if !canDeleteFile(fileID) {
showError("Permission denied")
return
}
// Safe to proceed
deleteFile(fileID)
})
```
## Default Context Menu
The WebView provides a built-in context menu for standard operations (copy, paste, inspect). Control it with `--default-contextmenu`:
### Hide Default Menu
```html
<div style="--default-contextmenu: hide">
No default menu here
</div>
```
**Use case:** Custom UI elements where default menu doesn't make sense.
### Show Default Menu
```html
<div style="--default-contextmenu: show">
Default menu always shown
</div>
```
**Use case:** Text areas, input fields, editable content.
### Auto (Smart) Mode
```html
<div style="--default-contextmenu: auto">
Smart context menu
</div>
```
**Default behaviour.** Shows default menu when:
- Text is selected
- In text input fields
- In editable content (`contenteditable`)
Hides default menu otherwise.
### Combining Custom and Default
```html
<!-- Custom menu + default menu -->
<textarea style="--custom-contextmenu: editor-menu; --default-contextmenu: show">
Both menus available
</textarea>
```
**Behaviour:**
1. Custom menu shows first
2. If custom menu is empty or not found, default menu shows
3. Both can coexist (platform-dependent)
## Dynamic Context Menus
Update menus based on application state:
### Enable/Disable Items
```go
var cutMenuItem *application.MenuItem
var copyMenuItem *application.MenuItem
func createContextMenu() {
contextMenu := application.NewContextMenu("editor-menu")
cutMenuItem = contextMenu.Add("Cut")
cutMenuItem.SetEnabled(false) // Initially disabled
cutMenuItem.OnClick(handleCut)
copyMenuItem = contextMenu.Add("Copy")
copyMenuItem.SetEnabled(false)
copyMenuItem.OnClick(handleCopy)
contextMenu.Update()
}
func onSelectionChanged(hasSelection bool) {
cutMenuItem.SetEnabled(hasSelection)
copyMenuItem.SetEnabled(hasSelection)
contextMenu.Update() // Important!
}
```
:::caution[Always Call Update()]
After changing menu state, **call `contextMenu.Update()`**. This is critical on Windows.
See [Menu Reference](/features/menus/reference#enabled-state) for details.
:::
### Change Labels
```go
playMenuItem := contextMenu.Add("Play")
playMenuItem.OnClick(func(ctx *application.Context) {
if isPlaying {
playMenuItem.SetLabel("Pause")
} else {
playMenuItem.SetLabel("Play")
}
contextMenu.Update()
})
```
### Rebuild Menus
For major changes, rebuild the entire menu:
```go
func rebuildContextMenu(fileType string) {
contextMenu := application.NewContextMenu("file-menu")
// Common items
contextMenu.Add("Open").OnClick(handleOpen)
contextMenu.Add("Delete").OnClick(handleDelete)
contextMenu.AddSeparator()
// Type-specific items
switch fileType {
case "image":
contextMenu.Add("Edit Image").OnClick(editImage)
contextMenu.Add("Set as Wallpaper").OnClick(setWallpaper)
case "video":
contextMenu.Add("Play").OnClick(playVideo)
contextMenu.Add("Extract Audio").OnClick(extractAudio)
case "document":
contextMenu.Add("Print").OnClick(printDocument)
contextMenu.Add("Export PDF").OnClick(exportPDF)
}
contextMenu.Update()
}
```
## Platform Behaviour
Context menus are **platform-native**:
<Tabs syncKey="platform">
<TabItem label="macOS" icon="apple">
**Native macOS context menus:**
- System animations and transitions
- Right-click = Control+Click (automatic)
- Adapts to system appearance (light/dark)
- Standard text operations in default menu
- Native scrolling for long menus
**macOS conventions:**
- Use sentence case for menu items
- Use ellipsis (...) for items that open dialogs
- Common shortcuts: ⌘C (Copy), ⌘V (Paste)
</TabItem>
<TabItem label="Windows" icon="seti:windows">
**Native Windows context menus:**
- Windows native style
- Follows Windows theme
- Standard Windows operations in default menu
- Touch and pen input support
**Windows conventions:**
- Use title case for menu items
- Use ellipsis (...) for items that open dialogs
- Common shortcuts: Ctrl+C (Copy), Ctrl+V (Paste)
</TabItem>
<TabItem label="Linux" icon="linux">
**Desktop environment integration:**
- Adapts to desktop theme (GTK, Qt, etc.)
- Right-click behaviour follows system settings
- Default menu content varies by environment
- Positioning follows DE conventions
**Linux considerations:**
- Test on target desktop environments
- GTK and Qt have different behaviours
- Some DEs customise context menus
</TabItem>
</Tabs>
## Complete Example
**Go code:**
```go
package main
import (
"encoding/json"
"log"
"github.com/wailsapp/wails/v3/pkg/application"
)
type FileData struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
}
func main() {
app := application.New(application.Options{
Name: "Context Menu Demo",
})
// Create context menus (auto-registered by name)
createFileMenu()
createImageMenu()
createTextMenu()
app.Window.New()
app.Run()
}
func createFileMenu() {
menu := application.NewContextMenu("file-menu")
menu.Add("Open").OnClick(func(ctx *application.Context) {
data := parseFileData(ctx.ContextMenuData())
openFile(data.ID)
})
menu.Add("Rename").OnClick(func(ctx *application.Context) {
data := parseFileData(ctx.ContextMenuData())
renameFile(data.ID)
})
menu.AddSeparator()
menu.Add("Delete").OnClick(func(ctx *application.Context) {
data := parseFileData(ctx.ContextMenuData())
deleteFile(data.ID)
})
menu.Update()
}
func createImageMenu() {
menu := application.NewContextMenu("image-menu")
menu.Add("View").OnClick(func(ctx *application.Context) {
data := parseFileData(ctx.ContextMenuData())
viewImage(data.ID)
})
menu.Add("Edit").OnClick(func(ctx *application.Context) {
data := parseFileData(ctx.ContextMenuData())
editImage(data.ID)
})
menu.AddSeparator()
exportMenu := menu.AddSubmenu("Export As")
exportMenu.Add("PNG").OnClick(exportPNG)
exportMenu.Add("JPEG").OnClick(exportJPEG)
exportMenu.Add("WebP").OnClick(exportWebP)
menu.Update()
}
func createTextMenu() {
menu := application.NewContextMenu("text-menu")
menu.Add("Cut").SetAccelerator("CmdOrCtrl+X").OnClick(handleCut)
menu.Add("Copy").SetAccelerator("CmdOrCtrl+C").OnClick(handleCopy)
menu.Add("Paste").SetAccelerator("CmdOrCtrl+V").OnClick(handlePaste)
menu.Update()
}
func parseFileData(dataStr string) FileData {
var data FileData
if err := json.Unmarshal([]byte(dataStr), &data); err != nil {
log.Printf("Invalid file data: %v", err)
}
return data
}
// Handler implementations...
func openFile(id string) { /* ... */ }
func renameFile(id string) { /* ... */ }
func deleteFile(id string) { /* ... */ }
func viewImage(id string) { /* ... */ }
func editImage(id string) { /* ... */ }
func exportPNG(ctx *application.Context) { /* ... */ }
func exportJPEG(ctx *application.Context) { /* ... */ }
func exportWebP(ctx *application.Context) { /* ... */ }
func handleCut(ctx *application.Context) { /* ... */ }
func handleCopy(ctx *application.Context) { /* ... */ }
func handlePaste(ctx *application.Context) { /* ... */ }
```
**HTML:**
```html
<!DOCTYPE html>
<html>
<head>
<style>
.file-item {
padding: 10px;
margin: 5px;
border: 1px solid #ccc;
cursor: pointer;
}
.file-item:hover {
background: #f0f0f0;
}
textarea {
width: 100%;
height: 200px;
}
</style>
</head>
<body>
<h2>Files</h2>
<!-- Regular file -->
<div class="file-item"
style="--custom-contextmenu: file-menu;
--custom-contextmenu-data: {&quot;id&quot;:&quot;file-1&quot;,&quot;type&quot;:&quot;document&quot;,&quot;name&quot;:&quot;Report.pdf&quot;}">
📄 Report.pdf
</div>
<!-- Image file -->
<div class="file-item"
style="--custom-contextmenu: image-menu;
--custom-contextmenu-data: {&quot;id&quot;:&quot;file-2&quot;,&quot;type&quot;:&quot;image&quot;,&quot;name&quot;:&quot;Photo.jpg&quot;}">
🖼️ Photo.jpg
</div>
<h2>Text Editor</h2>
<!-- Text area with custom menu + default menu -->
<textarea
style="--custom-contextmenu: text-menu; --default-contextmenu: show"
placeholder="Type here, then right-click...">
</textarea>
<h2>No Context Menu</h2>
<!-- Disable default menu -->
<div style="--default-contextmenu: hide; padding: 20px; border: 1px solid #ccc;">
Right-click here - no menu appears
</div>
</body>
</html>
```
## Best Practices
### ✅ Do
- **Keep menus focused** - Only relevant actions for the element
- **Validate context data** - Treat as untrusted input
- **Use clear labels** - "Delete File" not "Delete"
- **Call menu.Update()** - After changing menu state
- **Test on all platforms** - Behaviour varies
- **Provide keyboard shortcuts** - For common actions
- **Group related items** - Use separators
### ❌ Don't
- **Don't trust context data** - Always validate
- **Don't make menus too long** - 7-10 items maximum
- **Don't forget menu.Update()** - Menus won't work properly
- **Don't nest too deeply** - 2 levels maximum
- **Don't use jargon** - Keep labels user-friendly
- **Don't block handlers** - Keep them fast
## Troubleshooting
### Context Menu Not Appearing
**Possible causes:**
1. Menu ID mismatch
2. CSS property typo
3. Runtime not initialised
**Solution:**
```go
// Check menu is created and registered with the correct name
contextMenu := application.NewContextMenu("my-menu")
// ... add items ...
contextMenu.Update()
```
```html
<!-- Check ID matches -->
<div style="--custom-contextmenu: my-menu">
```
### Context Data Not Received
**Possible causes:**
1. CSS property not set
2. Data contains special characters
**Solution:**
```html
<!-- Escape quotes in JSON -->
<div style="--custom-contextmenu-data: {&quot;id&quot;:123}">
```
Or use JavaScript:
```javascript
element.style.setProperty('--custom-contextmenu-data', JSON.stringify(data))
```
### Menu Items Not Responding
**Cause:** Forgot to call `menu.Update()` after enabling
**Solution:**
```go
menuItem.SetEnabled(true)
contextMenu.Update() // Add this!
```
## Next Steps
<CardGrid>
<Card title="Menu Reference" icon="document">
Complete reference for menu item types and properties.
[Learn More →](/features/menus/reference)
</Card>
<Card title="Application Menus" icon="list-format">
Create application menu bars.
[Learn More →](/features/menus/application)
</Card>
<Card title="System Tray Menus" icon="star">
Add system tray/menu bar integration.
[Learn More →](/features/menus/systray)
</Card>
<Card title="Menu Patterns" icon="open-book">
Common menu patterns and best practices.
[Learn More →](/guides/patterns/menus)
</Card>
</CardGrid>
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [context menu example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/contextmenus).

View file

@ -0,0 +1,569 @@
---
title: Menu Reference
description: Complete reference for menu item types, properties, and methods
sidebar:
order: 4
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
## Menu Reference
Complete reference for menu item types, properties, and dynamic behaviour. Build professional, responsive menus with checkboxes, radio groups, separators, and dynamic updates.
## Menu Item Types
### Regular Menu Items
The most common type—displays text and triggers an action:
```go
menuItem := menu.Add("Click Me")
menuItem.OnClick(func(ctx *application.Context) {
fmt.Println("Menu item clicked!")
})
```
**Use for:** Commands, actions, opening windows
### Checkboxes
Toggle-able menu items with checked/unchecked state:
```go
checkbox := menu.AddCheckbox("Enable Feature", true) // true = initially checked
checkbox.OnClick(func(ctx *application.Context) {
isChecked := ctx.ClickedMenuItem().Checked()
fmt.Printf("Feature is now: %v\n", isChecked)
})
```
**Use for:** Boolean settings, feature toggles, view options
**Important:** The checked state toggles automatically when clicked.
### Radio Groups
Mutually exclusive options—only one can be selected:
```go
menu.AddRadio("Small", true) // true = initially selected
menu.AddRadio("Medium", false)
menu.AddRadio("Large", false)
```
**Use for:** Mutually exclusive choices (size, theme, mode)
**How grouping works:**
- Adjacent radio items form a group automatically
- Selecting one deselects others in the group
- Separate groups with a separator or regular item
**Example with multiple groups:**
```go
// Group 1: Size
menu.AddRadio("Small", true)
menu.AddRadio("Medium", false)
menu.AddRadio("Large", false)
menu.AddSeparator()
// Group 2: Theme
menu.AddRadio("Light", true)
menu.AddRadio("Dark", false)
```
### Submenus
Nested menu structures for organisation:
```go
submenu := menu.AddSubmenu("More Options")
submenu.Add("Submenu Item 1").OnClick(func(ctx *application.Context) {
// Handle click
})
submenu.Add("Submenu Item 2")
```
**Use for:** Grouping related items, reducing clutter
**Nesting limit:** Most platforms support 2-3 levels. Avoid deeper nesting.
### Separators
Visual dividers between menu items:
```go
menu.Add("Item 1")
menu.AddSeparator()
menu.Add("Item 2")
```
**Use for:** Grouping related items visually
**Best practice:** Don't start or end menus with separators.
## Menu Item Properties
### Label
The text displayed for the menu item:
```go
menuItem := menu.Add("Initial Label")
menuItem.SetLabel("New Label")
// Get current label
label := menuItem.Label()
```
**Dynamic labels:**
```go
updateMenuItem := menu.Add("Check for Updates")
updateMenuItem.OnClick(func(ctx *application.Context) {
updateMenuItem.SetLabel("Checking...")
menu.Update() // Important on Windows!
// Perform update check
checkForUpdates()
updateMenuItem.SetLabel("Check for Updates")
menu.Update()
})
```
### Enabled State
Control whether the menu item can be interacted with:
```go
menuItem := menu.Add("Save")
menuItem.SetEnabled(false) // Greyed out, can't click
// Enable it later
menuItem.SetEnabled(true)
menu.Update() // Important: Call this after changing enabled state!
// Check current state
isEnabled := menuItem.Enabled()
```
:::caution[Windows Menu Behaviour]
On Windows, menus need to be reconstructed when their state changes. **Always call `menu.Update()` after enabling/disabling menu items**, especially if the item was created whilst disabled.
**Why:** Windows menus are rebuilt from scratch when updated. If you don't call `Update()`, click handlers won't fire properly.
:::
**Example: Dynamic enable/disable**
```go
var hasSelection bool
cutMenuItem := menu.Add("Cut")
cutMenuItem.SetEnabled(false) // Initially disabled
copyMenuItem := menu.Add("Copy")
copyMenuItem.SetEnabled(false)
// When selection changes
func onSelectionChanged(selected bool) {
hasSelection = selected
cutMenuItem.SetEnabled(hasSelection)
copyMenuItem.SetEnabled(hasSelection)
menu.Update() // Critical on Windows!
}
```
**Common pattern: Enable on condition**
```go
saveMenuItem := menu.Add("Save")
func updateSaveMenuItem() {
canSave := hasUnsavedChanges() && !isSaving()
saveMenuItem.SetEnabled(canSave)
menu.Update()
}
// Call whenever state changes
onDocumentChanged(func() {
updateSaveMenuItem()
})
```
### Checked State
For checkbox and radio items, control or query their checked state:
```go
checkbox := menu.AddCheckbox("Feature", false)
checkbox.SetChecked(true)
menu.Update()
// Query state
isChecked := checkbox.Checked()
```
**Auto-toggle:** Checkboxes toggle automatically when clicked. You don't need to call `SetChecked()` in the click handler.
**Manual control:**
```go
checkbox := menu.AddCheckbox("Auto-save", false)
// Sync with external state
func syncAutoSave(enabled bool) {
checkbox.SetChecked(enabled)
menu.Update()
}
```
### Accelerators (Keyboard Shortcuts)
Add keyboard shortcuts to menu items:
```go
saveMenuItem := menu.Add("Save")
saveMenuItem.SetAccelerator("CmdOrCtrl+S")
quitMenuItem := menu.Add("Quit")
quitMenuItem.SetAccelerator("CmdOrCtrl+Q")
```
**Accelerator format:** `Modifier+Key` joined by `+`. Modifiers are case-insensitive.
**Modifiers:**
- `CmdOrCtrl` (aliases: `Cmd`, `Command`) - Cmd on macOS, Ctrl on Windows/Linux
- `Ctrl` - Control key on all platforms
- `Alt` (aliases: `Option`, `OptionOrAlt`) - Alt on Windows/Linux, Option on macOS
- `Shift` - Shift key on all platforms
- `Super` - Cmd on macOS, Windows key on Windows/Linux
**Keys:**
- `A-Z`, `0-9` - Letter and number keys
- `F1-F35` - Function keys
- `Plus` - The `+` character (since `+` is the separator)
- Named keys: `Backspace`, `Tab`, `Return`, `Enter`, `Escape`, `Left`, `Right`, `Up`, `Down`, `Space`, `Delete`, `Home`, `End`, `Page Up`, `Page Down`, `NumLock`
- Any single printable character
**Examples:**
```go
"CmdOrCtrl+S" // Save
"CmdOrCtrl+Shift+S" // Save As
"CmdOrCtrl+W" // Close Window
"CmdOrCtrl+Q" // Quit
"F5" // Refresh
"CmdOrCtrl+," // Preferences (macOS convention)
"Alt+F4" // Close (Windows convention)
```
**Platform-specific accelerators:**
```go
if runtime.GOOS == "darwin" {
prefsMenuItem.SetAccelerator("Cmd+,")
} else {
prefsMenuItem.SetAccelerator("Ctrl+P")
}
```
### Tooltip
Add hover text to menu items (platform support varies):
```go
menuItem := menu.Add("Advanced Options")
menuItem.SetTooltip("Configure advanced settings")
```
**Platform support:**
- **Windows:** ✅ Supported
- **macOS:** ❌ Not supported (tooltips not standard for menus)
- **Linux:** ⚠️ Varies by desktop environment
### Hidden State
Hide menu items without removing them:
```go
debugMenuItem := menu.Add("Debug Mode")
debugMenuItem.SetHidden(true) // Hidden
// Show in debug builds
if isDebugBuild {
debugMenuItem.SetHidden(false)
menu.Update()
}
```
**Use for:** Debug options, feature flags, conditional features
## Event Handling
### OnClick Handler
Execute code when menu item is clicked:
```go
menuItem := menu.Add("Click Me")
menuItem.OnClick(func(ctx *application.Context) {
// Handle click
fmt.Println("Clicked!")
})
```
**Context methods:**
- `ctx.ClickedMenuItem() *MenuItem` - Returns the menu item that was clicked
- `ctx.IsChecked() bool` - Returns the checked state of the clicked item (for checkbox/radio items)
- `ctx.ContextMenuData() string` - Returns the context data string from the HTML element (context menus only)
**Example: Access menu item in handler**
```go
checkbox := menu.AddCheckbox("Feature", false)
checkbox.OnClick(func(ctx *application.Context) {
item := ctx.ClickedMenuItem()
isChecked := ctx.IsChecked()
fmt.Printf("Feature is now: %v\n", isChecked)
})
```
### Multiple Handlers
You can set multiple handlers (last one wins):
```go
menuItem := menu.Add("Action")
menuItem.OnClick(func(ctx *application.Context) {
fmt.Println("First handler")
})
// This replaces the first handler
menuItem.OnClick(func(ctx *application.Context) {
fmt.Println("Second handler - this one runs")
})
```
**Best practice:** Set handler once, use conditional logic inside if needed.
## Dynamic Menus
### Updating Menu Items
**The golden rule:** Always call `menu.Update()` after changing menu state.
```go
// ✅ Correct
menuItem.SetEnabled(true)
menu.Update()
// ❌ Wrong (especially on Windows)
menuItem.SetEnabled(true)
// Forgot to call Update() - click handlers may not work!
```
**Why this matters:**
- **Windows:** Menus are reconstructed when updated
- **macOS/Linux:** Less critical but still recommended
- **Click handlers:** Won't fire properly without Update()
### Rebuilding Menus
For major changes, rebuild the entire menu:
```go
func rebuildFileMenu() {
menu := app.Menu.New()
menu.Add("New").OnClick(handleNew)
menu.Add("Open").OnClick(handleOpen)
if hasRecentFiles() {
recentMenu := menu.AddSubmenu("Open Recent")
for _, file := range getRecentFiles() {
recentMenu.Add(file).OnClick(func(ctx *application.Context) {
openFile(file)
})
}
}
menu.AddSeparator()
menu.Add("Quit").OnClick(handleQuit)
// Set the new menu
window.SetMenu(menu)
}
```
**When to rebuild:**
- Recent files list changes
- Plugin menus change
- Major state transitions
**When to update:**
- Enable/disable items
- Change labels
- Toggle checkboxes
### Context-Sensitive Menus
Adjust menus based on application state:
```go
func updateEditMenu() {
cutMenuItem.SetEnabled(hasSelection())
copyMenuItem.SetEnabled(hasSelection())
pasteMenuItem.SetEnabled(hasClipboardContent())
undoMenuItem.SetEnabled(canUndo())
redoMenuItem.SetEnabled(canRedo())
menu.Update()
}
// Call whenever state changes
onSelectionChanged(updateEditMenu)
onClipboardChanged(updateEditMenu)
onUndoStackChanged(updateEditMenu)
```
## Platform Differences
### Menu Bar Location
| Platform | Location | Notes |
|----------|----------|-------|
| **macOS** | Top of screen | Global menu bar |
| **Windows** | Top of window | Per-window menu |
| **Linux** | Top of window | Per-window (usually) |
### Standard Menus
**macOS:**
- Has "Application" menu (with app name)
- `AppMenu` role adds: About, Services, Hide/Show, Quit
- "Quit" in Application menu
**Windows/Linux:**
- No Application menu
- "Settings" typically in Edit or Tools menu
- "Exit" in File menu
**Example: Platform-appropriate structure**
```go
menu := app.Menu.New()
// macOS gets Application menu
if runtime.GOOS == "darwin" {
menu.AddRole(application.AppMenu)
}
// File menu
fileMenu := menu.AddSubmenu("File")
fileMenu.Add("New")
fileMenu.Add("Open")
// Settings location varies by platform convention
if runtime.GOOS != "darwin" {
// On Windows/Linux, add to Edit or Tools menu
editMenu := menu.AddSubmenu("Edit")
editMenu.Add("Settings")
}
```
### Accelerator Conventions
**macOS:**
- `Cmd+` for most shortcuts
- `Cmd+,` for Preferences
- `Cmd+Q` for Quit
**Windows:**
- `Ctrl+` for most shortcuts
- `Ctrl+P` or `Ctrl+,` for Preferences
- `Alt+F4` for Exit (or `Ctrl+Q`)
**Linux:**
- Generally follows Windows conventions
- Desktop environment may override
## Best Practices
### ✅ Do
- **Call menu.Update()** after changing menu state (especially on Windows)
- **Use radio groups** for mutually exclusive options
- **Use checkboxes** for toggleable features
- **Add accelerators** to common actions
- **Group related items** with separators
- **Test on all platforms** - behaviour varies
### ❌ Don't
- **Don't forget menu.Update()** - Click handlers won't work properly
- **Don't nest too deeply** - 2-3 levels maximum
- **Don't start/end with separators** - Looks unprofessional
- **Don't use tooltips on macOS** - Not supported
- **Don't hardcode platform shortcuts** - Use `CmdOrCtrl`
## Troubleshooting
### Menu Items Not Responding
**Symptom:** Click handlers don't fire
**Cause:** Forgot to call `menu.Update()` after enabling item
**Solution:**
```go
menuItem.SetEnabled(true)
menu.Update() // Add this!
```
### Menu Items Greyed Out
**Symptom:** Can't click menu items
**Cause:** Items are disabled
**Solution:**
```go
menuItem.SetEnabled(true)
menu.Update()
```
### Accelerators Not Working
**Symptom:** Keyboard shortcuts don't trigger menu items
**Causes:**
1. Accelerator format incorrect
2. Conflict with system shortcuts
3. Window doesn't have focus
**Solution:**
```go
// Check format
menuItem.SetAccelerator("CmdOrCtrl+S") // ✅ Correct
menuItem.SetAccelerator("Ctrl+S") // ❌ Wrong (macOS uses Cmd)
// Avoid conflicts
// ❌ Cmd+H (Hide Window on macOS - system shortcut)
// ✅ Cmd+Shift+H (Custom shortcut)
```
## Next Steps
- [Application Menus](/features/menus/application) - Create application menu bars
- [Context Menus](/features/menus/context) - Right-click context menus
- [System Tray Menus](/features/menus/systray) - System tray/menu bar menus
- [Menu Patterns](/guides/patterns/menus) - Common menu patterns and best practices
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [menu examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/menu).

View file

@ -0,0 +1,709 @@
---
title: System Tray Menus
description: Add system tray (notification area) integration to your application
sidebar:
order: 3
---
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
## System Tray Menus
Wails provides **unified system tray APIs** that work across all platforms. Create tray icons with menus, attach windows, and handle clicks with native platform behaviour for background applications, services, and quick-access utilities.
{/* VISUAL PLACEHOLDER: System Tray Comparison
Description: Three screenshots showing the same Wails system tray icon on:
1. Windows - Notification area (bottom-right)
2. macOS - Menu bar (top-right) with label
3. Linux (GNOME) - Top bar
All showing the same icon and menu structure
Style: Clean screenshots with arrows pointing to tray icon, menu expanded
*/}
## Quick Start
```go
package main
import (
_ "embed"
"github.com/wailsapp/wails/v3/pkg/application"
)
//go:embed assets/icon.png
var icon []byte
func main() {
app := application.New(application.Options{
Name: "Tray App",
})
// Create system tray
systray := app.SystemTray.New()
systray.SetIcon(icon)
systray.SetLabel("My App")
// Add menu
menu := application.NewMenu()
menu.Add("Show").OnClick(func(ctx *application.Context) {
// Show main window
})
menu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()
})
systray.SetMenu(menu)
// Create hidden window
window := app.Window.New()
window.Hide()
app.Run()
}
```
**Result:** System tray icon with menu on all platforms.
## Creating a System Tray
### Basic System Tray
```go
// Create system tray
systray := app.SystemTray.New()
// Set icon
systray.SetIcon(iconBytes)
// Set label (macOS) / tooltip (Windows)
systray.SetLabel("My Application")
```
### With Icon
Icons should be embedded:
```go
import _ "embed"
//go:embed assets/icon.png
var icon []byte
//go:embed assets/icon-dark.png
var iconDark []byte
func main() {
app := application.New(application.Options{
Name: "My App",
})
systray := app.SystemTray.New()
systray.SetIcon(icon)
systray.SetDarkModeIcon(iconDark) // macOS dark mode
app.Run()
}
```
**Icon requirements:**
| Platform | Size | Format | Notes |
|----------|------|--------|-------|
| **Windows** | 16x16 or 32x32 | PNG, ICO | Notification area |
| **macOS** | 18x18 to 22x22 | PNG | Menu bar, template recommended |
| **Linux** | 22x22 to 48x48 | PNG, SVG | Varies by DE |
### Template Icons (macOS)
Template icons adapt to light/dark mode automatically:
```go
systray.SetTemplateIcon(iconBytes)
```
**Template icon guidelines:**
- Use black and clear (transparent) colours only
- Black becomes white in dark mode
- Name file with `Template` suffix: `iconTemplate.png`
- [Design guide](https://bjango.com/articles/designingmenubarextras/)
## Adding Menus
System tray menus work like application menus:
```go
menu := application.NewMenu()
// Add items
menu.Add("Open").OnClick(func(ctx *application.Context) {
showMainWindow()
})
menu.AddSeparator()
menu.AddCheckbox("Start at Login", false).OnClick(func(ctx *application.Context) {
enabled := ctx.ClickedMenuItem().Checked()
setStartAtLogin(enabled)
})
menu.AddSeparator()
menu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()
})
// Set menu
systray.SetMenu(menu)
```
**For all menu item types**, see [Menu Reference](/features/menus/reference).
## Attaching Windows
Attach a window to the tray icon for automatic show/hide:
```go
// Create window
window := app.Window.New()
// Attach to tray
systray.AttachWindow(window)
// Configure behaviour
systray.WindowOffset(10) // Pixels from tray icon
systray.WindowDebounce(200 * time.Millisecond) // Click debounce
```
**Behaviour:**
- Window starts hidden
- **Left-click tray icon** → Toggle window visibility
- **Right-click tray icon** → Show menu (if set)
- Window positioned near tray icon
**Example: Popup window**
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Quick Access",
Width: 300,
Height: 400,
Frameless: true, // No title bar
AlwaysOnTop: true, // Stay on top
})
systray.AttachWindow(window)
systray.WindowOffset(5)
```
## Click Handlers
Handle tray icon clicks:
```go
systray := app.SystemTray.New()
// Left click
systray.OnClick(func() {
fmt.Println("Tray icon clicked")
})
// Right click
systray.OnRightClick(func() {
fmt.Println("Tray icon right-clicked")
})
// Double click
systray.OnDoubleClick(func() {
fmt.Println("Tray icon double-clicked")
})
// Mouse enter/leave
systray.OnMouseEnter(func() {
fmt.Println("Mouse entered tray icon")
})
systray.OnMouseLeave(func() {
fmt.Println("Mouse left tray icon")
})
```
**Platform support:**
| Event | Windows | macOS | Linux |
|-------|---------|-------|-------|
| OnClick | ✅ | ✅ | ✅ |
| OnRightClick | ✅ | ✅ | ✅ |
| OnDoubleClick | ✅ | ✅ | ⚠️ Varies |
| OnRightDoubleClick | ✅ | ✅ | ⚠️ Varies |
| OnMouseEnter | ✅ | ✅ | ⚠️ Varies |
| OnMouseLeave | ✅ | ✅ | ⚠️ Varies |
## Dynamic Updates
Update tray icon and menu dynamically:
### Change Icon
```go
var isActive bool
func updateTrayIcon() {
if isActive {
systray.SetIcon(activeIcon)
systray.SetLabel("Active")
} else {
systray.SetIcon(inactiveIcon)
systray.SetLabel("Inactive")
}
}
```
### Update Menu
```go
var isPaused bool
pauseMenuItem := menu.Add("Pause")
pauseMenuItem.OnClick(func(ctx *application.Context) {
isPaused = !isPaused
if isPaused {
pauseMenuItem.SetLabel("Resume")
} else {
pauseMenuItem.SetLabel("Pause")
}
menu.Update() // Important!
})
```
:::caution[Always Call Update()]
After changing menu state, **call `menu.Update()`**. See [Menu Reference](/features/menus/reference#enabled-state).
:::
### Rebuild Menu
For major changes, rebuild the entire menu:
```go
func rebuildTrayMenu(status string) {
menu := application.NewMenu()
// Status-specific items
switch status {
case "syncing":
menu.Add("Syncing...").SetEnabled(false)
menu.Add("Pause Sync").OnClick(pauseSync)
case "synced":
menu.Add("Up to date ✓").SetEnabled(false)
menu.Add("Sync Now").OnClick(startSync)
case "error":
menu.Add("Sync Error").SetEnabled(false)
menu.Add("Retry").OnClick(retrySync)
}
menu.AddSeparator()
menu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()
})
systray.SetMenu(menu)
}
```
## Platform-Specific Features
<Tabs syncKey="platform">
<TabItem label="macOS" icon="apple">
**Menu bar integration:**
```go
// Set label (appears next to icon)
systray.SetLabel("My App")
// Use template icon (adapts to dark mode)
systray.SetTemplateIcon(iconBytes)
// Set icon position
systray.SetIconPosition(application.NSImageLeading)
```
**Icon positions:**
- `NSImageLeading` - Icon leading edge of label (default)
- `NSImageTrailing` - Icon trailing edge of label
- `NSImageLeft` - Icon left of label
- `NSImageRight` - Icon right of label
- `NSImageOnly` - Icon only, no label
- `NSImageNone` - Label only, no icon
- `NSImageAbove` - Icon above label
- `NSImageBelow` - Icon below label
- `NSImageOverlaps` - Icon overlaps label
**Best practices:**
- Use template icons (black + transparent)
- Keep labels short (3-5 characters)
- 18x18 to 22x22 pixels for Retina displays
- Test in both light and dark modes
</TabItem>
<TabItem label="Windows" icon="seti:windows">
**Notification area integration:**
```go
// Set tooltip (appears on hover)
systray.SetTooltip("My Application")
// Or use SetLabel (same as tooltip on Windows)
systray.SetLabel("My Application")
// Show/Hide functionality (fully functional)
systray.Show() // Show tray icon
systray.Hide() // Hide tray icon
```
**Icon requirements:**
- 16x16 or 32x32 pixels
- PNG or ICO format
- Transparent background
**Tooltip limits:**
- Maximum 127 UTF-16 characters
- Longer tooltips will be truncated
- Keep concise for best experience
**Platform features:**
- Tray icon survives Windows Explorer restarts
- Show() and Hide() methods fully functional
- Proper lifecycle management
**Best practices:**
- Use 32x32 for high-DPI displays
- Keep tooltips under 127 characters
- Test on different Windows versions
- Consider notification area overflow
- Use Show/Hide for conditional tray visibility
</TabItem>
<TabItem label="Linux" icon="linux">
**System tray integration:**
Uses StatusNotifierItem specification (most modern DEs).
```go
systray.SetIcon(iconBytes)
systray.SetLabel("My App")
```
**Desktop environment support:**
- **GNOME**: Top bar (with extension)
- **KDE Plasma**: System tray
- **XFCE**: Notification area
- **Others**: Varies
**Best practices:**
- Use 22x22 or 24x24 pixels
- SVG icons scale better
- Test on target desktop environments
- Provide fallback for unsupported DEs
</TabItem>
</Tabs>
## Complete Example
Here's a production-ready system tray application:
```go
package main
import (
_ "embed"
"fmt"
"time"
"github.com/wailsapp/wails/v3/pkg/application"
)
//go:embed assets/icon.png
var icon []byte
//go:embed assets/icon-active.png
var iconActive []byte
type TrayApp struct {
app *application.App
systray *application.SystemTray
window *application.WebviewWindow
menu *application.Menu
isActive bool
}
func main() {
app := application.New(application.Options{
Name: "Tray Application",
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: false,
},
})
trayApp := &TrayApp{app: app}
trayApp.setup()
app.Run()
}
func (t *TrayApp) setup() {
// Create system tray
t.systray = t.app.SystemTray.New()
t.systray.SetIcon(icon)
t.systray.SetLabel("Inactive")
// Create menu
t.createMenu()
// Create window (hidden by default)
t.window = t.app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Tray Application",
Width: 400,
Height: 600,
Hidden: true,
})
// Attach window to tray
t.systray.AttachWindow(t.window)
t.systray.WindowOffset(10)
// Handle tray clicks
t.systray.OnRightClick(func() {
t.systray.OpenMenu()
})
// Start background task
go t.backgroundTask()
}
func (t *TrayApp) createMenu() {
t.menu = application.NewMenu()
// Status item (disabled)
statusItem := t.menu.Add("Status: Inactive")
statusItem.SetEnabled(false)
t.menu.AddSeparator()
// Toggle active
t.menu.Add("Start").OnClick(func(ctx *application.Context) {
t.toggleActive()
})
// Show window
t.menu.Add("Show Window").OnClick(func(ctx *application.Context) {
t.window.Show()
t.window.Focus()
})
t.menu.AddSeparator()
// Settings
t.menu.AddCheckbox("Start at Login", false).OnClick(func(ctx *application.Context) {
enabled := ctx.ClickedMenuItem().Checked()
t.setStartAtLogin(enabled)
})
t.menu.AddSeparator()
// Quit
t.menu.Add("Quit").OnClick(func(ctx *application.Context) {
t.app.Quit()
})
t.systray.SetMenu(t.menu)
}
func (t *TrayApp) toggleActive() {
t.isActive = !t.isActive
t.updateTray()
}
func (t *TrayApp) updateTray() {
if t.isActive {
t.systray.SetIcon(iconActive)
t.systray.SetLabel("Active")
} else {
t.systray.SetIcon(icon)
t.systray.SetLabel("Inactive")
}
// Rebuild menu with new status
t.createMenu()
}
func (t *TrayApp) backgroundTask() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for range ticker.C {
if t.isActive {
fmt.Println("Background task running...")
// Do work
}
}
}
func (t *TrayApp) setStartAtLogin(enabled bool) {
// Implementation varies by platform
fmt.Printf("Start at login: %v\n", enabled)
}
```
## Visibility Control
Show/hide the tray icon dynamically:
```go
// Hide tray icon
systray.Hide()
// Show tray icon
systray.Show()
```
**Platform Support:**
| Platform | Hide() | Show() | Notes |
|----------|--------|--------|-------|
| **Windows** | ✅ | ✅ | Fully functional - icon appears/disappears from notification area |
| **macOS** | ✅ | ✅ | Menu bar item shows/hides |
| **Linux** | ✅ | ✅ | Varies by desktop environment |
**Use cases:**
- Temporarily hide tray icon based on user preference
- Headless mode with tray icon appearing only when needed
- Toggle visibility based on application state
**Example - Conditional Tray Visibility:**
```go
func (t *TrayApp) setTrayVisibility(visible bool) {
if visible {
t.systray.Show()
} else {
t.systray.Hide()
}
}
// Show tray only when updates are available
func (t *TrayApp) checkForUpdates() {
if hasUpdates {
t.systray.Show()
t.systray.SetLabel("Update Available")
} else {
t.systray.Hide()
}
}
```
## Cleanup
Destroy the tray icon when done:
```go
// In OnShutdown
app := application.New(application.Options{
OnShutdown: func() {
if systray != nil {
systray.Destroy()
}
},
})
```
**Important:** Always destroy system tray on shutdown to release resources.
## Best Practices
### ✅ Do
- **Use template icons on macOS** - Adapts to dark mode
- **Keep labels short** - 3-5 characters maximum
- **Provide tooltips on Windows** - Helps users identify your app
- **Test on all platforms** - Behaviour varies
- **Handle clicks appropriately** - Left-click for main action, right-click for menu
- **Update icon for status** - Visual feedback is important
- **Destroy on shutdown** - Release resources
### ❌ Don't
- **Don't use large icons** - Follow platform guidelines
- **Don't use long labels** - Gets truncated
- **Don't forget dark mode** - Test on macOS dark mode
- **Don't block click handlers** - Keep them fast
- **Don't forget menu.Update()** - After changing menu state
- **Don't assume tray support** - Some Linux DEs don't support it
## Troubleshooting
### Tray Icon Not Appearing
**Possible causes:**
1. Icon format not supported
2. Icon size too large/small
3. System tray not supported (Linux)
**Solution:**
- Verify icon format and size match platform requirements (see icon table above)
- On Linux, ensure your desktop environment supports StatusNotifierItem or has an AppIndicator extension installed
- Test with a known-good PNG icon to rule out format issues
### Icon Looks Wrong on macOS
**Cause:** Not using template icon
**Solution:**
```go
// Use template icon
systray.SetTemplateIcon(iconBytes)
// Or design icon as template (black + transparent)
```
### Menu Not Updating
**Cause:** Forgot to call `menu.Update()`
**Solution:**
```go
menuItem.SetLabel("New Label")
menu.Update() // Add this!
```
## Next Steps
<CardGrid>
<Card title="Menu Reference" icon="document">
Complete reference for menu item types and properties.
[Learn More →](/features/menus/reference)
</Card>
<Card title="Application Menus" icon="list-format">
Create application menu bars.
[Learn More →](/features/menus/application)
</Card>
<Card title="Context Menus" icon="puzzle">
Create right-click context menus.
[Learn More →](/features/menus/context)
</Card>
<Card title="System Tray Tutorial" icon="open-book">
Build a complete system tray application.
[Learn More →](/tutorials/system-tray)
</Card>
</CardGrid>
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [system tray examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/systray-basic).

View file

@ -0,0 +1,307 @@
---
title: Notifications
description: Display native system notifications with action buttons and text input
sidebar:
order: 1
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
## Introduction
Wails provides a comprehensive cross-platform notification system for desktop applications. This service allows you to display native system notifications, with support for interactive elements like action buttons and text input fields.
## Basic Usage
### Creating the Service
First, initialize the notifications service:
```go
import "github.com/wailsapp/wails/v3/pkg/application"
import "github.com/wailsapp/wails/v3/pkg/services/notifications"
// Create a new notification service
notifier := notifications.New()
//Register the service with the application
app := application.New(application.Options{
Services: []application.Service{
application.NewService(notifier),
},
})
```
## Notification Authorization
Notifications on macOS require user authorization. Request and check authorization:
```go
authorized, err := notifier.CheckNotificationAuthorization()
if err != nil {
// Handle authorization error
}
if authorized {
// Send notifications
} else {
// Request authorization
authorized, err = notifier.RequestNotificationAuthorization()
}
```
On Windows and Linux this always returns `true`.
## Notification Types
### Basic Notifications
Send a basic notification with a unique id, title, optional subtitle (macOS and Linux), and body text to users:
```go
notifier.SendNotification(notifications.NotificationOptions{
ID: "unique-id",
Title: "New Calendar Invite",
Subtitle: "From: Jane Doe", // Optional
Body: "Tap to view the event",
})
```
### Interactive Notifications
Send a notification with action buttons and text inputs. These notifications require a notification category to be registered first:
```go
// Define a unique category id
categoryID := "unique-category-id"
// Define a category with actions
category := notifications.NotificationCategory{
ID: categoryID,
Actions: []notifications.NotificationAction{
{
ID: "OPEN",
Title: "Open",
},
{
ID: "ARCHIVE",
Title: "Archive",
Destructive: true, /* macOS-specific */
},
},
HasReplyField: true,
ReplyPlaceholder: "message...",
ReplyButtonTitle: "Reply",
}
// Register the category
notifier.RegisterNotificationCategory(category)
// Send an interactive notification with the actions registered in the provided category
notifier.SendNotificationWithActions(notifications.NotificationOptions{
ID: "unique-id",
Title: "New Message",
Subtitle: "From: Jane Doe",
Body: "Are you able to make it?",
CategoryID: categoryID,
})
```
## Notification Responses
Process user interactions with notifications:
```go
notifier.OnNotificationResponse(func(result notifications.NotificationResult) {
response := result.Response
fmt.Printf("Notification %s was actioned with: %s\n", response.ID, response.ActionIdentifier)
if response.ActionIdentifier == "TEXT_REPLY" {
fmt.Printf("User replied: %s\n", response.UserText)
}
if data, ok := response.UserInfo["sender"].(string); ok {
fmt.Printf("Original sender: %s\n", data)
}
// Emit an event to the frontend
app.Event.Emit("notification", result.Response)
})
```
## Notification Customisation
### Custom Metadata
Basic and interactive notifications can include custom data:
```go
notifier.SendNotification(notifications.NotificationOptions{
ID: "unique-id",
Title: "New Calendar Invite",
Subtitle: "From: Jane Doe", // Optional
Body: "Tap to view the event",
Data: map[string]interface{}{
"sender": "jane.doe@example.com",
"timestamp": "2025-03-10T15:30:00Z",
}
})
```
## Platform Considerations
<Tabs>
<TabItem label="macOS" icon="fa-brands:apple">
On macOS, notifications:
- Require user authorization
- Require the app to be notarized for distribution
- Use system-standard notification appearances
- Support `subtitle`
- Support user text input
- Support the `Destructive` action option
- Automatically handle dark/light mode
</TabItem>
<TabItem label="Windows" icon="fa-brands:windows">
On Windows, notifications:
- Use Windows system toast styles
- Adapt to Windows theme settings
- Support user text input
- Support high DPI displays
- Do not support `subtitle`
</TabItem>
<TabItem label="Linux" icon="fa-brands:linux">
On Linux, notification behaviour depends on the desktop environment:
- Use native notifications when available
- Follow desktop environment theme
- Position according to desktop environment rules
- Support `subtitle`
- Do not support user text input
</TabItem>
</Tabs>
## Best Practices
1. Check and request for authorization:
- macOS requires user authorization
2. Provide clear and concise notifications:
- Use descriptive titles, subtitles, text, and action titles
3. Handle dialog responses appropriately:
- Check for errors in notification responses
- Provide feedback for user actions
4. Consider platform conventions:
- Follow platform-specific notification patterns
- Respect system settings
## Examples
Explore this example:
- [Notifications](/examples/notifications)
## API Reference
### Service Management
| Method | Description |
|--------------------------------------------|-------------------------------------------------------|
| `New()` | Creates a new notifications service |
### Notification Authorization
| Method | Description |
|----------------------------------------------|------------------------------------------------------------|
| `RequestNotificationAuthorization()` | Requests permission to display notifications (macOS) |
| `CheckNotificationAuthorization()` | Checks current notification authorization status (macOS) |
### Sending Notifications
| Method | Returns | Description |
|------------------------------------------------------------|---------|------------------------------------------------|
| `SendNotification(options NotificationOptions)` | `error` | Sends a basic notification |
| `SendNotificationWithActions(options NotificationOptions)` | `error` | Sends an interactive notification with actions |
### Notification Categories
| Method | Returns | Description |
|---------------------------------------------------------------|---------|------------------------------------------- |
| `RegisterNotificationCategory(category NotificationCategory)` | `error` | Registers a reusable notification category |
| `RemoveNotificationCategory(categoryID string)` | `error` | Removes a previously registered category |
### Managing Notifications
| Method | Returns | Description |
|-------------------------------------------------|---------|---------------------------------------------------------------------|
| `RemoveAllPendingNotifications()` | `error` | Removes all pending notifications (macOS and Linux only) |
| `RemovePendingNotification(identifier string)` | `error` | Removes a specific pending notification (macOS and Linux only) |
| `RemoveAllDeliveredNotifications()` | `error` | Removes all delivered notifications (macOS and Linux only) |
| `RemoveDeliveredNotification(identifier string)`| `error` | Removes a specific delivered notification (macOS and Linux only) |
| `RemoveNotification(identifier string)` | `error` | Removes a notification (Linux-specific) |
### Event Handling
| Method | Description |
|--------------------------------------------------------------------|-------------------------------------------------|
| `OnNotificationResponse(callback func(result NotificationResult))` | Registers a callback for notification responses |
### Structs and Types
#### NotificationOptions
```go
type NotificationOptions struct {
ID string // Unique identifier for the notification
Title string // Main notification title
Subtitle string // Subtitle text (macOS and Linux only)
Body string // Main notification content
CategoryID string // Category identifier for interactive notifications
Data map[string]interface{} // Custom data to associate with the notification
}
```
#### NotificationCategory
```go
type NotificationCategory struct {
ID string // Unique identifier for the category
Actions []NotificationAction // Button actions for the notification
HasReplyField bool // Whether to include a text input field
ReplyPlaceholder string // Placeholder text for the input field
ReplyButtonTitle string // Text for the reply button
}
```
#### NotificationAction
```go
type NotificationAction struct {
ID string // Unique identifier for the action
Title string // Button text
Destructive bool // Whether the action is destructive (macOS-specific)
}
```
#### NotificationResponse
```go
type NotificationResponse struct {
ID string // Notification identifier
ActionIdentifier string // Action that was triggered
CategoryID string // Category of the notification
Title string // Title of the notification
Subtitle string // Subtitle of the notification
Body string // Body text of the notification
UserText string // Text entered by the user
UserInfo map[string]interface{} // Custom data from the notification
}
```
#### NotificationResult
```go
type NotificationResult struct {
Response NotificationResponse // Response data
Error error // Any error that occurred
}
```

View file

@ -0,0 +1,235 @@
---
title: Dock & Taskbar
description: Manage dock icon visibility and display badges on macOS and Windows
sidebar:
order: 1
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
## Introduction
Wails provides a cross-platform Dock service for desktop applications. This service allows you to:
- Hide and show the application icon in the macOS Dock
- Display badges on your application tile or dock/taskbar icon (macOS and Windows)
## Basic Usage
### Creating the Service
First, initialize the dock service:
```go
import "github.com/wailsapp/wails/v3/pkg/application"
import "github.com/wailsapp/wails/v3/pkg/services/dock"
// Create a new Dock service
dockService := dock.New()
// Register the service with the application
app := application.New(application.Options{
Services: []application.Service{
application.NewService(dockService),
},
})
```
### Creating the Service with Custom Badge Options (Windows Only)
On Windows, you can customize the badge appearance with various options:
```go
import "github.com/wailsapp/wails/v3/pkg/application"
import "github.com/wailsapp/wails/v3/pkg/services/dock"
import "image/color"
// Create a dock service with custom badge options
options := dock.BadgeOptions{
TextColour: color.RGBA{255, 255, 255, 255}, // White text
BackgroundColour: color.RGBA{0, 0, 255, 255}, // Blue background
FontName: "consolab.ttf", // Bold Consolas font
FontSize: 20, // Font size for single character
SmallFontSize: 14, // Font size for multiple characters
}
dockService := dock.NewWithOptions(options)
// Register the service with the application
app := application.New(application.Options{
Services: []application.Service{
application.NewService(dockService),
},
})
```
## Dock Operations
### Hiding the dock app icon
Hide the app icon from the macOS Dock:
```go
// Hide the app icon
dockService.HideAppIcon()
```
### Showing the dock app icon
Show the app icon in the macOS Dock:
```go
// Show the app icon
dockService.ShowAppIcon()
```
## Badge Operations
### Setting a Badge
Set a badge on the application tile/dock icon:
```go
// Set a default badge
dockService.SetBadge("")
// Set a numeric badge
dockService.SetBadge("3")
// Set a text badge
dockService.SetBadge("New")
```
### Setting a Custom Badge (Windows Only)
Set a badge with one-off options applied:
```go
options := dock.BadgeOptions{
BackgroundColour: color.RGBA{0, 255, 255, 255},
FontName: "arialb.ttf", // System font
FontSize: 16,
SmallFontSize: 10,
TextColour: color.RGBA{0, 0, 0, 255},
}
// Set a default badge
dockService.SetCustomBadge("", options)
// Set a numeric badge
dockService.SetCustomBadge("3", options)
// Set a text badge
dockService.SetCustomBadge("New", options)
```
### Removing a Badge
Remove the badge from the application icon:
```go
dockService.RemoveBadge()
```
## Platform Considerations
<Tabs>
<TabItem label="macOS" icon="fa-brands:apple">
On macOS:
- The dock icon can be **hidden** and **shown**
- Badges are displayed directly on the dock icon
- Badge options are **not customizable** (any options passed to `NewWithOptions`/`SetCustomBadge` are ignored)
- The standard macOS dock badge styling is used and automatically adapts to appearance
- Label overflow is handled by the system
- Providing an empty label displays a default badge of "●"
</TabItem>
<TabItem label="Windows" icon="fa-brands:windows">
On Windows:
- Hiding/showing the taskbar icon is not currently supported by this service
- Badges are displayed as an overlay icon in the taskbar
- Badges support text values
- Badge appearance can be customized via `BadgeOptions`
- The application must have a window for badges to display
- A smaller font size is automatically used for multi-character labels
- Label overflow is not handled
- Customization options:
- **TextColour**: Text color (default: white)
- **BackgroundColour**: Badge background color (default: red)
- **FontName**: Font file name (default: "segoeuib.ttf")
- **FontSize**: Font size for single character (default: 18)
- **SmallFontSize**: Font size for multiple characters (default: 14)
</TabItem>
<TabItem label="Linux" icon="fa-brands:linux">
On Linux:
- Dock icon visibility and badge functionality are not available
</TabItem>
</Tabs>
## Best Practices
1. **When hiding the dock icon (macOS):**
- Ensure users can still access your app (e.g., via [system tray](https://v3alpha.wails.io/learn/systray/))
- Include a "Quit" option in your alternative UI
- The app won't appear in Command+Tab switcher
- Open windows remain visible and functional
- Closing all windows may not quit the app (macOS behavior varies)
- Users lose the standard way to quit via Dock right-click
2. **Use badges sparingly:**
- Too many badge updates can distract users
- Reserve badges for important notifications
3. **Keep badge text short:**
- Numeric badges are most effective
- On macOS, text badges should be brief
4. **For Windows badge customization:**
- Ensure high contrast between text and background colors
- Test with different text lengths as font size decreases with length
- Use common system fonts to ensure availability
## API Reference
### Service Management
| Method | Description |
|--------------------------------------------|-------------------------------------------------------|
| `New()` | Creates a new dock service |
| `NewWithOptions(options BadgeOptions)` | Creates a new dock service with custom badge options (Windows only; options are ignored on macOS and Linux) |
### Dock Operations
| Method | Description |
|--------------------------------|-------------------------------------------------------------|
| `HideAppIcon()` | Hides the app icon from the macOS Dock (macOS only) |
| `ShowAppIcon()` | Shows the app icon in the macOS Dock (macOS only) |
### Badge Operations
| Method | Description |
|---------------------------------------------------|------------------------------------------------------------|
| `SetBadge(label string) error` | Sets a badge with the specified label |
| `SetCustomBadge(label string, options BadgeOptions) error` | Sets a badge with the specified label and custom styling options (Windows only) |
| `RemoveBadge() error` | Removes the badge from the application icon |
### Structs and Types
```go
// Options for customizing badge appearance (Windows only)
type BadgeOptions struct {
TextColour color.RGBA // Color of the badge text
BackgroundColour color.RGBA // Color of the badge background
FontName string // Font file name (e.g., "segoeuib.ttf")
FontSize int // Font size for single character
SmallFontSize int // Font size for multiple characters
}
```

View file

@ -0,0 +1,463 @@
---
title: Screen Information
description: Get information about displays and monitors
sidebar:
order: 1
---
import { Card, CardGrid } from "@astrojs/starlight/components";
## Screen Information
Wails provides a **unified screen API** that works across all platforms. Get screen information, detect multiple monitors, query screen properties (size, position, DPI), identify the primary display, and handle DPI scaling with consistent code.
## Quick Start
```go
// Get all screens
screens := app.Screen.GetAll()
for _, screen := range screens {
fmt.Printf("Screen: %s (%dx%d)\n",
screen.Name, screen.Size.Width, screen.Size.Height)
}
// Get primary screen
primary := app.Screen.GetPrimary()
fmt.Printf("Primary: %s\n", primary.Name)
```
**That's it!** Cross-platform screen information.
## Getting Screen Information
### All Screens
```go
screens := app.Screen.GetAll()
for _, screen := range screens {
fmt.Printf("ID: %s\n", screen.ID)
fmt.Printf("Name: %s\n", screen.Name)
fmt.Printf("Size: %dx%d\n", screen.Size.Width, screen.Size.Height)
fmt.Printf("Position: %d,%d\n", screen.X, screen.Y)
fmt.Printf("Scale: %.2f\n", screen.ScaleFactor)
fmt.Printf("Primary: %v\n", screen.IsPrimary)
fmt.Println("---")
}
```
### Primary Screen
```go
primary := app.Screen.GetPrimary()
fmt.Printf("Primary screen: %s\n", primary.Name)
fmt.Printf("Resolution: %dx%d\n", primary.Size.Width, primary.Size.Height)
fmt.Printf("Scale factor: %.2f\n", primary.ScaleFactor)
```
## Screen Properties
### Screen Structure
```go
type Screen struct {
ID string // A unique identifier for the display
Name string // The name of the display
ScaleFactor float32 // The scale factor of the display (DPI/96)
X int // The x-coordinate of the top-left corner
Y int // The y-coordinate of the top-left corner
Size Size // The size of the display
Bounds Rect // The bounds of the display
PhysicalBounds Rect // The physical bounds (before scaling)
WorkArea Rect // The work area of the display
PhysicalWorkArea Rect // The physical work area (before scaling)
IsPrimary bool // Whether this is the primary display
Rotation float32 // The rotation of the display
}
type Rect struct {
X int
Y int
Width int
Height int
}
type Size struct {
Width int
Height int
}
```
### Physical vs Logical Pixels
```go
screen := app.Screen.GetPrimary()
// Logical pixels (DIP - what you use)
logicalWidth := screen.Bounds.Width
logicalHeight := screen.Bounds.Height
// Physical pixels (actual display)
physicalWidth := screen.PhysicalBounds.Width
physicalHeight := screen.PhysicalBounds.Height
fmt.Printf("Logical: %dx%d\n", logicalWidth, logicalHeight)
fmt.Printf("Physical: %dx%d\n", physicalWidth, physicalHeight)
fmt.Printf("Scale: %.2f\n", screen.ScaleFactor)
```
**Common scale factors:**
- `1.0` - Standard DPI (96 DPI)
- `1.25` - 125% scaling (120 DPI)
- `1.5` - 150% scaling (144 DPI)
- `2.0` - 200% scaling (192 DPI) - Retina
- `3.0` - 300% scaling (288 DPI) - 4K/5K
## Window Positioning
### Centre on Screen
```go
func centreOnScreen(window *application.WebviewWindow, screen *Screen) {
windowWidth, windowHeight := window.Size()
x := screen.X + (screen.Size.Width-windowWidth)/2
y := screen.Y + (screen.Size.Height-windowHeight)/2
window.SetPosition(x, y)
}
```
### Position on Specific Screen
```go
func moveToScreen(window *application.WebviewWindow, screenIndex int) {
screens := app.Screen.GetAll()
if screenIndex < 0 || screenIndex >= len(screens) {
return
}
screen := screens[screenIndex]
// Centre on target screen
centreOnScreen(window, screen)
}
```
### Position Relative to Screen
```go
// Top-left corner
func positionTopLeft(window *application.WebviewWindow, screen *Screen) {
window.SetPosition(screen.X+10, screen.Y+10)
}
// Top-right corner
func positionTopRight(window *application.WebviewWindow, screen *Screen) {
windowWidth, _ := window.Size()
window.SetPosition(screen.X+screen.Size.Width-windowWidth-10, screen.Y+10)
}
// Bottom-right corner
func positionBottomRight(window *application.WebviewWindow, screen *Screen) {
windowWidth, windowHeight := window.Size()
window.SetPosition(
screen.X+screen.Size.Width-windowWidth-10,
screen.Y+screen.Size.Height-windowHeight-10,
)
}
```
## Multi-Monitor Support
### Detect Multiple Monitors
```go
func hasMultipleMonitors() bool {
return len(app.Screen.GetAll()) > 1
}
func getMonitorCount() int {
return len(app.Screen.GetAll())
}
```
### List All Monitors
```go
func listMonitors() {
screens := app.Screen.GetAll()
fmt.Printf("Found %d monitor(s):\n", len(screens))
for i, screen := range screens {
primary := ""
if screen.IsPrimary {
primary = " (Primary)"
}
fmt.Printf("%d. %s%s\n", i+1, screen.Name, primary)
fmt.Printf(" Resolution: %dx%d\n", screen.Size.Width, screen.Size.Height)
fmt.Printf(" Position: %d,%d\n", screen.X, screen.Y)
fmt.Printf(" Scale: %.2fx\n", screen.ScaleFactor)
}
}
```
### Choose Monitor
```go
func chooseMonitor() (*Screen, error) {
screens := app.Screen.GetAll()
if len(screens) == 1 {
return screens[0], nil
}
// Show dialog to choose
var options []string
for i, screen := range screens {
primary := ""
if screen.IsPrimary {
primary = " (Primary)"
}
options = append(options,
fmt.Sprintf("%d. %s%s - %dx%d",
i+1, screen.Name, primary, screen.Size.Width, screen.Size.Height))
}
// Use dialog to select
// (Implementation depends on your dialog system)
return screens[0], nil
}
```
## Complete Examples
### Multi-Monitor Window Manager
```go
type MultiMonitorManager struct {
app *application.App
windows map[int]*application.WebviewWindow
}
func NewMultiMonitorManager(app *application.App) *MultiMonitorManager {
return &MultiMonitorManager{
app: app,
windows: make(map[int]*application.WebviewWindow),
}
}
func (m *MultiMonitorManager) CreateWindowOnScreen(screenIndex int) error {
screens := m.app.Screen.GetAll()
if screenIndex < 0 || screenIndex >= len(screens) {
return errors.New("invalid screen index")
}
screen := screens[screenIndex]
// Create window
window := m.app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: fmt.Sprintf("Window on %s", screen.Name),
Width: 800,
Height: 600,
})
// Centre on screen
x := screen.X + (screen.Size.Width-800)/2
y := screen.Y + (screen.Size.Height-600)/2
window.SetPosition(x, y)
window.Show()
m.windows[screenIndex] = window
return nil
}
func (m *MultiMonitorManager) CreateWindowOnEachScreen() {
screens := m.app.Screen.GetAll()
for i := range screens {
m.CreateWindowOnScreen(i)
}
}
```
### Screen Change Detection
```go
type ScreenMonitor struct {
app *application.App
lastScreens []*Screen
changeHandler func([]*Screen)
}
func NewScreenMonitor(app *application.App) *ScreenMonitor {
return &ScreenMonitor{
app: app,
lastScreens: app.Screen.GetAll(),
}
}
func (sm *ScreenMonitor) OnScreenChange(handler func([]*Screen)) {
sm.changeHandler = handler
}
func (sm *ScreenMonitor) Start() {
ticker := time.NewTicker(2 * time.Second)
go func() {
for range ticker.C {
sm.checkScreens()
}
}()
}
func (sm *ScreenMonitor) checkScreens() {
current := sm.app.Screen.GetAll()
if len(current) != len(sm.lastScreens) {
sm.lastScreens = current
if sm.changeHandler != nil {
sm.changeHandler(current)
}
}
}
```
### DPI-Aware Window Sizing
```go
func createDPIAwareWindow(screen *Screen) *application.WebviewWindow {
// Base size at 1.0 scale
baseWidth := 800
baseHeight := 600
// Adjust for DPI
width := int(float32(baseWidth) * screen.ScaleFactor)
height := int(float32(baseHeight) * screen.ScaleFactor)
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "DPI-Aware Window",
Width: width,
Height: height,
})
// Centre on screen
x := screen.X + (screen.Size.Width-width)/2
y := screen.Y + (screen.Size.Height-height)/2
window.SetPosition(x, y)
return window
}
```
### Screen Layout Visualiser
```go
func visualiseScreenLayout() string {
screens := app.Screen.GetAll()
var layout strings.Builder
layout.WriteString("Screen Layout:\n\n")
for i, screen := range screens {
primary := ""
if screen.IsPrimary {
primary = " [PRIMARY]"
}
layout.WriteString(fmt.Sprintf("Screen %d: %s%s\n", i+1, screen.Name, primary))
layout.WriteString(fmt.Sprintf(" Position: (%d, %d)\n", screen.X, screen.Y))
layout.WriteString(fmt.Sprintf(" Size: %dx%d\n", screen.Size.Width, screen.Size.Height))
layout.WriteString(fmt.Sprintf(" Scale: %.2fx\n", screen.ScaleFactor))
layout.WriteString(fmt.Sprintf(" Physical: %dx%d\n",
screen.PhysicalBounds.Width,
screen.PhysicalBounds.Height))
layout.WriteString("\n")
}
return layout.String()
}
```
## Best Practices
### ✅ Do
- **Check screen count** - Handle single and multiple monitors
- **Use logical pixels** - Wails handles DPI automatically
- **Centre windows** - Better UX than fixed positions
- **Validate positions** - Ensure windows are visible
- **Handle screen changes** - Monitors can be added/removed
- **Test on different DPI** - 100%, 125%, 150%, 200%
### ❌ Don't
- **Don't hardcode positions** - Use screen dimensions
- **Don't assume primary screen** - User might have multiple
- **Don't ignore scale factor** - Important for DPI awareness
- **Don't position off-screen** - Validate coordinates
- **Don't forget screen changes** - Laptops dock/undock
- **Don't use physical pixels** - Use logical pixels
## Platform Differences
### macOS
- Retina displays (2x scale factor)
- Multiple displays common
- Coordinate system: (0,0) at bottom-left
- Spaces (virtual desktops) affect positioning
### Windows
- Various DPI scaling (100%, 125%, 150%, 200%)
- Multiple displays common
- Coordinate system: (0,0) at top-left
- Per-monitor DPI awareness
### Linux
- Varies by desktop environment
- X11 vs Wayland differences
- DPI scaling support varies
- Multiple displays supported
## Next Steps
<CardGrid>
<Card title="Windows" icon="laptop">
Learn about window management.
[Learn More →](/features/windows/basics)
</Card>
<Card title="Window Options" icon="seti:config">
Configure window appearance.
[Learn More →](/features/windows/options)
</Card>
<Card title="Multiple Windows" icon="puzzle">
Multi-window patterns.
[Learn More →](/features/windows/multiple)
</Card>
<Card title="Bindings" icon="rocket">
Call Go functions from JavaScript.
[Learn More →](/features/bindings/methods)
</Card>
</CardGrid>
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [screen examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).

View file

@ -0,0 +1,570 @@
---
title: Window Basics
description: Creating and managing application windows in Wails
sidebar:
order: 1
---
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
## Window Management
Wails provides a **unified window management API** that works across all platforms. Create windows, control their behaviour, and manage multiple windows with full control over creation, appearance, behaviour, and lifecycle.
## Quick Start
```go
package main
import "github.com/wailsapp/wails/v3/pkg/application"
func main() {
app := application.New(application.Options{
Name: "My App",
})
// Create a window
window := app.Window.New()
// Configure it
window.SetTitle("Hello Wails")
window.SetSize(800, 600)
window.Center()
// Show it
window.Show()
app.Run()
}
```
**That's it!** You have a cross-platform window.
## Creating Windows
### Basic Window
The simplest way to create a window:
```go
window := app.Window.New()
```
**What you get:**
- Default size (800x600)
- Default title (application name)
- WebView ready for your frontend
- Platform-native appearance
### Window with Options
Create a window with custom configuration:
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "My Application",
Width: 1200,
Height: 800,
X: 100, // Position from left
Y: 100, // Position from top
AlwaysOnTop: false,
Frameless: false,
Hidden: false,
MinWidth: 400,
MinHeight: 300,
MaxWidth: 1920,
MaxHeight: 1080,
})
```
**Common options:**
| Option | Type | Description |
|--------|------|-------------|
| `Title` | `string` | Window title |
| `Width` | `int` | Window width in pixels |
| `Height` | `int` | Window height in pixels |
| `X` | `int` | X position (from left) |
| `Y` | `int` | Y position (from top) |
| `AlwaysOnTop` | `bool` | Keep window above others |
| `Frameless` | `bool` | Remove title bar and borders |
| `Hidden` | `bool` | Start hidden |
| `MinWidth` | `int` | Minimum width |
| `MinHeight` | `int` | Minimum height |
| `MaxWidth` | `int` | Maximum width |
| `MaxHeight` | `int` | Maximum height |
**See [Window Options](/features/windows/options) for complete list.**
### Named Windows
Give windows names for easy retrieval:
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "main-window",
Title: "Main Application",
})
// Later, find it by name
mainWindow, ok := app.Window.GetByName("main-window")
if ok {
mainWindow.Show()
}
```
**Use cases:**
- Multiple windows (main, settings, about)
- Finding windows from different parts of your code
- Window communication
## Controlling Windows
### Show and Hide
```go
// Show window
window.Show()
// Hide window
window.Hide()
// Check if visible
if window.IsVisible() {
fmt.Println("Window is visible")
}
```
**Use cases:**
- Splash screens (show, then hide)
- Settings windows (hide when not needed)
- Popup windows (show on demand)
### Position and Size
```go
// Set size
window.SetSize(1024, 768)
// Set position
window.SetPosition(100, 100)
// Centre on screen
window.Center()
// Get current size
width, height := window.Size()
// Get current position
x, y := window.Position()
```
**Coordinate system:**
- (0, 0) is top-left of primary screen
- Positive X goes right
- Positive Y goes down
### Window State
```go
// Minimise
window.Minimise()
// Maximise
window.Maximise()
// Fullscreen
window.Fullscreen()
// Restore to normal
window.Restore()
// Check state
if window.IsMinimised() {
fmt.Println("Window is minimised")
}
if window.IsMaximised() {
fmt.Println("Window is maximised")
}
if window.IsFullscreen() {
fmt.Println("Window is fullscreen")
}
```
**State transitions:**
```
Normal ←→ Minimised
Normal ←→ Maximised
Normal ←→ Fullscreen
```
### Title and Appearance
```go
// Set title
window.SetTitle("My Application - Document.txt")
// Set background colour
window.SetBackgroundColour(application.NewRGBA(0, 0, 0, 255))
// Set always on top
window.SetAlwaysOnTop(true)
// Set resizable
window.SetResizable(false)
```
### Closing Windows
```go
// Close window (triggers WindowClosing event, can be cancelled via RegisterHook)
window.Close()
```
## Finding Windows
### By Name
```go
window, ok := app.Window.GetByName("settings")
if ok {
window.Show()
}
```
### Current Window
Get the currently focused window:
```go
current := app.Window.Current()
if current != nil {
current.SetTitle("Active Window")
}
```
### All Windows
Get all windows:
```go
windows := app.Window.GetAll()
fmt.Printf("Total windows: %d\n", len(windows))
for _, w := range windows {
fmt.Printf("Window: %s\n", w.Name())
}
```
## Window Lifecycle
### Creation
```go
app.Window.OnCreate(func(window application.Window) {
fmt.Printf("Window created: %s\n", window.Name())
// Configure new windows
window.SetMinSize(400, 300)
})
```
### Preventing Close
Use `RegisterHook` to intercept the window close event and optionally cancel it:
```go
import "github.com/wailsapp/wails/v3/pkg/events"
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
if hasUnsavedChanges() {
e.Cancel() // Prevent the window from closing
}
})
```
### Cleanup on Close
Use `OnWindowEvent` to run cleanup logic when the window closes:
```go
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
fmt.Println("Window closing, cleaning up...")
// Cleanup resources
})
```
## Multiple Windows
### Creating Multiple Windows
```go
// Main window
mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "main",
Title: "Main Application",
Width: 1200,
Height: 800,
})
// Settings window
settingsWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "settings",
Title: "Settings",
Width: 600,
Height: 400,
Hidden: true, // Start hidden
})
// Show settings when needed
settingsWindow.Show()
```
### Window Communication
Windows can communicate via events:
```go
// In main window
app.Event.Emit("data-updated", map[string]interface{}{
"value": 42,
})
// In settings window
app.Event.On("data-updated", func(event *application.CustomEvent) {
data := event.Data.(map[string]interface{})
value := data["value"].(int)
fmt.Printf("Received: %d\n", value)
})
```
**See [Events](/features/events/system) for more.**
### Multiple Window Communication
Windows can communicate via the event system. See [Multiple Windows](/features/windows/multiple) for patterns.
## Platform-Specific Features
<Tabs syncKey="platform">
<TabItem label="Windows" icon="seti:windows">
**Windows-specific features:**
```go
// Flash taskbar button
window.Flash(true) // Start flashing
window.Flash(false) // Stop flashing
// Trigger Windows 11 Snap Assist (Win+Z)
window.SnapAssist()
```
**Snap Assist:**
Shows Windows 11 snap layout options for the window.
**Taskbar flashing:**
Useful for notifications when window is minimised.
</TabItem>
<TabItem label="macOS" icon="apple">
**macOS-specific features:**
```go
// Transparent title bar
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Mac: application.MacWindow{
TitleBar: application.MacTitleBar{
AppearsTransparent: true,
},
Backdrop: application.MacBackdropTranslucent,
},
})
```
**Backdrop types:**
- `MacBackdropNormal` - Standard window
- `MacBackdropTransparent` - Fully transparent
- `MacBackdropTranslucent` - Translucent background
- `MacBackdropLiquidGlass` - Liquid Glass effect (macOS 15.0+)
**Native fullscreen:**
macOS fullscreen creates a new Space (virtual desktop).
</TabItem>
<TabItem label="Linux" icon="linux">
**Linux-specific features:**
```go
// Set window icon
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Linux: application.LinuxWindow{
Icon: iconBytes,
},
})
```
**Desktop environment notes:**
- GNOME: Full support
- KDE Plasma: Full support
- XFCE: Partial support
- Others: Varies
**Window managers:**
- Tiling WMs may ignore size/position
- Some WMs don't support always-on-top
</TabItem>
</Tabs>
## Common Patterns
### Splash Screen
```go
// Create splash screen
splash := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Loading...",
Width: 400,
Height: 300,
Frameless: true,
AlwaysOnTop: true,
})
// Show splash
splash.Show()
// Initialise application
time.Sleep(2 * time.Second)
// Hide splash, show main window
splash.Close()
mainWindow.Show()
```
### Settings Window
```go
var settingsWindow *application.WebviewWindow
func showSettings() {
if settingsWindow == nil {
settingsWindow = app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "settings",
Title: "Settings",
Width: 600,
Height: 400,
})
}
settingsWindow.Show()
settingsWindow.Focus()
}
```
### Confirm Before Close
```go
import "github.com/wailsapp/wails/v3/pkg/events"
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
if hasUnsavedChanges() {
e.Cancel() // Prevent the window from closing
}
})
```
## Best Practices
### ✅ Do
- **Name important windows** - Easier to find later
- **Set minimum size** - Prevent unusable layouts
- **Centre windows** - Better UX than random position
- **Handle close events** - Prevent data loss
- **Test on all platforms** - Behaviour varies
- **Use appropriate sizes** - Consider different screen sizes
### ❌ Don't
- **Don't create too many windows** - Confusing for users
- **Don't forget to close windows** - Memory leaks
- **Don't hardcode positions** - Different screen sizes
- **Don't ignore platform differences** - Test thoroughly
- **Don't block the UI thread** - Use goroutines for long operations
## Troubleshooting
### Window Not Showing
**Possible causes:**
1. Window created as hidden
2. Window off-screen
3. Window behind other windows
**Solution:**
```go
window.Show()
window.Center()
window.Focus()
```
### Window Wrong Size
**Cause:** DPI scaling on Windows/Linux
**Solution:**
```go
// Wails handles DPI automatically
// Just use logical pixels
window.SetSize(800, 600)
```
### Window Closes Immediately
**Cause:** Application exits when last window closes
**Solution:**
```go
app := application.New(application.Options{
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: false,
},
})
```
## Next Steps
<CardGrid>
<Card title="Window Options" icon="seti:config">
Complete reference for all window options.
[Learn More →](/features/windows/options)
</Card>
<Card title="Multiple Windows" icon="laptop">
Patterns for multi-window applications.
[Learn More →](/features/windows/multiple)
</Card>
<Card title="Frameless Windows" icon="star">
Create custom window chrome.
[Learn More →](/features/windows/frameless)
</Card>
<Card title="Window Events" icon="rocket">
Handle window lifecycle events.
[Learn More →](/features/windows/events)
</Card>
</CardGrid>
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [window examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).

View file

@ -0,0 +1,516 @@
---
title: Window Events
description: Handle window lifecycle and state change events
sidebar:
order: 5
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
## Window Events
Wails provides **comprehensive event handling** for window lifecycle and state changes: creation, focus/blur, resize/move, minimise/maximise, and close events. Register callbacks, handle events, and coordinate between windows with simple, consistent APIs.
## Event Registration Methods
Wails provides two methods for handling window events:
- **`OnWindowEvent()`** - Listen to window events (cannot prevent them)
- **`RegisterHook()`** - Hook into window events (can prevent them by calling `event.Cancel()`)
Both methods return a cleanup function that can be called to remove the listener/hook.
### OnWindowEvent
Registers a callback for window events. The callback receives a `*WindowEvent` argument.
```go
import "github.com/wailsapp/wails/v3/pkg/events"
// Listen for window focus
cancel := window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
fmt.Println("Window gained focus")
})
// Later, remove the listener
cancel()
```
### RegisterHook
Registers a hook that runs **before** listeners and can prevent the event by calling `event.Cancel()`.
```go
import "github.com/wailsapp/wails/v3/pkg/events"
// Prevent window from closing
cancel := window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
if hasUnsavedChanges() {
e.Cancel() // Prevent the close
}
})
```
## Lifecycle Events
### OnCreate
Called when a window is created. Register via the window manager:
```go
app.Window.OnCreate(func(window application.Window) {
fmt.Printf("Window created: %s\n", window.Name())
// Configure all new windows
window.SetMinSize(400, 300)
})
```
**Use cases:**
- Configure all windows consistently
- Register event handlers
- Track window creation
- Initialise window-specific resources
### WindowClosing
Fired when the user attempts to close a window. Use `RegisterHook` to cancel the close.
```go
// Prevent close with a hook
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
if hasUnsavedChanges() {
e.Cancel() // Prevent the window from closing
}
})
// React to close with a listener (cannot cancel)
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
fmt.Println("Window is closing, cleaning up...")
cleanup()
})
```
**Important:**
- Hooks run before listeners and can call `e.Cancel()` to prevent the close
- Listeners cannot cancel the event
## Focus Events
### WindowFocus / WindowLostFocus
```go
window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
fmt.Println("Window gained focus")
updateTitleBar(true)
})
window.OnWindowEvent(events.Common.WindowLostFocus, func(e *application.WindowEvent) {
fmt.Println("Window lost focus")
updateTitleBar(false)
})
```
**Use cases:**
- Update UI appearance
- Refresh data on focus
- Pause operations on blur
- Coordinate with other windows
**Example: Focus-aware UI:**
```go
window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
window.EmitEvent("update-theme", "active")
})
window.OnWindowEvent(events.Common.WindowLostFocus, func(e *application.WindowEvent) {
window.EmitEvent("update-theme", "inactive")
})
```
## State Change Events
### WindowMinimise / WindowUnMinimise
```go
window.OnWindowEvent(events.Common.WindowMinimise, func(e *application.WindowEvent) {
fmt.Println("Window minimised")
pauseRendering()
})
window.OnWindowEvent(events.Common.WindowUnMinimise, func(e *application.WindowEvent) {
fmt.Println("Window restored from minimised")
resumeRendering()
})
```
### WindowMaximise / WindowUnMaximise
```go
window.OnWindowEvent(events.Common.WindowMaximise, func(e *application.WindowEvent) {
fmt.Println("Window maximised")
window.EmitEvent("layout-mode", "maximised")
})
window.OnWindowEvent(events.Common.WindowUnMaximise, func(e *application.WindowEvent) {
fmt.Println("Window restored from maximised")
window.EmitEvent("layout-mode", "normal")
})
```
### WindowFullscreen / WindowUnFullscreen
```go
window.OnWindowEvent(events.Common.WindowFullscreen, func(e *application.WindowEvent) {
fmt.Println("Window entered fullscreen")
window.EmitEvent("chrome-visibility", false)
})
window.OnWindowEvent(events.Common.WindowUnFullscreen, func(e *application.WindowEvent) {
fmt.Println("Window exited fullscreen")
window.EmitEvent("chrome-visibility", true)
})
```
## Position and Size Events
### WindowDidMove
```go
window.OnWindowEvent(events.Common.WindowDidMove, func(e *application.WindowEvent) {
x, y := window.Position()
fmt.Printf("Window moved to: %d, %d\n", x, y)
saveWindowPosition(x, y)
})
```
### WindowDidResize
```go
window.OnWindowEvent(events.Common.WindowDidResize, func(e *application.WindowEvent) {
width, height := window.Size()
fmt.Printf("Window resized to: %dx%d\n", width, height)
saveWindowSize(width, height)
})
```
**Note:** The move and resize event callbacks receive a `*WindowEvent` argument. To get the new position or size, call `window.Position()` or `window.Size()` inside the callback.
## Common Window Events Reference
| Event | Description |
|-------|-------------|
| `events.Common.WindowClosing` | Window is about to close |
| `events.Common.WindowDidMove` | Window moved |
| `events.Common.WindowDidResize` | Window resized |
| `events.Common.WindowDPIChanged` | DPI scaling changed |
| `events.Common.WindowFilesDropped` | Files were dropped onto the window |
| `events.Common.WindowFocus` | Window gained focus |
| `events.Common.WindowFullscreen` | Window entered fullscreen |
| `events.Common.WindowHide` | Window was hidden |
| `events.Common.WindowLostFocus` | Window lost focus |
| `events.Common.WindowMaximise` | Window maximised |
| `events.Common.WindowMinimise` | Window minimised |
| `events.Common.WindowToggleFrameless` | Frameless mode toggled |
| `events.Common.WindowRestore` | Window restored from min/max |
| `events.Common.WindowRuntimeReady` | Wails runtime loaded in window |
| `events.Common.WindowShow` | Window became visible |
| `events.Common.WindowUnFullscreen` | Window exited fullscreen |
| `events.Common.WindowUnMaximise` | Window restored from maximised |
| `events.Common.WindowUnMinimise` | Window restored from minimised |
| `events.Common.WindowZoom` | Window zoom changed |
| `events.Common.WindowZoomIn` | Zoom level increased |
| `events.Common.WindowZoomOut` | Zoom level decreased |
| `events.Common.WindowZoomReset` | Zoom reset to default |
## Complete Example
Here's a production-ready window with full event handling:
```go
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)
type WindowState struct {
X int `json:"x"`
Y int `json:"y"`
Width int `json:"width"`
Height int `json:"height"`
Maximised bool `json:"maximised"`
Fullscreen bool `json:"fullscreen"`
}
type ManagedWindow struct {
app *application.App
window *application.WebviewWindow
state WindowState
dirty bool
}
func main() {
app := application.New(application.Options{
Name: "Event Demo",
})
mw := &ManagedWindow{app: app}
mw.CreateWindow()
mw.LoadState()
mw.SetupEventHandlers()
app.Run()
}
func (mw *ManagedWindow) CreateWindow() {
mw.window = mw.app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "main",
Title: "Event Demo",
Width: 800,
Height: 600,
})
}
func (mw *ManagedWindow) SetupEventHandlers() {
// Focus events
mw.window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
fmt.Println("Window focused")
mw.window.EmitEvent("focus-state", true)
})
mw.window.OnWindowEvent(events.Common.WindowLostFocus, func(e *application.WindowEvent) {
fmt.Println("Window blurred")
mw.window.EmitEvent("focus-state", false)
})
// State change events
mw.window.OnWindowEvent(events.Common.WindowMinimise, func(e *application.WindowEvent) {
fmt.Println("Window minimised")
mw.SaveState()
})
mw.window.OnWindowEvent(events.Common.WindowMaximise, func(e *application.WindowEvent) {
fmt.Println("Window maximised")
mw.state.Maximised = true
mw.dirty = true
})
mw.window.OnWindowEvent(events.Common.WindowUnMaximise, func(e *application.WindowEvent) {
fmt.Println("Window restored from maximised")
mw.state.Maximised = false
mw.dirty = true
})
mw.window.OnWindowEvent(events.Common.WindowFullscreen, func(e *application.WindowEvent) {
fmt.Println("Window fullscreen")
mw.state.Fullscreen = true
mw.dirty = true
})
mw.window.OnWindowEvent(events.Common.WindowUnFullscreen, func(e *application.WindowEvent) {
fmt.Println("Window exited fullscreen")
mw.state.Fullscreen = false
mw.dirty = true
})
// Position and size events
mw.window.OnWindowEvent(events.Common.WindowDidMove, func(e *application.WindowEvent) {
mw.state.X, mw.state.Y = mw.window.Position()
mw.dirty = true
})
mw.window.OnWindowEvent(events.Common.WindowDidResize, func(e *application.WindowEvent) {
mw.state.Width, mw.state.Height = mw.window.Size()
mw.dirty = true
})
// Lifecycle: save state on close
mw.window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
if mw.dirty {
mw.SaveState()
}
fmt.Println("Window closing")
})
}
func (mw *ManagedWindow) LoadState() {
data, err := os.ReadFile("window-state.json")
if err != nil {
return
}
if err := json.Unmarshal(data, &mw.state); err != nil {
return
}
// Restore window state
mw.window.SetPosition(mw.state.X, mw.state.Y)
mw.window.SetSize(mw.state.Width, mw.state.Height)
if mw.state.Maximised {
mw.window.Maximise()
}
if mw.state.Fullscreen {
mw.window.Fullscreen()
}
}
func (mw *ManagedWindow) SaveState() {
data, err := json.Marshal(mw.state)
if err != nil {
return
}
os.WriteFile("window-state.json", data, 0644)
mw.dirty = false
fmt.Println("Window state saved")
}
```
## Event Coordination
### Cross-Window Events
Coordinate between multiple windows:
```go
// In main window
window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
// Notify all windows via the application event system
app.Event.Emit("main-window-focused", nil)
})
// In other windows
app.Event.On("main-window-focused", func(event *application.CustomEvent) {
updateRelativeToMain()
})
```
### Event Chains
Chain events together:
```go
window.OnWindowEvent(events.Common.WindowMaximise, func(e *application.WindowEvent) {
// Save state
saveWindowState()
// Update layout
window.EmitEvent("layout-changed", "maximised")
// Notify other windows
app.Event.Emit("window-maximised", window.Name())
})
```
### Debounced Events
Debounce frequent events:
```go
var resizeTimer *time.Timer
window.OnWindowEvent(events.Common.WindowDidResize, func(e *application.WindowEvent) {
if resizeTimer != nil {
resizeTimer.Stop()
}
resizeTimer = time.AfterFunc(500*time.Millisecond, func() {
width, height := window.Size()
saveWindowSize(width, height)
})
})
```
## Best Practices
### Do
- **Save state on close** - Restore window position/size
- **Cleanup on close** - Release resources via `OnWindowEvent(events.Common.WindowClosing, ...)`
- **Debounce frequent events** - Resize, move
- **Handle focus changes** - Update UI appropriately
- **Coordinate windows** - Use events for communication
- **Test all events** - Ensure handlers work correctly
### Don't
- **Don't block event handlers** - Keep them fast
- **Don't forget cleanup** - Memory leaks
- **Don't ignore errors** - Log or handle them
- **Don't save on every event** - Debounce first
- **Don't create circular events** - Infinite loops
- **Don't forget platform differences** - Test thoroughly
## Troubleshooting
### Close Not Being Prevented
**Cause:** Using `OnWindowEvent` instead of `RegisterHook`
**Solution:**
```go
// OnWindowEvent cannot cancel events
// Use RegisterHook instead:
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
e.Cancel() // This prevents the close
})
```
### Events Not Firing
**Cause:** Handler registered after event occurred
**Solution:**
```go
// Register handlers immediately after creation
window := app.Window.New()
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
// handle close
})
```
### Memory Leaks
**Cause:** Not calling the cleanup function returned by event registration
**Solution:**
```go
// Store and call the cancel function when done
cancel := window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
// handle focus
})
// Later, remove the listener
cancel()
```
## Next Steps
**Window Basics** - Learn the fundamentals of window management
[Learn More ->](/features/windows/basics)
**Multiple Windows** - Patterns for multi-window applications
[Learn More ->](/features/windows/multiple)
**Events System** - Deep dive into the event system
[Learn More ->](/features/events/system)
**Application Lifecycle** - Understand the application lifecycle
[Learn More ->](/concepts/lifecycle)
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).

View file

@ -0,0 +1,870 @@
---
title: Frameless Windows
description: Create custom window chrome with frameless windows
sidebar:
order: 4
---
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
## Frameless Windows
Wails provides **frameless window support** with CSS-based drag regions and platform-native behaviour. Remove the platform-native title bar for complete control over window chrome, custom designs, and unique user experiences whilst maintaining essential functionality like dragging, resizing, and system controls.
## Quick Start
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Frameless App",
Width: 800,
Height: 600,
Frameless: true,
})
```
**CSS for draggable title bar:**
```css
.titlebar {
--wails-draggable: drag;
height: 40px;
background: #333;
}
.titlebar button {
--wails-draggable: no-drag;
}
```
**HTML:**
```html
<div class="titlebar">
<span>My Application</span>
<button onclick="window.close()">×</button>
</div>
```
**That's it!** You have a custom title bar.
## Creating Frameless Windows
### Basic Frameless Window
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Frameless: true,
Width: 800,
Height: 600,
})
```
**What you get:**
- No title bar
- No window borders
- No system buttons
- Transparent background (optional)
**What you need to implement:**
- Draggable area
- Close/minimise/maximise buttons
- Resize handles (if resizable)
### With Transparent Background
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Frameless: true,
BackgroundType: application.BackgroundTypeTransparent,
})
```
**Use cases:**
- Rounded corners
- Custom shapes
- Overlay windows
- Splash screens
## Drag Regions
### CSS-Based Dragging
Use the `--wails-draggable` CSS property:
```css
/* Draggable area */
.titlebar {
--wails-draggable: drag;
}
/* Non-draggable elements within draggable area */
.titlebar button {
--wails-draggable: no-drag;
}
```
**Values:**
- `drag` - Area is draggable
- `no-drag` - Area is not draggable (even if parent is)
### Complete Title Bar Example
```html
<div class="titlebar">
<div class="title">My Application</div>
<div class="controls">
<button class="minimize"></button>
<button class="maximize">□</button>
<button class="close">×</button>
</div>
</div>
```
```css
.titlebar {
--wails-draggable: drag;
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
background: #2c2c2c;
color: white;
padding: 0 16px;
}
.title {
font-size: 14px;
user-select: none;
}
.controls {
display: flex;
gap: 8px;
}
.controls button {
--wails-draggable: no-drag;
width: 32px;
height: 32px;
border: none;
background: transparent;
color: white;
font-size: 16px;
cursor: pointer;
border-radius: 4px;
}
.controls button:hover {
background: rgba(255, 255, 255, 0.1);
}
.controls .close:hover {
background: #e81123;
}
```
**JavaScript for buttons:**
```javascript
import { WindowMinimise, WindowMaximise, WindowClose } from '@wailsio/runtime'
document.querySelector('.minimize').addEventListener('click', WindowMinimise)
document.querySelector('.maximize').addEventListener('click', WindowMaximise)
document.querySelector('.close').addEventListener('click', WindowClose)
```
## System Buttons
### Implementing Close/Minimise/Maximise
**Go side:**
```go
type WindowControls struct {
window *application.WebviewWindow
}
func (wc *WindowControls) Minimise() {
wc.window.Minimise()
}
func (wc *WindowControls) Maximise() {
if wc.window.IsMaximised() {
wc.window.UnMaximise()
} else {
wc.window.Maximise()
}
}
func (wc *WindowControls) Close() {
wc.window.Close()
}
```
**JavaScript side:**
```javascript
import { Minimise, Maximise, Close } from './bindings/WindowControls'
document.querySelector('.minimize').addEventListener('click', Minimise)
document.querySelector('.maximize').addEventListener('click', Maximise)
document.querySelector('.close').addEventListener('click', Close)
```
**Or use runtime methods:**
```javascript
import {
WindowMinimise,
WindowMaximise,
WindowClose
} from '@wailsio/runtime'
document.querySelector('.minimize').addEventListener('click', WindowMinimise)
document.querySelector('.maximize').addEventListener('click', WindowMaximise)
document.querySelector('.close').addEventListener('click', WindowClose)
```
### Toggle Maximise State
Track maximise state for button icon:
```javascript
import { WindowIsMaximised, WindowMaximise, WindowUnMaximise } from '@wailsio/runtime'
async function toggleMaximise() {
const isMaximised = await WindowIsMaximised()
if (isMaximised) {
await WindowUnMaximise()
} else {
await WindowMaximise()
}
updateMaximiseButton()
}
async function updateMaximiseButton() {
const isMaximised = await WindowIsMaximised()
const button = document.querySelector('.maximize')
button.textContent = isMaximised ? '❐' : '□'
}
```
## Resize Handles
### CSS-Based Resize
Wails provides automatic resize handles for frameless windows:
```css
/* Enable resize on all edges */
body {
--wails-resize: all;
}
/* Or specific edges */
.resize-top {
--wails-resize: top;
}
.resize-bottom {
--wails-resize: bottom;
}
.resize-left {
--wails-resize: left;
}
.resize-right {
--wails-resize: right;
}
/* Corners */
.resize-top-left {
--wails-resize: top-left;
}
.resize-top-right {
--wails-resize: top-right;
}
.resize-bottom-left {
--wails-resize: bottom-left;
}
.resize-bottom-right {
--wails-resize: bottom-right;
}
```
**Values:**
- `all` - Resize from all edges
- `top`, `bottom`, `left`, `right` - Specific edges
- `top-left`, `top-right`, `bottom-left`, `bottom-right` - Corners
- `none` - No resize
### Resize Handle Example
```html
<div class="window">
<div class="titlebar">...</div>
<div class="content">...</div>
<div class="resize-handle resize-bottom-right"></div>
</div>
```
```css
.resize-handle {
position: absolute;
width: 16px;
height: 16px;
}
.resize-bottom-right {
--wails-resize: bottom-right;
bottom: 0;
right: 0;
cursor: nwse-resize;
}
```
## Platform-Specific Behaviour
<Tabs syncKey="platform">
<TabItem label="Windows" icon="seti:windows">
**Windows frameless windows:**
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Frameless: true,
Windows: application.WindowsWindow{
DisableFramelessWindowDecorations: false,
},
})
```
**Features:**
- Automatic drop shadow
- Snap layouts support (Windows 11)
- Aero Snap support
- DPI scaling
**Disable decorations:**
```go
Windows: application.WindowsWindow{
DisableFramelessWindowDecorations: true,
},
```
**Snap Assist:**
```go
// Trigger Windows 11 Snap Assist
window.SnapAssist()
```
**Custom title bar height:**
Windows automatically detects drag regions from CSS.
</TabItem>
<TabItem label="macOS" icon="apple">
**macOS frameless windows:**
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Frameless: true,
Mac: application.MacWindow{
TitleBar: application.MacTitleBar{
AppearsTransparent: true,
},
InvisibleTitleBarHeight: 40,
},
})
```
**Features:**
- Native fullscreen support
- Traffic light buttons (optional)
- Vibrancy effects
- Transparent title bar
**Hide traffic lights:**
```go
Mac: application.MacWindow{
TitleBar: application.MacTitleBarHidden,
},
```
**Invisible title bar:**
Allows dragging whilst hiding the title bar:
```go
Mac: application.MacWindow{
InvisibleTitleBarHeight: 40,
},
```
</TabItem>
<TabItem label="Linux" icon="linux">
**Linux frameless windows:**
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Frameless: true,
})
```
**Features:**
- Basic frameless support
- CSS drag regions
- Varies by desktop environment
**Desktop environment notes:**
- **GNOME:** Good support
- **KDE Plasma:** Good support
- **XFCE:** Basic support
- **Tiling WMs:** Limited support
**Compositor required:**
Transparency requires a compositor (most modern DEs have one).
</TabItem>
</Tabs>
## Common Patterns
### Pattern 1: Modern Title Bar
```html
<div class="modern-titlebar">
<div class="app-icon">
<img src="/icon.png" alt="App Icon">
</div>
<div class="title">My Application</div>
<div class="controls">
<button class="minimize"></button>
<button class="maximize">□</button>
<button class="close">×</button>
</div>
</div>
```
```css
.modern-titlebar {
--wails-draggable: drag;
display: flex;
align-items: center;
height: 40px;
background: linear-gradient(to bottom, #3a3a3a, #2c2c2c);
border-bottom: 1px solid #1a1a1a;
padding: 0 16px;
}
.app-icon {
--wails-draggable: no-drag;
width: 24px;
height: 24px;
margin-right: 12px;
}
.title {
flex: 1;
font-size: 13px;
color: #e0e0e0;
user-select: none;
}
.controls {
display: flex;
gap: 1px;
}
.controls button {
--wails-draggable: no-drag;
width: 46px;
height: 32px;
border: none;
background: transparent;
color: #e0e0e0;
font-size: 14px;
cursor: pointer;
transition: background 0.2s;
}
.controls button:hover {
background: rgba(255, 255, 255, 0.1);
}
.controls .close:hover {
background: #e81123;
color: white;
}
```
### Pattern 2: Splash Screen
```go
splash := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Loading...",
Width: 400,
Height: 300,
Frameless: true,
AlwaysOnTop: true,
BackgroundType: application.BackgroundTypeTransparent,
})
```
```css
body {
background: transparent;
display: flex;
justify-content: center;
align-items: center;
}
.splash {
background: white;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
padding: 40px;
text-align: center;
}
```
### Pattern 3: Rounded Window
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Frameless: true,
BackgroundType: application.BackgroundTypeTransparent,
})
```
```css
body {
background: transparent;
margin: 8px;
}
.window {
background: white;
border-radius: 16px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
overflow: hidden;
height: calc(100vh - 16px);
}
.titlebar {
--wails-draggable: drag;
background: #f5f5f5;
border-bottom: 1px solid #e0e0e0;
}
```
### Pattern 4: Overlay Window
```go
overlay := app.Window.NewWithOptions(application.WebviewWindowOptions{
Frameless: true,
AlwaysOnTop: true,
BackgroundType: application.BackgroundTypeTransparent,
})
```
```css
body {
background: transparent;
}
.overlay {
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
border-radius: 8px;
padding: 20px;
}
```
## Complete Example
Here's a production-ready frameless window:
**Go:**
```go
package main
import (
_ "embed"
"github.com/wailsapp/wails/v3/pkg/application"
)
//go:embed frontend/dist
var assets embed.FS
func main() {
app := application.New(application.Options{
Name: "Frameless App",
})
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Frameless Application",
Width: 1000,
Height: 700,
MinWidth: 800,
MinHeight: 600,
Frameless: true,
Mac: application.MacWindow{
TitleBar: application.MacTitleBar{
AppearsTransparent: true,
},
InvisibleTitleBarHeight: 40,
},
Windows: application.WindowsWindow{
DisableFramelessWindowDecorations: false,
},
})
window.Center()
window.Show()
app.Run()
}
```
**HTML:**
```html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<div class="window">
<div class="titlebar">
<div class="title">Frameless Application</div>
<div class="controls">
<button class="minimize" title="Minimise"></button>
<button class="maximize" title="Maximise">□</button>
<button class="close" title="Close">×</button>
</div>
</div>
<div class="content">
<h1>Hello from Frameless Window!</h1>
</div>
</div>
<script src="/main.js" type="module"></script>
</body>
</html>
```
**CSS:**
```css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
}
.window {
height: 100vh;
display: flex;
flex-direction: column;
}
.titlebar {
--wails-draggable: drag;
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
background: #ffffff;
border-bottom: 1px solid #e0e0e0;
padding: 0 16px;
}
.title {
font-size: 13px;
font-weight: 500;
color: #333;
user-select: none;
}
.controls {
display: flex;
gap: 8px;
}
.controls button {
--wails-draggable: no-drag;
width: 32px;
height: 32px;
border: none;
background: transparent;
color: #666;
font-size: 16px;
cursor: pointer;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.controls button:hover {
background: #f0f0f0;
color: #333;
}
.controls .close:hover {
background: #e81123;
color: white;
}
.content {
flex: 1;
padding: 40px;
overflow: auto;
}
```
**JavaScript:**
```javascript
import {
WindowMinimise,
WindowMaximise,
WindowUnMaximise,
WindowIsMaximised,
WindowClose
} from '@wailsio/runtime'
// Minimise button
document.querySelector('.minimize').addEventListener('click', () => {
WindowMinimise()
})
// Maximise/restore button
const maximiseBtn = document.querySelector('.maximize')
maximiseBtn.addEventListener('click', async () => {
const isMaximised = await WindowIsMaximised()
if (isMaximised) {
await WindowUnMaximise()
} else {
await WindowMaximise()
}
updateMaximiseButton()
})
// Close button
document.querySelector('.close').addEventListener('click', () => {
WindowClose()
})
// Update maximise button icon
async function updateMaximiseButton() {
const isMaximised = await WindowIsMaximised()
maximiseBtn.textContent = isMaximised ? '❐' : '□'
maximiseBtn.title = isMaximised ? 'Restore' : 'Maximise'
}
// Initial state
updateMaximiseButton()
```
## Best Practices
### ✅ Do
- **Provide draggable area** - Users need to move the window
- **Implement system buttons** - Close, minimise, maximise
- **Set minimum size** - Prevent unusable layouts
- **Test on all platforms** - Behaviour varies
- **Use CSS for drag regions** - Flexible and maintainable
- **Provide visual feedback** - Hover states on buttons
### ❌ Don't
- **Don't forget resize handles** - If window is resizable
- **Don't make entire window draggable** - Prevents interaction
- **Don't forget no-drag on buttons** - They won't work
- **Don't use tiny drag areas** - Hard to grab
- **Don't forget platform differences** - Test thoroughly
## Troubleshooting
### Window Won't Drag
**Cause:** Missing `--wails-draggable: drag`
**Solution:**
```css
.titlebar {
--wails-draggable: drag;
}
```
### Buttons Don't Work
**Cause:** Buttons are in draggable area
**Solution:**
```css
.titlebar button {
--wails-draggable: no-drag;
}
```
### Can't Resize Window
**Cause:** Missing resize handles
**Solution:**
```css
body {
--wails-resize: all;
}
```
## Next Steps
<CardGrid>
<Card title="Window Basics" icon="laptop">
Learn the fundamentals of window management.
[Learn More →](/features/windows/basics)
</Card>
<Card title="Window Options" icon="seti:config">
Complete reference for window options.
[Learn More →](/features/windows/options)
</Card>
<Card title="Window Events" icon="rocket">
Handle window lifecycle events.
[Learn More →](/features/windows/events)
</Card>
<Card title="Multiple Windows" icon="puzzle">
Patterns for multi-window applications.
[Learn More →](/features/windows/multiple)
</Card>
</CardGrid>
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [frameless example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/frameless).

View file

@ -0,0 +1,783 @@
---
title: Multiple Windows
description: Patterns and best practices for multi-window applications
sidebar:
order: 3
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
## Multi-Window Applications
Wails v3 provides **native multi-window support** for creating settings windows, document windows, tool palettes, and inspector windows. Track windows, enable communication between them, and manage their lifecycle with simple, consistent APIs.
### Main + Settings Window
```go
package main
import "github.com/wailsapp/wails/v3/pkg/application"
type App struct {
app *application.App
mainWindow *application.WebviewWindow
settingsWindow *application.WebviewWindow
}
func main() {
app := &App{}
app.app = application.New(application.Options{
Name: "Multi-Window App",
})
// Create main window
app.mainWindow = app.app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "main",
Title: "Main Application",
Width: 1200,
Height: 800,
})
// Create settings window (hidden initially)
app.settingsWindow = app.app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "settings",
Title: "Settings",
Width: 600,
Height: 400,
Hidden: true,
})
app.app.Run()
}
// Show settings from main window
func (a *App) ShowSettings() {
if a.settingsWindow != nil {
a.settingsWindow.Show()
a.settingsWindow.Focus()
}
}
```
**Key points:**
- Main window always visible
- Settings window created but hidden
- Show settings on demand
- Reuse same window (don't create multiple)
## Window Tracking
### Get All Windows
```go
windows := app.Window.GetAll()
fmt.Printf("Total windows: %d\n", len(windows))
for _, window := range windows {
fmt.Printf("- %s\n", window.Name())
}
```
### Find Specific Window
```go
// By name
settings, ok := app.Window.GetByName("settings")
if ok {
settings.Show()
}
// Current (focused) window
current := app.Window.Current()
```
### Window Registry Pattern
Track windows in your application:
```go
type WindowManager struct {
windows map[string]*application.WebviewWindow
mu sync.RWMutex
}
func (wm *WindowManager) Register(name string, window *application.WebviewWindow) {
wm.mu.Lock()
defer wm.mu.Unlock()
wm.windows[name] = window
}
func (wm *WindowManager) Get(name string) *application.WebviewWindow {
wm.mu.RLock()
defer wm.mu.RUnlock()
return wm.windows[name]
}
func (wm *WindowManager) Remove(name string) {
wm.mu.Lock()
defer wm.mu.Unlock()
delete(wm.windows, name)
}
```
## Window Communication
### Using Events
Windows communicate via the event system:
```go
// In main window - emit event
app.Event.Emit("settings-changed", map[string]interface{}{
"theme": "dark",
"fontSize": 14,
})
// In settings window - listen for event
app.Event.On("settings-changed", func(event *application.CustomEvent) {
data := event.Data.(map[string]interface{})
theme := data["theme"].(string)
fontSize := data["fontSize"].(int)
// Update UI
updateSettings(theme, fontSize)
})
```
### Shared State Pattern
Use a shared state manager:
```go
type AppState struct {
theme string
fontSize int
mu sync.RWMutex
}
var state = &AppState{
theme: "light",
fontSize: 12,
}
func (s *AppState) SetTheme(theme string) {
s.mu.Lock()
s.theme = theme
s.mu.Unlock()
// Notify all windows
app.Event.Emit("theme-changed", theme)
}
func (s *AppState) GetTheme() string {
s.mu.RLock()
defer s.mu.RUnlock()
return s.theme
}
```
### Window-to-Window Messages
Send messages between specific windows:
```go
// Get target window
targetWindow, ok := app.Window.GetByName("preview")
if ok {
// Emit event to specific window
targetWindow.EmitEvent("update-preview", previewData)
}
```
## Common Patterns
### Pattern 1: Singleton Windows
Ensure only one instance of a window exists:
```go
var settingsWindow *application.WebviewWindow
func ShowSettings(app *application.App) {
// Create if doesn't exist
if settingsWindow == nil {
settingsWindow = app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "settings",
Title: "Settings",
Width: 600,
Height: 400,
})
// Cleanup on close
settingsWindow.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
settingsWindow = nil
})
}
// Show and focus
settingsWindow.Show()
settingsWindow.Focus()
}
```
### Pattern 2: Document Windows
Multiple instances of the same window type:
```go
type DocumentWindow struct {
window *application.WebviewWindow
filePath string
modified bool
}
var documents = make(map[string]*DocumentWindow)
func OpenDocument(app *application.App, filePath string) {
// Check if already open
if doc, exists := documents[filePath]; exists {
doc.window.Show()
doc.window.Focus()
return
}
// Create new document window
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: filepath.Base(filePath),
Width: 800,
Height: 600,
})
doc := &DocumentWindow{
window: window,
filePath: filePath,
modified: false,
}
documents[filePath] = doc
// Cleanup on close
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
delete(documents, filePath)
})
// Load document
loadDocument(window, filePath)
}
```
### Pattern 3: Tool Palettes
Floating windows that stay on top:
```go
func CreateToolPalette(app *application.App) *application.WebviewWindow {
palette := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "tools",
Title: "Tools",
Width: 200,
Height: 400,
AlwaysOnTop: true,
DisableResize: true,
})
return palette
}
```
### Pattern 4: Modal dialogs
Child windows that block parent:
```go
func ShowModaldialog(parent *application.WebviewWindow, title string) {
dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: title,
Width: 400,
Height: 200,
AlwaysOnTop: true,
DisableResize: true,
})
// Disable parent (platform-specific)
parent.SetEnabled(false)
// Re-enable parent on close
dialog.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
parent.SetEnabled(true)
parent.Focus()
})
}
```
### Pattern 5: Inspector/Preview Windows
Linked windows that update together:
```go
type EditorApp struct {
editor *application.WebviewWindow
preview *application.WebviewWindow
}
func (e *EditorApp) UpdatePreview(content string) {
if e.preview != nil && e.preview.IsVisible() {
e.preview.EmitEvent("content-changed", content)
}
}
func (e *EditorApp) TogglePreview() {
if e.preview == nil {
e.preview = app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "preview",
Title: "Preview",
Width: 600,
Height: 800,
})
e.preview.OnWindowEvent(events.Common.WindowClosing, func(e2 *application.WindowEvent) {
e.preview = nil
})
}
if e.preview.IsVisible() {
e.preview.Hide()
} else {
e.preview.Show()
}
}
```
## Modal-like Behaviour
Create modal-like behaviour by disabling the parent window:
```go
func ShowModal(parent *application.WebviewWindow) {
modal := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Modal dialog",
Width: 400,
Height: 200,
AlwaysOnTop: true,
})
// Disable parent interaction
parent.SetEnabled(false)
// Re-enable on close
modal.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
parent.SetEnabled(true)
parent.Focus()
})
}
```
**Note:** True modal behaviour (blocking) varies by platform.
## Window Lifecycle Management
### Creation Callbacks
Be notified when windows are created:
```go
app.Window.OnCreate(func(window application.Window) {
fmt.Printf("Window created: %s\n", window.Name())
// Configure all new windows
window.SetMinSize(400, 300)
})
```
### Window Close Callbacks
Cleanup when windows are closed:
```go
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
fmt.Printf("Window %s closing\n", window.Name())
// Cleanup resources
cleanup(window.Name())
// Remove from tracking
removeFromRegistry(window.Name())
})
```
### Application Quit Behaviour
Control when application quits:
```go
app := application.New(application.Options{
Mac: application.MacOptions{
// Don't quit when last window closes
ApplicationShouldTerminateAfterLastWindowClosed: false,
},
})
```
**Use cases:**
- System tray applications
- Background services
- Menu bar applications (macOS)
## Memory Management
### Preventing Leaks
Always clean up window references:
```go
var windows = make(map[string]*application.WebviewWindow)
func CreateWindow(name string) {
window := app.Window.New()
windows[name] = window
// IMPORTANT: Clean up on close
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
delete(windows, name)
})
}
```
### Closing Windows
```go
// Close - triggers WindowClosing event, can be cancelled via RegisterHook
window.Close()
```
**Note:** `Close()` emits a `WindowClosing` event. Use `RegisterHook` to intercept and optionally cancel the close.
### Resource Cleanup
```go
type ManagedWindow struct {
window *application.WebviewWindow
resources []io.Closer
}
func (mw *ManagedWindow) CleanupAndClose() {
// Close all resources
for _, resource := range mw.resources {
resource.Close()
}
// Close window
mw.window.Close()
}
```
## Advanced Patterns
### Window Pool
Reuse windows instead of creating new ones:
```go
type WindowPool struct {
available []*application.WebviewWindow
inUse map[string]*application.WebviewWindow
mu sync.Mutex
}
func (wp *WindowPool) Acquire() *application.WebviewWindow {
wp.mu.Lock()
defer wp.mu.Unlock()
// Reuse available window
if len(wp.available) > 0 {
window := wp.available[0]
wp.available = wp.available[1:]
wp.inUse[window.Name()] = window
return window
}
// Create new window
window := app.Window.New()
wp.inUse[window.Name()] = window
return window
}
func (wp *WindowPool) Release(window *application.WebviewWindow) {
wp.mu.Lock()
defer wp.mu.Unlock()
delete(wp.inUse, window.Name())
window.Hide()
wp.available = append(wp.available, window)
}
```
### Window Groups
Manage related windows together:
```go
type WindowGroup struct {
name string
windows []*application.WebviewWindow
}
func (wg *WindowGroup) Add(window *application.WebviewWindow) {
wg.windows = append(wg.windows, window)
}
func (wg *WindowGroup) ShowAll() {
for _, window := range wg.windows {
window.Show()
}
}
func (wg *WindowGroup) HideAll() {
for _, window := range wg.windows {
window.Hide()
}
}
func (wg *WindowGroup) CloseAll() {
for _, window := range wg.windows {
window.Close()
}
}
```
### Workspace Management
Save and restore window layouts:
```go
type WindowLayout struct {
Windows []WindowState `json:"windows"`
}
type WindowState struct {
Name string `json:"name"`
X int `json:"x"`
Y int `json:"y"`
Width int `json:"width"`
Height int `json:"height"`
}
func SaveLayout() *WindowLayout {
layout := &WindowLayout{}
for _, window := range app.Window.GetAll() {
x, y := window.Position()
width, height := window.Size()
layout.Windows = append(layout.Windows, WindowState{
Name: window.Name(),
X: x,
Y: y,
Width: width,
Height: height,
})
}
return layout
}
func RestoreLayout(layout *WindowLayout) {
for _, state := range layout.Windows {
window, ok := app.Window.GetByName(state.Name)
if ok {
window.SetPosition(state.X, state.Y)
window.SetSize(state.Width, state.Height)
}
}
}
```
## Complete Example
Here's a production-ready multi-window application:
```go
package main
import (
"encoding/json"
"os"
"sync"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)
type MultiWindowApp struct {
app *application.App
windows map[string]*application.WebviewWindow
mu sync.RWMutex
}
func main() {
mwa := &MultiWindowApp{
windows: make(map[string]*application.WebviewWindow),
}
mwa.app = application.New(application.Options{
Name: "Multi-Window Application",
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: false,
},
})
// Create main window
mwa.CreateMainWindow()
// Load saved layout
mwa.LoadLayout()
mwa.app.Run()
}
func (mwa *MultiWindowApp) CreateMainWindow() {
window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "main",
Title: "Main Application",
Width: 1200,
Height: 800,
})
mwa.RegisterWindow("main", window)
}
func (mwa *MultiWindowApp) ShowSettings() {
if window := mwa.GetWindow("settings"); window != nil {
window.Show()
window.Focus()
return
}
window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "settings",
Title: "Settings",
Width: 600,
Height: 400,
})
mwa.RegisterWindow("settings", window)
}
func (mwa *MultiWindowApp) OpenDocument(path string) {
name := "doc-" + path
if window := mwa.GetWindow(name); window != nil {
window.Show()
window.Focus()
return
}
window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: name,
Title: path,
Width: 800,
Height: 600,
})
mwa.RegisterWindow(name, window)
}
func (mwa *MultiWindowApp) RegisterWindow(name string, window *application.WebviewWindow) {
mwa.mu.Lock()
mwa.windows[name] = window
mwa.mu.Unlock()
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
mwa.UnregisterWindow(name)
})
}
func (mwa *MultiWindowApp) UnregisterWindow(name string) {
mwa.mu.Lock()
delete(mwa.windows, name)
mwa.mu.Unlock()
}
func (mwa *MultiWindowApp) GetWindow(name string) *application.WebviewWindow {
mwa.mu.RLock()
defer mwa.mu.RUnlock()
return mwa.windows[name]
}
func (mwa *MultiWindowApp) SaveLayout() {
layout := make(map[string]WindowState)
mwa.mu.RLock()
for name, window := range mwa.windows {
x, y := window.Position()
width, height := window.Size()
layout[name] = WindowState{
X: x,
Y: y,
Width: width,
Height: height,
}
}
mwa.mu.RUnlock()
data, _ := json.Marshal(layout)
os.WriteFile("layout.json", data, 0644)
}
func (mwa *MultiWindowApp) LoadLayout() {
data, err := os.ReadFile("layout.json")
if err != nil {
return
}
var layout map[string]WindowState
if err := json.Unmarshal(data, &layout); err != nil {
return
}
for name, state := range layout {
if window := mwa.GetWindow(name); window != nil {
window.SetPosition(state.X, state.Y)
window.SetSize(state.Width, state.Height)
}
}
}
type WindowState struct {
X int `json:"x"`
Y int `json:"y"`
Width int `json:"width"`
Height int `json:"height"`
}
```
## Best Practices
### ✅ Do
- **Track windows** - Keep references for easy access
- **Clean up on close** - Use `OnWindowEvent` with `WindowClosing` to prevent memory leaks
- **Use events for communication** - Decoupled architecture
- **Reuse windows** - Don't create duplicates
- **Save/restore layouts** - Better UX
- **Handle window close** - Confirm before closing with unsaved data
### ❌ Don't
- **Don't create unlimited windows** - Memory and performance issues
- **Don't forget to clean up** - Memory leaks
- **Don't use global variables carelessly** - Thread-safety issues
- **Don't block window creation** - Create asynchronously if needed
- **Don't ignore platform differences** - Test on all platforms
## Next Steps
- [Window Basics](/features/windows/basics) - Learn the fundamentals of window management
- [Window Events](/features/windows/events) - Handle window lifecycle events
- [Events System](/features/events/system) - Deep dive into the event system
- [Frameless Windows](/features/windows/frameless) - Create custom window chrome
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [multi-window example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/multi-window).

View file

@ -0,0 +1,884 @@
---
title: Window Options
description: Complete reference for WebviewWindowOptions
sidebar:
order: 2
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
## Window Configuration Options
Wails provides comprehensive window configuration with dozens of options for size, position, appearance, and behaviour. This reference covers all available options across Windows, macOS, and Linux as the **complete reference** for `WebviewWindowOptions`. Every option, every platform, with examples and constraints.
## WebviewWindowOptions Structure
```go
type WebviewWindowOptions struct {
// Identity
Name string
Title string
// Size and Position
Width int
Height int
X int
Y int
MinWidth int
MinHeight int
MaxWidth int
MaxHeight int
InitialPosition WindowStartPosition
// Initial State
Hidden bool
Frameless bool
DisableResize bool
AlwaysOnTop bool
StartState WindowState
// Appearance
BackgroundColour RGBA
BackgroundType BackgroundType
// Content
URL string
HTML string
JS string
CSS string
// Zoom
Zoom float64
ZoomControlEnabled bool
// Features
EnableFileDrop bool
OpenInspectorOnStartup bool
DevToolsEnabled bool
DefaultContextMenuDisabled bool
IgnoreMouseEvents bool
// Security
ContentProtectionEnabled bool
// Toolbar button states
MinimiseButtonState ButtonState
MaximiseButtonState ButtonState
CloseButtonState ButtonState
// Key Bindings
KeyBindings map[string]func(window Window)
// Focus/Escape behaviour
HideOnFocusLost bool
HideOnEscape bool
// Menu
UseApplicationMenu bool
// Platform-Specific
Mac MacWindow
Windows WindowsWindow
Linux LinuxWindow
}
```
## Core Options
### Name
**Type:** `string`
**Default:** Auto-generated (`window-{id}`)
**Platform:** All
```go
Name: "main-window"
```
**Purpose:** Unique identifier for finding windows later.
**Best practices:**
- Use descriptive names: `"main"`, `"settings"`, `"about"`
- Use kebab-case: `"file-browser"`, `"color-picker"`
- Keep it short and memorable
**Example:**
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "settings-window",
})
// Later...
settings, ok := app.Window.GetByName("settings-window")
```
### Title
**Type:** `string`
**Default:** Application name
**Platform:** All
```go
Title: "My Application"
```
**Purpose:** Text shown in title bar and taskbar.
**Dynamic updates:**
```go
window.SetTitle("My Application - Document.txt")
```
### Width / Height
**Type:** `int` (pixels)
**Default:** 800 x 600
**Platform:** All
**Constraints:** Must be positive
```go
Width: 1200,
Height: 800,
```
**Purpose:** Initial window size in logical pixels.
**Notes:**
- Wails handles DPI scaling automatically
- Use logical pixels, not physical pixels
- Consider minimum screen resolution (1024x768)
**Example sizes:**
| Use Case | Width | Height |
|----------|-------|--------|
| Small utility | 400 | 300 |
| Standard app | 1024 | 768 |
| Large app | 1440 | 900 |
| Full HD | 1920 | 1080 |
### X / Y
**Type:** `int` (pixels)
**Default:** Centred on screen
**Platform:** All
```go
X: 100, // 100px from left edge
Y: 100, // 100px from top edge
```
**Purpose:** Initial window position.
**Coordinate system:**
- (0, 0) is top-left of primary screen
- Positive X goes right
- Positive Y goes down
**Best practice:** Use `Center()` instead:
```go
window := app.Window.New()
window.Center()
```
### MinWidth / MinHeight
**Type:** `int` (pixels)
**Default:** 0 (no minimum)
**Platform:** All
```go
MinWidth: 400,
MinHeight: 300,
```
**Purpose:** Prevent window from being too small.
**Use cases:**
- Prevent broken layouts
- Ensure usability
- Maintain aspect ratio
**Example:**
```go
// Prevent window smaller than 400x300
MinWidth: 400,
MinHeight: 300,
```
### MaxWidth / MaxHeight
**Type:** `int` (pixels)
**Default:** 0 (no maximum)
**Platform:** All
```go
MaxWidth: 1920,
MaxHeight: 1080,
```
**Purpose:** Prevent window from being too large.
**Use cases:**
- Fixed-size applications
- Prevent excessive resource usage
- Maintain design constraints
## State Options
### Hidden
**Type:** `bool`
**Default:** `false`
**Platform:** All
```go
Hidden: true,
```
**Purpose:** Create window without showing it.
**Use cases:**
- Background windows
- Windows shown on demand
- Splash screens (create, load, then show)
- Prevent white flash while loading content
**Platform improvements:**
- **Windows:** Fixed white window flash - window stays invisible until `Show()` is called
- **macOS:** Full support
- **Linux:** Full support
**Recommended pattern for smooth loading:**
```go
// Create hidden window
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "main-window",
Hidden: true,
BackgroundColour: application.NewRGBA(30, 30, 30, 255), // Match your theme
})
// Load content while hidden
// ... content loads ...
// Show when ready (no flash!)
window.Show()
```
**Example:**
```go
settings := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "settings",
Hidden: true,
})
// Show when needed
settings.Show()
```
### Frameless
**Type:** `bool`
**Default:** `false`
**Platform:** All
```go
Frameless: true,
```
**Purpose:** Remove title bar and window borders.
**Use cases:**
- Custom window chrome
- Splash screens
- Kiosk applications
- Custom-designed windows
**Important:** You'll need to implement:
- Window dragging
- Close/minimise/maximise buttons
- Resize handles (if resizable)
**See [Frameless Windows](/features/windows/frameless) for details.**
### DisableResize
**Type:** `bool`
**Default:** `false`
**Platform:** All
```go
DisableResize: true,
```
**Purpose:** Prevent window resizing when set to `true`.
**Use cases:**
- Fixed-size applications
- Splash screens
- Dialogs
**Note:** Users can still maximise/fullscreen unless you prevent those too.
**Runtime control:**
```go
window.SetResizable(false) // Disable resizing
window.SetResizable(true) // Enable resizing
```
### AlwaysOnTop
**Type:** `bool`
**Default:** `false`
**Platform:** All
```go
AlwaysOnTop: true,
```
**Purpose:** Keep window above all other windows.
**Use cases:**
- Floating toolbars
- Notifications
- Picture-in-picture
- Timers
**Platform notes:**
- **macOS:** Full support
- **Windows:** Full support
- **Linux:** Depends on window manager
### StartState
**Type:** `WindowState` enum
**Default:** `WindowStateNormal`
**Platform:** All
```go
StartState: application.WindowStateMaximised,
```
**Purpose:** Set the initial window state.
**Values:**
- `WindowStateNormal` - Normal window
- `WindowStateMinimised` - Minimised
- `WindowStateMaximised` - Maximised
- `WindowStateFullscreen` - Fullscreen
**Platform behaviour for fullscreen:**
- **macOS:** Creates new Space (virtual desktop)
- **Windows:** Covers taskbar
- **Linux:** Varies by desktop environment
**Toggle at runtime:**
```go
window.Fullscreen()
window.UnFullscreen()
window.Maximise()
window.UnMaximise()
window.Minimise()
window.UnMinimise()
```
## Appearance Options
### BackgroundColour
**Type:** `RGBA` struct
**Default:** White (255, 255, 255, 255)
**Platform:** All
```go
BackgroundColour: application.NewRGBA(0, 0, 0, 255),
```
**Purpose:** Window background colour before content loads.
**Use cases:**
- Match your app's theme
- Prevent white flash on dark themes
- Smooth loading experience
**Example:**
```go
// Dark theme
BackgroundColour: application.NewRGBA(30, 30, 30, 255),
// Light theme
BackgroundColour: application.NewRGB(255, 255, 255),
```
**Helper method:**
```go
window.SetBackgroundColour(application.NewRGBA(30, 30, 30, 255))
```
### BackgroundType
**Type:** `BackgroundType` enum
**Default:** `BackgroundTypeSolid`
**Platform:** macOS, Windows (partial)
```go
BackgroundType: application.BackgroundTypeTranslucent,
```
**Values:**
- `BackgroundTypeSolid` - Solid colour
- `BackgroundTypeTransparent` - Fully transparent
- `BackgroundTypeTranslucent` - Semi-transparent blur
**Platform support:**
- **macOS:** All types supported
- **Windows:** Transparent and Translucent (Windows 11+)
- **Linux:** Solid only
**Example (macOS):**
```go
BackgroundType: application.BackgroundTypeTranslucent,
Mac: application.MacWindow{
Backdrop: application.MacBackdropTranslucent,
},
```
## Content Options
### URL
**Type:** `string`
**Default:** Empty (loads from Assets)
**Platform:** All
```go
URL: "https://example.com",
```
**Purpose:** Load external URL instead of embedded assets.
**Use cases:**
- Development (load from dev server)
- Web-based applications
- Hybrid applications
**Example:**
```go
// Development
URL: "http://localhost:5173",
// Production
Assets: application.AssetOptions{
Handler: application.AssetFileServerFS(assets),
},
```
### HTML
**Type:** `string`
**Default:** Empty
**Platform:** All
```go
HTML: "<h1>Hello World</h1>",
```
**Purpose:** Load HTML string directly.
**Use cases:**
- Simple windows
- Generated content
- Testing
**Example:**
```go
HTML: `
<!DOCTYPE html>
<html>
<head><title>Simple Window</title></head>
<body><h1>Hello from Wails!</h1></body>
</html>
`,
```
## Security Options
### ContentProtectionEnabled
**Type:** `bool`
**Default:** `false`
**Platform:** Windows (10+), macOS
```go
ContentProtectionEnabled: true,
```
**Purpose:** Prevent screen capture of window contents.
**Platform support:**
- **Windows:** Windows 10 build 19041+ (full), older versions (partial)
- **macOS:** Full support
- **Linux:** Not supported
**Use cases:**
- Banking applications
- Password managers
- Medical records
- Confidential documents
**Important notes:**
1. Doesn't prevent physical photography
2. Some tools may bypass protection
3. Part of comprehensive security, not sole protection
4. DevTools windows not protected automatically
**Example:**
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Secure Window",
ContentProtectionEnabled: true,
})
// Toggle at runtime
window.SetContentProtection(true)
```
## Lifecycle Handling
Window lifecycle events are handled via `OnWindowEvent` and `RegisterHook` methods after window creation, not through options fields.
### Preventing Close
Use `RegisterHook` to intercept close events and optionally cancel them:
```go
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
if hasUnsavedChanges() {
e.Cancel() // Prevent the window from closing
}
})
```
### Cleanup on Close
Use `OnWindowEvent` for cleanup when the window closes:
```go
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
// Close database connection
if db != nil {
db.Close()
}
// Remove from window list
removeWindow(window.Name())
})
```
**See [Window Events](/features/windows/events) for details.**
## Platform-Specific Options
### Mac Options
```go
Mac: application.MacWindow{
TitleBar: application.MacTitleBar{
AppearsTransparent: true,
HideTitle: true,
FullSizeContent: true,
},
Backdrop: application.MacBackdropTranslucent,
InvisibleTitleBarHeight: 50,
},
```
**TitleBar** (`MacTitleBar`)
- `AppearsTransparent` - Makes title bar transparent
- `Hide` - Hides the entire title bar
- `HideTitle` - Hides just the title text
- `FullSizeContent` - Extends content to full window size
- `UseToolbar` - Uses a toolbar instead of title bar
- `HideToolbarSeparator` - Hides the toolbar separator
- `ToolbarStyle` - Style of toolbar (`MacToolbarStyleAutomatic`, `MacToolbarStyleExpanded`, etc.)
**Predefined title bar configurations:**
- `MacTitleBarDefault` - Standard title bar
- `MacTitleBarHidden` - Hidden title bar with traffic lights
- `MacTitleBarHiddenInset` - Hidden with inset traffic lights
- `MacTitleBarHiddenInsetUnified` - Hidden with unified toolbar style
**DisableShadow** (`bool`)
- Disable the window shadow
**Backdrop** (`MacBackdrop`)
- `MacBackdropNormal` - Standard opaque background
- `MacBackdropTransparent` - Fully transparent
- `MacBackdropTranslucent` - Blurred translucent
- `MacBackdropLiquidGlass` - Apple's Liquid Glass effect (macOS 15.0+, falls back to translucent)
**InvisibleTitleBarHeight** (`int`)
- Height of invisible title bar (for dragging)
**Appearance** (`MacAppearanceType`)
- `DefaultAppearance` - System default
- `NSAppearanceNameAqua` - Light appearance
- `NSAppearanceNameDarkAqua` - Dark appearance
- `NSAppearanceNameVibrantLight` - Light vibrant appearance
- `NSAppearanceNameAccessibilityHighContrastAqua` - High-contrast light
- `NSAppearanceNameAccessibilityHighContrastDarkAqua` - High-contrast dark
- `NSAppearanceNameAccessibilityHighContrastVibrantLight` - High-contrast vibrant light
- `NSAppearanceNameAccessibilityHighContrastVibrantDark` - High-contrast vibrant dark
**EnableFraudulentWebsiteWarnings** (`bool`)
- Enable warnings for fraudulent websites
**WebviewPreferences** (`MacWebviewPreferences`)
- `TabFocusesLinks` - Enable tabbing to links
- `TextInteractionEnabled` - Enable text interaction
- `FullscreenEnabled` - Enable fullscreen
- `AllowsBackForwardNavigationGestures` - Enable horizontal swipe gestures for back/forward navigation
**WindowLevel** (`MacWindowLevel`)
- Controls the window level ordering. Values: `MacWindowLevelNormal`, `MacWindowLevelFloating`, `MacWindowLevelTornOffMenu`, `MacWindowLevelModalPanel`, `MacWindowLevelMainMenu`, `MacWindowLevelStatus`, `MacWindowLevelPopUpMenu`, `MacWindowLevelScreenSaver`
**CollectionBehavior** (`MacWindowCollectionBehavior`)
- Controls how the window behaves across macOS Spaces and fullscreen. Values can be combined with bitwise OR. Includes: `MacWindowCollectionBehaviorCanJoinAllSpaces`, `MacWindowCollectionBehaviorMoveToActiveSpace`, `MacWindowCollectionBehaviorFullScreenPrimary`, `MacWindowCollectionBehaviorFullScreenAuxiliary`, etc.
**EventMapping** (`map[events.WindowEventType]events.WindowEventType`)
- Maps platform-specific events to common event types
**LiquidGlass** (`MacLiquidGlass`)
- Configuration for the Liquid Glass effect: `Style`, `Material`, `CornerRadius`, `TintColor`, `GroupID`, `GroupSpacing`
**ShowToolbarWhenFullscreen** (in `MacTitleBar`)
- Keep the toolbar visible when the window is in fullscreen mode
**Example:**
```go
Mac: application.MacWindow{
TitleBar: application.MacTitleBarHidden,
Backdrop: application.MacBackdropTranslucent,
InvisibleTitleBarHeight: 50,
},
```
### Windows Options
```go
Windows: application.WindowsWindow{
DisableIcon: false,
BackdropType: application.Mica,
CustomTheme: nil,
DisableFramelessWindowDecorations: false,
},
```
**DisableIcon** (`bool`)
- Remove icon from title bar
- Cleaner appearance
**BackdropType** (`BackdropType`)
- `Auto` - System default
- `None` - No backdrop
- `Mica` - Mica material (Windows 11 22621+)
- `Acrylic` - Acrylic material (Windows 11 22621+)
- `Tabbed` - Tabbed material (Windows 11 22621+)
**Theme** (`Theme`)
- `SystemDefault` - Follow system theme
- `Dark` - Dark mode
- `Light` - Light mode
**CustomTheme** (`ThemeSettings`)
- Custom colour theme for dark/light mode
- Override system colours
**DisableFramelessWindowDecorations** (`bool`)
- Disable default frameless decorations (shadow and rounded corners)
- For custom window chrome
**WindowMask** (`[]byte`)
- Set the window shape using a PNG with an alpha channel
**WindowMaskDraggable** (`bool`)
- Make the window draggable by clicking on the window mask
**ResizeDebounceMS** (`uint16`)
- Amount of time in milliseconds to debounce redraws of webview2 when resizing
**WindowDidMoveDebounceMS** (`uint16`)
- Amount of time in milliseconds to debounce the WindowDidMove event when moving
**EventMapping** (`map[events.WindowEventType]events.WindowEventType`)
- Maps platform-specific events to common event types
**HiddenOnTaskbar** (`bool`)
- Hide the window from the taskbar
**EnableSwipeGestures** (`bool`)
- Enable swipe gestures for the window
**Menu** (`*Menu`)
- The menu to use for the window
**Permissions** (`map[CoreWebView2PermissionKind]CoreWebView2PermissionState`)
- WebView2 permissions map. If empty, default permissions will be granted.
**ExStyle** (`int`)
- Extended window style
**GeneralAutofillEnabled** (`bool`)
- Enable general autofill
**PasswordAutosaveEnabled** (`bool`)
- Enable autosaving passwords
**Example:**
```go
Windows: application.WindowsWindow{
BackdropType: application.Mica,
DisableIcon: true,
Theme: application.Dark,
},
```
### Linux Options
```go
Linux: application.LinuxWindow{
Icon: []byte{/* PNG data */},
WindowIsTranslucent: false,
},
```
**Icon** (`[]byte`)
- Window icon (PNG format)
- Shown in title bar and taskbar
**WindowIsTranslucent** (`bool`)
- Enable window translucency
- Requires compositor support
**WebviewGpuPolicy** (`WebviewGpuPolicy`)
- `WebviewGpuPolicyAlways` - Hardware acceleration always enabled
- `WebviewGpuPolicyOnDemand` - Enabled/disabled as requested by web contents
- `WebviewGpuPolicyNever` - Hardware acceleration always disabled
**WindowDidMoveDebounceMS** (`uint16`)
- Debounce time in milliseconds for the WindowDidMove event
**Menu** (`*Menu`)
- The window's menu
**MenuStyle** (`LinuxMenuStyle`) - GTK4 only, ignored on GTK3
- `LinuxMenuStyleMenuBar` - Traditional menu bar below the title bar (default)
- `LinuxMenuStylePrimaryMenu` - Primary menu button in the header bar (GNOME style)
**Example:**
```go
//go:embed icon.png
var icon []byte
Linux: application.LinuxWindow{
Icon: icon,
},
```
## Complete Example
Here's a production-ready window configuration:
```go
package main
import (
_ "embed"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)
//go:embed icon.png
var icon []byte
func main() {
app := application.New(application.Options{
Name: "My Application",
})
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
// Identity
Name: "main-window",
Title: "My Application",
// Size and Position
Width: 1200,
Height: 800,
MinWidth: 800,
MinHeight: 600,
// Initial State
StartState: application.WindowStateNormal,
// Appearance
BackgroundColour: application.NewRGB(255, 255, 255),
// Platform-Specific
Mac: application.MacWindow{
TitleBar: application.MacTitleBar{
AppearsTransparent: true,
},
Backdrop: application.MacBackdropTranslucent,
},
Windows: application.WindowsWindow{
BackdropType: application.Mica,
DisableIcon: false,
},
Linux: application.LinuxWindow{
Icon: icon,
},
})
// Handle close events after creation
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
if hasUnsavedChanges() {
e.Cancel()
}
})
window.Center()
window.Show()
app.Run()
}
```
## Next Steps
- [Window Basics](/features/windows/basics) - Creating and controlling windows
- [Multiple Windows](/features/windows/multiple) - Multi-window patterns
- [Frameless Windows](/features/windows/frameless) - Custom window chrome
- [Window Events](/features/windows/events) - Lifecycle events
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).

View file

@ -0,0 +1,86 @@
---
title: v3 Alpha Feedback
sidebar:
order: 40
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
We welcome (and encourage) your feedback! Please search for existing tickets or
posts before creating new ones. Here are the different ways to provide feedback:
<Tabs>
<TabItem label="Bugs" icon="error">
If you find a bug, please let us know by posting into the [v3 Alpha Feedback](https://discord.gg/bdj28QNHmT) channel on Discord.
- The post should clearly state what the bug is and have a simple reproducible example. If the docs are unclear what *should* happen, please include that in the post.
- The post should be given the `Bug` tag.
- Please include the output of `wails doctor` in your post.
- If the bug is behavior that does not align with current documentation, e.g. a window does not resize properly, please do the following:
- Update an existing example in the `v3/example` directory or create a new example in the `v3/examples` folder that clearly shows the issue.
- Open a [PR](https://github.com/wailsapp/wails/pulls) with the title `[v3 alpha test] <description of bug>`.
- Please include a link to the PR in your post.
:::caution
_Remember_, unexpected behavior isn't necessarily a bug - it might just not do
what you expect it to do. Use `Suggestions` for this.
:::
</TabItem>
<TabItem label="Fixes" icon="approve-check">
If you have a fix for a bug or an update for documentation, please do the following:
- Open a pull request on the [Wails repository](https://github.com/wailsapp/wails). The title of the PR should start with `[v3 alpha]`.
- Create a post in the [v3 Alpha Feedback](https://discord.gg/bdj28QNHmT) channel.
- The post should be given the `PR` tag.
- Please include a link to the PR in your post.
</TabItem>
<TabItem label="Suggestions" icon="puzzle" id="abc">
If you have a suggestion, please let us know by posting into the [v3 Alpha Feedback](https://discord.gg/bdj28QNHmT) channel on Discord:
- The post should be given the `Suggestion` tag.
Please feel free to reach out to us on [Discord](https://discord.gg/bdj28QNHmT) if you have any questions.
</TabItem>
<TabItem label="Upvoting" icon="star">
- Posts can be "upvoted" by using the :thumbsup: emoji. Please apply to any posts that are a priority for you.
- Please *don't* just add comments like "+1" or "me too".
- Please feel free to comment if there is more to add to the post, such as "this bug also affect ARM builds" or "Another option would be to ....."
</TabItem>
</Tabs>
There is a list of known issues & work in progress can be found
[here](https://github.com/orgs/wailsapp/projects/6).
## Things we are looking for feedback on
- The API
- Is it easy to use?
- Does it do what you expect?
- Is it missing anything?
- Is there anything that should be removed?
- Is it consistent between Go and JS?
- The build system
- Is it easy to use?
- Can we improve it?
- The examples
- Are they clear?
- Do they cover the basics?
- Features
- What features are missing?
- What features are not needed?
- Documentation
- What could be clearer?

View file

@ -0,0 +1,114 @@
---
title: Installation
sidebar:
order: 10
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
## Supported Platforms
- Windows 10/11 AMD64/ARM64
- macOS 10.15+ AMD64 (Can deploy to macOS 10.13+)
- macOS 11.0+ ARM64
- Ubuntu 24.04 AMD64/ARM64 (other Linux may work too!)
## Dependencies
Wails has a number of common dependencies that are required before installation:
<Tabs>
<TabItem label="Go (At least 1.24)" icon="seti:go">
Download Go from the [Go Downloads Page](https://go.dev/dl/).
Ensure that you follow the official [Go installation instructions](https://go.dev/doc/install). You will also need to ensure that your `PATH` environment variable also includes the path to your `~/go/bin` directory. Restart your terminal and do the following checks:
- Check Go is installed correctly: `go version`
- Check `~/go/bin` is in your PATH variable
- Mac / Linux: `echo $PATH | grep go/bin`
- Windows: `$env:PATH -split ';' | Where-Object { $_ -like '*\go\bin' }`
</TabItem>
<TabItem label="npm (Optional)" icon="seti:npm">
Although Wails doesn't require npm to be installed, it is needed by most of the bundled templates.
Download the latest node installer from the [Node Downloads Page](https://nodejs.org/en/download/). It is best to use the latest release as that is what we generally test against.
Run `npm --version` to verify.
:::note
If you prefer a different package manager to npm, feel free to use it. You will need to update the project Taskfiles to use it.
:::
</TabItem>
</Tabs>
## Platform Specific Dependencies
You will also need to install platform specific dependencies:
<Tabs syncKey="platform">
<TabItem label="Mac" icon="apple">
Wails requires that the xcode command line tools are installed. This can be
done by running:
```sh
xcode-select --install
```
</TabItem>
<TabItem label="Windows" icon="seti:windows">
Wails requires that the [WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) is installed. Almost all Windows installations will already have this installed. You can check using the `wails3 doctor` command.
</TabItem>
<TabItem label="Linux" icon="linux">
Linux requires the standard `gcc` build tools plus `gtk3` and `webkit2gtk`. Run <code>wails3 doctor</code> after installation to be shown how to install the dependencies. If your distro/package manager is not supported, please let us know on discord.
</TabItem>
</Tabs>
## Installation
To install the Wails CLI using Go Modules, run the following commands:
```shell
go install -v github.com/wailsapp/wails/v3/cmd/wails3@latest
```
If you would like to install the latest development version, run the following
commands:
```shell
git clone https://github.com/wailsapp/wails.git
cd wails
git checkout v3-alpha
cd v3/cmd/wails3
go install
```
When using the development version, all generated projects will use Go's
[replace](https://go.dev/ref/mod#go-mod-file-replace) directive to ensure
projects use the development version of Wails.
## System Check
Running `wails3 doctor` will check if you have the correct dependencies
installed. If not, it will advise on what is missing and help on how to rectify
any problems.
## The `wails3` command appears to be missing?
If your system is reporting that the `wails3` command is missing, check the
following:
- Make sure you have followed the above `Go installation guide` correctly and
that the `go/bin` directory is in the `PATH` environment variable.
- Close/Reopen current terminals to pick up the new `PATH` variable.

View file

@ -0,0 +1,272 @@
---
title: Your First Application
sidebar:
order: 20
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
import { Badge } from '@astrojs/starlight/components';
import { Steps } from "@astrojs/starlight/components";
import { FileTree } from '@astrojs/starlight/components';
import wails_init from '../../../assets/wails_init.mp4';
import wails_build from '../../../assets/wails_build.mp4';
import wails_dev from '../../../assets/wails_dev.mp4';
This guide shows you how to create your first Wails v3 application, covering project setup, building, and development workflow.
<br/>
<br/>
<Steps>
1. ## Creating a New Project
Open your terminal and run the following command to create a new Wails
project:
```bash
wails3 init -n myfirstapp
```
This command creates a new directory called `myfirstapp` with all the
necessary files.
<video src={wails_init} controls></video>
2. ## Exploring the Project Structure
Navigate to the `myfirstapp` directory. You'll find several files and
folders:
<FileTree>
- build/ Contains files used by the build process
- appicon.png The application icon
- config.yml Build configuration
- Taskfile.yml Build tasks
- darwin/ macOS specific build files
- Info.dev.plist Development configuration
- Info.plist Production configuration
- Taskfile.yml macOS build tasks
- icons.icns macOS application icon
- linux/ Linux specific build files
- Taskfile.yml Linux build tasks
- appimage/ AppImage packaging
- build.sh AppImage build script
- nfpm/ NFPM packaging
- nfpm.yaml Package configuration
- scripts/ Build scripts
- windows/ Windows specific build files
- Taskfile.yml Windows build tasks
- icon.ico Windows application icon
- info.json Application metadata
- wails.exe.manifest Windows manifest file
- nsis/ NSIS installer files
- project.nsi NSIS project file
- wails_tools.nsh NSIS helper scripts
- frontend/ Frontend application files
- index.html Main HTML file
- main.js Main JavaScript file
- package.json NPM package configuration
- public/ Static assets
- Inter Font License.txt Font license
- .gitignore Git ignore file
- README.md Project documentation
- Taskfile.yml Project tasks
- go.mod Go module file
- go.sum Go module checksums
- greetservice.go Greeting service
- main.go Main application code
</FileTree>
Take a moment to explore these files and familiarize yourself with the
structure.
:::note
Although Wails v3 uses [Task](https://taskfile.dev/) as its
default build system, there is nothing stopping you from using `make` or any other
alternative build system.
:::
3. ## Building Your Application
To build your application, execute:
```bash
wails3 build
```
This command compiles a debug version of your application and saves it in a
new `bin` directory.
:::note
`wails3 build` is shorthand for `wails3 task build` and will run the `build` task in `Taskfile.yml`.
:::
<video src={wails_build} controls></video>
Once built, you can run this like you would any normal application:
<Tabs syncKey="platform">
<TabItem label="Mac" icon="apple">
```sh
./bin/myfirstapp
```
</TabItem>
<TabItem label="Windows" icon="seti:windows">
```sh
bin\myfirstapp.exe
```
</TabItem>
<TabItem label="Linux" icon="linux">
```sh
./bin/myfirstapp
```
</TabItem>
</Tabs>
You'll see a simple UI, the starting point for your application. As it is
the debug version, you'll also see logs in the console window. This is
useful for debugging purposes.
4. ## Dev Mode
We can also run the application in development mode. This mode allows you to
make changes to your frontend code and see the changes reflected in the
running application without having to rebuild the entire application.
1. Open a new terminal window.
2. Run `wails3 dev`. The application will compile and run in debug mode.
3. Open `frontend/index.html` in your editor of choice.
4. Edit the code and change `Please enter your name below` to
`Please enter your name below!!!`.
5. Save the file.
This change will reflect in your application immediately.
Any changes to backend code will trigger a rebuild:
1. Open `greetservice.go`.
2. Change the line that has `return "Hello " + name + "!"` to
`return "Hello there " + name + "!"`.
3. Save the file.
The application will update within a matter of seconds.
<video src={wails_dev} controls></video>
5. ## Packaging Your Application
Once your application is ready for distribution, you can create
platform-specific packages:
<Tabs syncKey="platform">
<TabItem label="Mac" icon="apple">
To create a `.app` bundle:
```bash
wails3 package
```
This will create a production build and package it into a `.app` bundle in the `bin` directory.
</TabItem>
<TabItem label="Windows" icon="seti:windows">
To create an NSIS installer:
```bash
wails3 package
```
This will create a production build and package it into an NSIS installer in the `bin` directory.
</TabItem>
<TabItem label="Linux" icon="linux">
Wails supports multiple package formats for Linux distribution:
```bash
# Create all package types (AppImage, deb, rpm, and Arch Linux)
wails3 package
# Or create specific package types
wails3 task linux:create:appimage # AppImage format
wails3 task linux:create:deb # Debian package
wails3 task linux:create:rpm # Red Hat package
wails3 task linux:create:aur # Arch Linux package
```
</TabItem>
</Tabs>
For more detailed information about packaging options and configuration,
check out our [Packaging Guide](/guides/packaging).
6. ## Setting up Version Control and Module Name
Your project is created with the placeholder module name `changeme`. It's recommended to update this to match your repository URL:
1. Create a new repository on GitHub (or your preferred Git host)
2. Initialize git in your project directory:
```bash
git init
git add .
git commit -m "Initial commit"
```
3. Set your remote repository (replace with your repository URL):
```bash
git remote add origin https://github.com/username/myfirstapp.git
```
4. Update your module name in `go.mod` to match your repository URL:
```bash
go mod edit -module github.com/username/myfirstapp
```
5. Push your code:
```bash
git push -u origin main
```
This ensures your Go module name aligns with Go's module naming conventions and makes it easier to share your code.
:::tip[Pro Tip]
You can automate all of the initialisation steps by using the `-git` flag when creating your project:
```bash
wails3 init -n myfirstapp -git github.com/username/myfirstapp
```
This supports various Git URL formats:
- HTTPS: `https://github.com/username/project`
- SSH: `git@github.com:username/project` or `ssh://git@github.com/username/project`
- Git protocol: `git://github.com/username/project`
- Filesystem: `file:///path/to/project.git`
:::
</Steps>
## Congratulations!
You've just created, developed and packaged your first Wails application.
This is just the beginning of what you can achieve with Wails v3.
## Next Steps
If you are new to Wails, we recommend reading through our Tutorials next which will be a practical guide through
the various features of Wails. The first tutorial is [Creating a Service](/tutorials/01-creating-a-service).
If you are a more advanced user, check out our [Guides](/guides) for more detailed information on how to use Wails.

View file

@ -0,0 +1,8 @@
{
"label": "Guides",
"position": 3,
"link": {
"type": "generated-index",
"description": "Comprehensive guides for developing Wails applications"
}
}

View file

@ -0,0 +1,199 @@
---
title: Architecture Patterns
description: Design patterns for Wails applications
sidebar:
order: 7
---
## Overview
Proven patterns for organising your Wails application.
## Service Layer Pattern
### Structure
```
app/
├── main.go
├── services/
│ ├── user_service.go
│ ├── data_service.go
│ └── file_service.go
└── models/
└── user.go
```
### Implementation
```go
// Service interface
type UserService interface {
Create(email, password string) (*User, error)
GetByID(id int) (*User, error)
Update(user *User) error
Delete(id int) error
}
// Implementation
type userService struct {
app *application.App
db *sql.DB
}
func NewUserService(app *application.App, db *sql.DB) UserService {
return &userService{app: app, db: db}
}
```
## Repository Pattern
### Structure
```go
// Repository interface
type UserRepository interface {
Create(user *User) error
FindByID(id int) (*User, error)
Update(user *User) error
Delete(id int) error
}
// Service uses repository
type UserService struct {
repo UserRepository
}
func (s *UserService) Create(email, password string) (*User, error) {
user := &User{Email: email}
return user, s.repo.Create(user)
}
```
## Event-Driven Architecture
### Event Bus
```go
type EventBus struct {
app *application.App
listeners map[string][]func(interface{})
mu sync.RWMutex
}
func (eb *EventBus) Subscribe(event string, handler func(interface{})) {
eb.mu.Lock()
defer eb.mu.Unlock()
eb.listeners[event] = append(eb.listeners[event], handler)
}
func (eb *EventBus) Publish(event string, data interface{}) {
eb.mu.RLock()
handlers := eb.listeners[event]
eb.mu.RUnlock()
for _, handler := range handlers {
go handler(data)
}
}
```
### Usage
```go
// Subscribe
eventBus.Subscribe("user.created", func(data interface{}) {
user := data.(*User)
sendWelcomeEmail(user)
})
// Publish
eventBus.Publish("user.created", user)
```
## Dependency Injection
### Manual DI
```go
type App struct {
userService *UserService
fileService *FileService
db *sql.DB
}
func NewApp() *App {
db := openDatabase()
return &App{
db: db,
userService: NewUserService(db),
fileService: NewFileService(db),
}
}
```
### Using Wire
```go
// wire.go
//go:build wireinject
func InitializeApp() (*App, error) {
wire.Build(
openDatabase,
NewUserService,
NewFileService,
NewApp,
)
return nil, nil
}
```
## State Management
### Centralised State
```go
type AppState struct {
currentUser *User
settings *Settings
mu sync.RWMutex
}
func (s *AppState) SetUser(user *User) {
s.mu.Lock()
defer s.mu.Unlock()
s.currentUser = user
}
func (s *AppState) GetUser() *User {
s.mu.RLock()
defer s.mu.RUnlock()
return s.currentUser
}
```
## Best Practices
### ✅ Do
- Separate concerns
- Use interfaces
- Inject dependencies
- Handle errors properly
- Keep services focused
- Document architecture
### ❌ Don't
- Don't create god objects
- Don't tightly couple components
- Don't skip error handling
- Don't ignore concurrency
- Don't over-engineer
## Next Steps
- [Security](/guides/security) - Security best practices
- [Best Practices](/features/bindings/best-practices) - Bindings best practices

View file

@ -0,0 +1,171 @@
---
title: Auto-Updates
description: Implement automatic application updates
sidebar:
order: 4
---
## Overview
Keep your application up-to-date with automatic updates.
## Update Strategies
### 1. Check on Startup
```go
func (a *App) checkForUpdates() {
latest, err := getLatestVersion()
if err != nil {
return
}
if isNewer(latest, currentVersion) {
a.promptUpdate(latest)
}
}
```
### 2. Periodic Checks
```go
func (a *App) startUpdateChecker() {
ticker := time.NewTicker(24 * time.Hour)
go func() {
for range ticker.C {
a.checkForUpdates()
}
}()
}
```
### 3. Manual Check
```go
func (a *App) CheckForUpdates() {
result, _ := a.app.QuestionDialog().
SetMessage("Check for updates?").
SetButtons("Check", "Cancel").
Show()
if result == "Check" {
a.checkForUpdates()
}
}
```
## Implementation
### Version Checking
```go
type UpdateInfo struct {
Version string `json:"version"`
DownloadURL string `json:"download_url"`
ReleaseNotes string `json:"release_notes"`
}
func getLatestVersion() (*UpdateInfo, error) {
resp, err := http.Get("https://api.example.com/latest")
if err != nil {
return nil, err
}
defer resp.Body.Close()
var info UpdateInfo
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
return nil, err
}
return &info, nil
}
```
### Download and Install
```go
func (a *App) downloadUpdate(url string) error {
// Download update
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
// Save to temp file
tmpFile, err := os.CreateTemp("", "update-*.exe")
if err != nil {
return err
}
defer tmpFile.Close()
// Copy data
if _, err := io.Copy(tmpFile, resp.Body); err != nil {
return err
}
// Launch installer and quit
return a.installUpdate(tmpFile.Name())
}
```
## Third-Party Solutions
### Squirrel
```go
import "github.com/Squirrel/go-squirrel"
func setupUpdater() {
updater := squirrel.NewUpdater(squirrel.Options{
URL: "https://updates.example.com",
})
updater.CheckForUpdates()
}
```
### Self-Hosted
Host update manifests on your own server:
```json
{
"version": "1.0.1",
"platforms": {
"windows": {
"url": "https://example.com/myapp-1.0.1-windows.exe",
"sha256": "..."
},
"darwin": {
"url": "https://example.com/myapp-1.0.1-macos.dmg",
"sha256": "..."
}
},
"release_notes": "Bug fixes and improvements"
}
```
## Best Practices
### ✅ Do
- Verify update signatures
- Show release notes
- Allow users to skip versions
- Test update process thoroughly
- Provide rollback mechanism
- Use HTTPS for downloads
### ❌ Don't
- Don't force immediate updates
- Don't skip signature verification
- Don't interrupt user work
- Don't forget error handling
- Don't lose user data
## Next Steps
- [Creating Installers](/guides/installers) - Package your application
- [Testing](/guides/testing) - Test your application

View file

@ -0,0 +1,282 @@
---
title: Build Customization
description: Customize your build process using Task and Taskfile.yml
sidebar:
order: 1
---
import { FileTree } from "@astrojs/starlight/components";
## Overview
The Wails build system is a flexible and powerful tool designed to streamline
the build process for your Wails applications. It leverages
[Task](https://taskfile.dev), a task runner that allows you to define and run
tasks easily. While the v3 build system is the default, Wails encourages a
"bring your own tooling" approach, allowing developers to customize their build
process as needed.
Learn more about how to use Task in the
[official documentation](https://taskfile.dev/usage/).
## Task: The Heart of the Build System
[Task](https://taskfile.dev) is a modern alternative to Make, written in Go. It
uses a YAML file to define tasks and their dependencies. In the Wails build
system, [Task](https://taskfile.dev) plays a central role in orchestrating the
build process.
The main `Taskfile.yml` is located in the project root, while platform-specific
tasks are defined in `build/<platform>/Taskfile.yml` files. A common `Taskfile.yml`
file in the `build` directory contains common tasks that are shared across
platforms.
<FileTree>
- Project Root
- Taskfile.yml
- build
- windows/Taskfile.yml
- darwin/Taskfile.yml
- linux/Taskfile.yml
- Taskfile.yml
</FileTree>
## Taskfile.yml
The `Taskfile.yml` file in the project root is the main entry point for the build system. It defines
the tasks and their dependencies. Here's the default `Taskfile.yml` file:
```yaml
version: '3'
includes:
common: ./build/Taskfile.yml
windows: ./build/windows/Taskfile.yml
darwin: ./build/darwin/Taskfile.yml
linux: ./build/linux/Taskfile.yml
vars:
APP_NAME: "myproject"
BIN_DIR: "bin"
VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}'
tasks:
build:
summary: Builds the application
cmds:
- task: "{{OS}}:build"
package:
summary: Packages a production build of the application
cmds:
- task: "{{OS}}:package"
run:
summary: Runs the application
cmds:
- task: "{{OS}}:run"
dev:
summary: Runs the application in development mode
cmds:
- wails3 dev -config ./build/config.yml -port {{.VITE_PORT}}
```
## Platform-Specific Taskfiles
Each platform has its own Taskfile, located in the platform directories beneath the `build` directory. These
files define the core tasks for that platform. Each taskfile includes common tasks from the `build/Taskfile.yml` file.
### Windows
Location: `build/windows/Taskfile.yml`
The Windows-specific Taskfile includes tasks for building, packaging, and
running the application on Windows. Key features include:
- Building with optional production flags
- Generating `.ico` icon file
- Generating Windows `.syso` file
- Creating an NSIS installer for packaging
### Linux
Location: `build/linux/Taskfile.yml`
The Linux-specific Taskfile includes tasks for building, packaging, and running
the application on Linux. Key features include:
- Building with optional production flags
- Creating an AppImage, deb, rpm, and Arch Linux packages
- Generating `.desktop` file for Linux applications
### macOS
Location: `build/darwin/Taskfile.yml`
The macOS-specific Taskfile includes tasks for building, packaging, and running
the application on macOS. Key features include:
- Building binaries for amd64, arm64 and universal (both) architectures
- Generating `.icns` icon file
- Creating an `.app` bundle for distributing
- Ad-hoc signing `.app` bundles
- Setting macOS-specific build flags and environment variables
## Task Execution and Command Aliases
The `wails3 task` command is an embedded version of [Taskfile](https://taskfile.dev), which executes
the tasks defined in your `Taskfile.yml`.
The `wails3 build` and `wails3 package` commands are aliases for
`wails3 task build` and `wails3 task package` respectively. When you run these
commands, Wails internally translates them to the appropriate task execution:
- `wails3 build` → `wails3 task build`
- `wails3 package` → `wails3 task package`
### Passing Parameters to Tasks
You can pass CLI variables to tasks using the `KEY=VALUE` format. These variables are forwarded through the alias commands:
```bash
# These are equivalent:
wails3 build PLATFORM=linux CONFIG=production
wails3 task build PLATFORM=linux CONFIG=production
# Package with custom version:
wails3 package VERSION=2.0.0 OUTPUT=myapp.pkg
```
In your `Taskfile.yml`, you can access these variables using Go template syntax:
```yaml
tasks:
build:
cmds:
- echo "Building for {{.PLATFORM | default "darwin"}}"
- go build -tags {{.CONFIG | default "debug"}} -o myapp
```
## Common Build Process
Across all platforms, the build process typically includes the following steps:
1. Tidying Go modules
2. Building the frontend
3. Generating icons
4. Compiling the Go code with platform-specific flags
5. Packaging the application (platform-specific)
## Customising the Build Process
While the v3 build system provides a solid default configuration, you can easily
customise it to fit your project's needs. By modifying the `Taskfile.yml` and
platform-specific Taskfiles, you can:
- Add new tasks
- Modify existing tasks
- Change the order of task execution
- Integrate with other tools and scripts
This flexibility allows you to tailor the build process to your specific
requirements while still benefiting from the structure provided by the Wails
build system.
:::tip[Learning Taskfile]
We highly recommend reading the [Taskfile](https://taskfile.dev) documentation to
understand how to use Taskfile effectively.
You can find out which version of Taskfile is embedded in the Wails CLI by running `wails3 task --version`.
:::
## Development Mode
The Wails build system includes a powerful development mode that enhances the
developer experience by providing live reloading and hot module replacement.
This mode is activated using the `wails3 dev` command.
### How It Works
When you run `wails3 dev`, the following process occurs:
1. The command checks for an available port, defaulting to 9245 if not
specified.
2. It sets up the environment variables for the frontend dev server (Vite).
3. It starts the file watcher using the [refresh](https://github.com/atterpac/refresh) library.
The [refresh](https://github.com/atterpac/refresh) library is responsible for
monitoring file changes and triggering rebuilds. It uses the configuration defined under the `dev_mode` key in the `./build/config.yaml` file.
It may be configured to ignore certain directories and files, to determine which files to watch and what actions to take when changes are detected.
The default configuration works pretty well, but feel free to customise it to your needs.
### Configuration
Here's an example of its structure:
```yaml
dev_mode:
root_path: .
log_level: warn
debounce: 1000
ignore:
dir:
- .git
- node_modules
- frontend
- bin
file:
- .DS_Store
- .gitignore
- .gitkeep
watched_extension:
- "*.go"
git_ignore: true
executes:
- cmd: wails3 task common:install:frontend:deps
type: once
- cmd: wails3 task common:dev:frontend
type: background
- cmd: go mod tidy
type: blocking
- cmd: wails3 task build
type: blocking
- cmd: wails3 task run
type: primary
```
This configuration file allows you to:
- Set the root path for file watching
- Configure logging level
- Set a debounce time for file change events
- Ignore specific directories, files, or file extensions
- Define commands to execute on file changes
### Customising Development Mode
You can customise the development mode experience by modifying these values in the `config.yaml` file.
Some ways to customise include:
1. Changing the watched directories or files
2. Adjusting the debounce time to control how quickly the system responds to
changes
3. Adding or modifying the execute commands to fit your project's needs
### Using a browser for development
Whilst Wails v2 fully supported the use of a browser for development, it caused a lot
of confusion. Applications that would work in the browser would not necessarily
work in the desktop application, as not all browser APIs are available in webviews.
For UI-focused development work, you still have the flexibility to use a browser
in v3, by accessing the Vite URL at `http://localhost:9245` in dev mode. This
gives you access to powerful browser dev tools while working on styling and
layout. Be aware that Go bindings *will not work* in this mode.
When you're ready to test functionality like bindings and events, simply
switch to the desktop view to ensure everything works perfectly in the
production environment.

View file

@ -0,0 +1,409 @@
---
title: Windows Packaging
description: Package your Wails application for Windows distribution
sidebar:
order: 4
---
import { Tabs, TabItem } from '@astrojs/starlight/components';
Learn how to package your Wails application for Windows distribution using various installer formats.
## Overview
Wails provides several packaging options for Windows:
- **NSIS** - Nullsoft Scriptable Install System (recommended)
- **MSI** - Windows Installer Package
- **Portable** - Standalone executable (no installation)
- **MSIX** - Modern Windows app package
## NSIS Installer
NSIS creates professional installers with customization options and automatic system integration.
### Quick Start
Build an NSIS installer:
```bash
wails3 build
wails3 task nsis
```
This generates `build/bin/<AppName>-<version>-<arch>-installer.exe`.
### Features
**Automatic Integration:**
- ✅ Start Menu shortcuts
- ✅ Desktop shortcuts (optional)
- ✅ File associations
- ✅ **Custom URL protocol registration** (NEW)
- ✅ Uninstaller creation
- ✅ Registry entries
**Custom Protocols:**
Wails automatically registers custom URL protocols defined in your application:
```go
app := application.New(application.Options{
Name: "My Application",
Protocols: []application.Protocol{
{
Scheme: "myapp",
Description: "My Application Protocol",
},
},
})
```
The NSIS installer:
1. Registers all protocols during installation
2. Associates them with your application executable
3. Removes them during uninstallation
**No additional configuration needed!**
### Customization
Customize the installer via `wails.json`:
```json
{
"name": "MyApp",
"outputfilename": "myapp.exe",
"nsis": {
"companyName": "My Company",
"productName": "My Application",
"productDescription": "An amazing desktop application",
"productVersion": "1.0.0",
"installerIcon": "build/appicon.ico",
"license": "LICENSE.txt",
"allowInstallDirCustomization": true,
"installDirectory": "$PROGRAMFILES64\\MyApp",
"createDesktopShortcut": true,
"runAfterInstall": true,
"adminPrivileges": false
}
}
```
### NSIS Options
| Option | Type | Description | Default |
|--------|------|-------------|---------|
| `companyName` | string | Company name shown in installer | Project name |
| `productName` | string | Product name | App name |
| `productDescription` | string | Description shown in installer | App description |
| `productVersion` | string | Version number (e.g., "1.0.0") | "1.0.0" |
| `installerIcon` | string | Path to .ico file for installer | App icon |
| `license` | string | Path to license file (txt) | None |
| `allowInstallDirCustomization` | boolean | Let users choose install location | true |
| `installDirectory` | string | Default installation directory | `$PROGRAMFILES64\<ProductName>` |
| `createDesktopShortcut` | boolean | Create desktop shortcut | true |
| `runAfterInstall` | boolean | Run app after installation | false |
| `adminPrivileges` | boolean | Require admin rights to install | false |
### Advanced NSIS
#### Custom NSIS Script
For advanced customization, provide your own NSIS script:
```bash
# Create custom template
cp build/nsis/installer.nsi build/nsis/installer.custom.nsi
# Edit build/nsis/installer.custom.nsi
# Add custom sections, pages, or logic
# Build with custom script
wails3 task nsis --script build/nsis/installer.custom.nsi
```
#### Available Macros
Wails provides NSIS macros for common tasks:
**wails.associateCustomProtocols**
- Registers all custom URL protocols defined in `application.Options.Protocols`
- Called automatically during installation
- Creates registry entries under `HKEY_CURRENT_USER\SOFTWARE\Classes\`
**wails.unassociateCustomProtocols**
- Removes custom URL protocol registrations
- Called automatically during uninstallation
- Cleans up all protocol-related registry entries
**Example usage in custom NSIS script:**
```nsis
Section "Install"
# ... your installation code ...
# Register custom protocols
!insertmacro wails.associateCustomProtocols
# ... more installation code ...
SectionEnd
Section "Uninstall"
# Remove custom protocols
!insertmacro wails.unassociateCustomProtocols
# ... your uninstallation code ...
SectionEnd
```
## MSI Installer
Windows Installer Package format with Windows logo certification support.
### Build MSI
```bash
wails3 build
wails3 task msi
```
Generates `build/bin/<AppName>-<version>-<arch>.msi`.
### Customization
Configure via `wails.json`:
```json
{
"msi": {
"productCode": "{GUID}",
"upgradeCode": "{GUID}",
"manufacturer": "My Company",
"installScope": "perMachine",
"shortcuts": {
"desktop": true,
"startMenu": true
}
}
}
```
### MSI vs NSIS
| Feature | NSIS | MSI |
|---------|------|-----|
| Customization | ✅ High | ⚠️ Limited |
| File Size | ✅ Smaller | ⚠️ Larger |
| Corporate Deployment | ⚠️ Less common | ✅ Preferred |
| Custom UI | ✅ Full control | ⚠️ Restricted |
| Windows Logo | ❌ No | ✅ Yes |
| Protocol Registration | ✅ Automatic | ⚠️ Manual |
**Use NSIS when:**
- You want maximum customization
- You need custom branding and UI
- You want automatic protocol registration
- File size matters
**Use MSI when:**
- You need Windows logo certification
- You're deploying in enterprise environments
- You need Group Policy support
- You want Windows Update integration
## Portable Executable
Single executable with no installation required.
### Build Portable
```bash
wails3 build
```
Output: `build/bin/<appname>.exe`
### Characteristics
- No installation required
- No registry changes
- No administrator privileges needed
- Can run from USB drives
- No automatic updates
- No Start Menu integration
### Use Cases
- Trial versions
- USB stick applications
- Corporate environments with restricted installations
- Quick testing and demos
## MSIX Packages
Modern Windows app package format for Microsoft Store and sideloading.
See [MSIX Packaging Guide](/guides/build/msix) for detailed information.
## Code Signing
Sign your executables and installers to:
- Avoid Windows SmartScreen warnings
- Establish publisher identity
- Enable automatic updates
- Meet corporate security requirements
See [Code Signing Guide](/guides/build/signing) for details.
## Icon Requirements
### Application Icon
**Format:** `.ico` file with multiple resolutions
**Recommended sizes:**
- 16x16, 32x32, 48x48, 64x64, 128x128, 256x256
**Create from PNG:**
```bash
# Using ImageMagick
magick convert icon.png -define icon:auto-resize=256,128,64,48,32,16 appicon.ico
# Using online tools
# https://icoconvert.com/
```
### Installer Icon
Same `.ico` file can be used for both application and installer.
Configure in `wails.json`:
```json
{
"nsis": {
"installerIcon": "build/appicon.ico"
},
"windows": {
"applicationIcon": "build/appicon.ico"
}
}
```
## Building for Different Architectures
The target architecture is controlled by the `GOARCH` environment variable and the platform-specific Taskfile. The default Windows Taskfile builds for the current architecture.
### AMD64 (64-bit)
```bash
GOARCH=amd64 wails3 build
```
### ARM64
```bash
GOARCH=arm64 wails3 build
```
## Distribution Checklist
Before distributing your Windows application:
- [ ] Test installer on clean Windows installation
- [ ] Verify application icon displays correctly
- [ ] Test uninstaller completely removes application
- [ ] Verify Start Menu shortcuts work
- [ ] Test custom protocol handlers (if used)
- [ ] Check SmartScreen behavior (sign your app if possible)
- [ ] Test on Windows 10 and Windows 11
- [ ] Verify file associations work (if used)
- [ ] Test with antivirus software
- [ ] Include license and documentation
## Troubleshooting
### NSIS Build Fails
**Error:** `makensis: command not found`
**Solution:**
```bash
# Install NSIS
winget install NSIS.NSIS
# Or download from https://nsis.sourceforge.io/
```
### Custom Protocols Not Working
**Check registration:**
```powershell
# Check registry
Get-ItemProperty -Path "HKCU:\SOFTWARE\Classes\myapp"
# Test protocol
Start-Process "myapp://test"
```
**Fix:**
1. Reinstall with NSIS installer
2. Run installer as administrator if needed
3. Verify `Protocols` configuration in Go code
### SmartScreen Warning
**Cause:** Unsigned executable
**Solutions:**
1. **Code sign your application** (recommended)
2. Build reputation (downloads over time)
3. Submit to Microsoft for analysis
4. Use Extended Validation (EV) certificate for immediate trust
### Installer Won't Run
**Possible causes:**
- Antivirus blocking
- Missing dependencies
- Corrupted download
- User permissions
**Solutions:**
1. Temporarily disable antivirus for testing
2. Run as administrator
3. Re-download installer
4. Check Windows Event Viewer for errors
## Best Practices
### ✅ Do
- **Code sign your releases** - Avoids SmartScreen warnings
- **Test on clean Windows installations** - Don't rely on dev environment
- **Provide both installer and portable versions** - Give users choice
- **Include comprehensive uninstaller** - Remove all traces
- **Use semantic versioning** - Clear version numbering
- **Test protocol handlers thoroughly** - Validate all URL inputs
- **Provide clear installation instructions** - Help users succeed
### ❌ Don't
- **Don't skip code signing** - Users will see scary warnings
- **Don't require admin for normal apps** - Only if truly necessary
- **Don't install to non-standard locations** - Use `$PROGRAMFILES64`
- **Don't leave orphaned registry entries** - Clean up properly
- **Don't forget to test uninstaller** - Broken uninstallers frustrate users
- **Don't hardcode paths** - Use Windows environment variables
## Next Steps
- [Code Signing](/guides/build/signing) - Sign your executables and installers
- [MSIX Packaging](/guides/build/msix) - Modern Windows app packages
- [Custom Protocols](/guides/distribution/custom-protocols) - Deep linking and URL schemes
- [Auto-Updates](/guides/distribution/auto-updates) - Keep your app current
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [Windows packaging examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).

View file

@ -0,0 +1,179 @@
---
title: Building Applications
description: Build and package your Wails application
sidebar:
order: 1
---
import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components";
## Overview
Wails uses [Task](https://taskfile.dev) as its build system. The `wails3 build`, `wails3 package`, and `wails3 dev` commands are wrappers around tasks defined in your project's `Taskfile.yml`. You can customize the build process by editing the Taskfile.
## Development Build
### Quick Start
```bash
wails3 dev
```
**Features:**
- Hot reload for frontend changes
- Automatic Go rebuild on changes
- Debug mode enabled
- Fast iteration
### Dev Options
```bash
# Custom config file
wails3 dev -config ./build/config.yml
# Custom vite dev server port
wails3 dev -port 3000
# Enable HTTPS for the dev server
wails3 dev -s
```
The dev server port defaults to `9245`. You can also set the port via the `WAILS_VITE_PORT` environment variable.
:::note
`wails3 dev` is equivalent to running `wails3 task dev` and runs the `dev` task in the project's main Taskfile. You can customise this by editing the `Taskfile.yml` file.
:::
## Production Build
### Basic Build
```bash
wails3 build
```
**Output:** Binary in `bin/` directory.
### Build with Tags
```bash
# Pass additional Go build tags
wails3 build -tags production,debug
```
### Build with CLI Variables
You can pass CLI variables to customize the build via the underlying Taskfile:
```bash
wails3 build PRODUCTION=true
```
These variables can be accessed in your `Taskfile.yml` using Go template syntax.
:::note
`wails3 build` is equivalent to running `wails3 task build` which runs the `build` task in the project's main Taskfile. Any CLI variables are forwarded to the underlying task. You can customise the build process by editing the `Taskfile.yml` file.
:::
## Platform-Specific Builds
Platform-specific build tasks are defined in the Taskfiles under `build/<platform>/Taskfile.yml`. The main `Taskfile.yml` dispatches to the correct platform automatically based on the current OS.
<Tabs syncKey="platform">
<TabItem label="Windows" icon="seti:windows">
The Windows Taskfile handles:
- Building the binary with appropriate flags
- Generating `.ico` icon file
- Generating Windows `.syso` file
- Creating NSIS installers for packaging
```bash
# Build for Windows (runs automatically on Windows)
wails3 build
```
</TabItem>
<TabItem label="macOS" icon="apple">
The macOS Taskfile handles:
- Building binaries for amd64, arm64, and universal architectures
- Generating `.icns` icon file
- Creating `.app` bundles
- Ad-hoc code signing
```bash
# Build for macOS (runs automatically on macOS)
wails3 build
```
</TabItem>
<TabItem label="Linux" icon="linux">
The Linux Taskfile handles:
- Building the binary with appropriate flags
- Generating `.desktop` files
- Creating AppImage, deb, rpm, and Arch Linux packages
```bash
# Build for Linux (runs automatically on Linux)
wails3 build
```
</TabItem>
</Tabs>
## Packaging
```bash
wails3 package
```
This creates platform-specific packages for distribution. Like `build`, it is a wrapper around `wails3 task package`.
| Platform | Package Types |
|----------|-------------------------------------------|
| Windows | `.exe`, NSIS installer |
| macOS | `.app` bundle |
| Linux | `.AppImage`, `.deb`, `.rpm`, `.archlinux` |
## Optimization
### Binary Size
```bash
# UPX compression (external tool)
upx --best --lzma bin/myapp
```
The default Taskfiles already include `-ldflags "-s -w"` to strip debug symbols.
### Build Tags
```bash
# Pass build tags to the Go compiler
wails3 build -tags production
```
## Troubleshooting
### Build Fails
**Problem:** Build errors
**Solutions:**
- Check `go.mod` is up to date
- Run `go mod tidy`
- Verify frontend builds: `cd frontend && npm run build`
- Run `wails3 doctor` to check your environment
### Large Binary Size
**Problem:** Binary is too large
**Solutions:**
- Ensure Taskfile includes `-ldflags "-s -w"` to strip symbols (default Taskfiles do this)
- Remove unused dependencies
- Use UPX compression
- Check embedded assets size
## Next Steps
- [Build Customization](/guides/build/customization) - Customize the build process with Taskfiles
- [Build System](/concepts/build-system) - Understand how the build system works

View file

@ -0,0 +1,696 @@
---
title: CLI Reference
description: Complete reference for the Wails CLI commands
sidebar:
order: 1
---
The Wails CLI provides a comprehensive set of commands to help you develop, build, and maintain your Wails applications.
## Core Commands
Core commands are the primary commands used for project creation, development, and building.
All CLI commands are of the following format: `wails3 <command>`.
### `init`
Initializes a new Wails project. During this initialization, the `go mod tidy` command is run to bring the project packages up to date. This can be bypassed by using the `-skipgomodtidy` flag with the `init` command.
```bash
wails3 init [flags]
```
#### Flags
| Flag | Description | Default |
|-----------------------|----------------------|--------------------------|
| `-p` | Package name | `main` |
| `-t` | Template name or URL | `vanilla` |
| `-n` | Project name | |
| `-d` | Project directory | `.` |
| `-q` | Suppress output | `false` |
| `-l` | List templates | `false` |
| `-git` | Git repository URL | |
| `-productname` | Product name | `My Product` |
| `-productdescription` | Product description | `My Product Description` |
| `-productversion` | Product version | `0.1.0` |
| `-productcompany` | Company name | `My Company` |
| `-productcopyright` | Copyright notice | ` now, My Company` |
| `-productcomments` | File comments | `This is a comment` |
| `-productidentifier` | Product identifier | |
| `-s` | Skip warning for remote templates | `false` |
| `-skipgomodtidy` | Skip go mod tidy | `false` |
| `-mod` | Go module path | |
The `-mod` flag sets the Go module path for the project. If omitted and `-git` is provided, the module path will be computed from the Git URL.
The `-git` flag accepts various Git URL formats:
- HTTPS: `https://github.com/username/project`
- SSH: `git@github.com:username/project` or `ssh://git@github.com/username/project`
- Git protocol: `git://github.com/username/project`
- Filesystem: `file:///path/to/project.git`
When provided, this flag will:
1. Initialize a git repository in the project directory
2. Set the specified URL as the remote origin
3. Update the module name in `go.mod` to match the repository URL
4. Add all files
### `dev`
Runs the application in development mode. This will give you a live view of your frontend code, and you can make changes and see them reflected
in the running application without having to rebuild the entire application. Changes to your Go code will also be detected and the application
will automatically rebuild and relaunch.
```bash
wails3 dev [flags]
```
#### Flags
| Flag | Description | Default |
|-----------|----------------------|----------------------|
| `-config` | Config file path | `./build/config.yml` |
| `-port` | Vite dev server port | `9245` |
| `-s` | Enable HTTPS | `false` |
:::note
This is equivalent to running `wails3 task dev` and runs the `dev` task in the project's main Taskfile. You can customise this by editing the `Taskfile.yml` file.
:::
### `build`
Builds a debug version of your application. It defaults to building for the current platform and architecture.
```bash
wails3 build [flags] [CLI variables...]
```
#### Flags
| Flag | Description | Default |
|-----------|----------------------------------------------------------|---------|
| `-tags` | Additional build tags to pass to the Go compiler (comma-separated) | |
You can pass CLI variables to customize the build:
```bash
wails3 build PLATFORM=linux CONFIG=production
```
You can also pass additional Go build tags:
```bash
wails3 build -tags production,debug
```
:::note
This is equivalent to running `wails3 task build` which runs the `build` task in the project's main Taskfile. Any CLI variables passed to `build` are forwarded to the underlying task. You can customise the build process by editing the `Taskfile.yml` file.
:::
### `package`
Creates platform-specific packages for distribution.
```bash
wails3 package [CLI variables...]
```
You can pass CLI variables to customize the packaging:
```bash
wails3 package VERSION=2.0.0 OUTPUT=myapp.pkg
```
#### Package Types
The following package types are available for each platform:
| Platform | Package Type |
|----------|-------------------------------------------|
| Windows | `.exe` |
| macOS | `.app`, |
| Linux | `.AppImage`, `.deb`, `.rpm`, `.archlinux` |
:::note
This is equivalent to `wails3 task package` which runs the `package` task in the project's main Taskfile. Any CLI variables passed to `package` are forwarded to the underlying task. You can customise the packaging process by editing the `Taskfile.yml` file.
:::
### `task`
Runs tasks defined in your project's Taskfile.yml. This is an embedded version of [Taskfile](https://taskfile.dev) that allows you to define and run custom build, test, and deployment tasks.
```bash
wails3 task [taskname] [CLI variables...] [flags]
```
#### CLI Variables
You can pass variables to tasks in the format `KEY=VALUE`:
```bash
wails3 task build PLATFORM=linux CONFIG=production
wails3 task deploy ENV=staging VERSION=1.2.3
```
These variables can be accessed in your Taskfile.yml using Go template syntax:
```yaml
tasks:
build:
cmds:
- echo "Building for {{.PLATFORM | default "darwin"}}"
- echo "Config: {{.CONFIG | default "debug"}}"
```
#### Flags
| Flag | Description | Default |
|--------------|-------------------------------------------|----------|
| `-h` | Shows Task usage | `false` |
| `-i` | Creates a new Taskfile.yml | `false` |
| `-list` | Lists tasks with descriptions | `false` |
| `-list-all` | Lists all tasks (with or without descriptions) | `false` |
| `-json` | Formats task list as JSON | `false` |
| `-status` | Exits with non-zero if task is not up-to-date | `false` |
| `-f` | Forces execution even when task is up-to-date | `false` |
| `-w` | Enables watch mode for the given task | `false` |
| `-v` | Enables verbose mode | `false` |
| `-version` | Prints Task version | `false` |
| `-s` | Disables echoing | `false` |
| `-p` | Executes tasks in parallel | `false` |
| `-dry` | Compiles and prints tasks without executing | `false` |
| `-summary` | Shows summary about a task | `false` |
| `-x` | Pass-through the exit code of the task | `false` |
| `-dir` | Sets directory of execution | |
| `-taskfile` | Choose which Taskfile to run | |
| `-output` | Sets output style: [interleaved|group|prefixed] | |
| `-c` | Colored output (enabled by default) | `true` |
| `-C` | Limit number of tasks to run concurrently | |
| `-interval` | Interval to watch for changes (in seconds) | |
#### Examples
```bash
# Run the default task
wails3 task
# Run a specific task
wails3 task test
# Run a task with variables
wails3 task build PLATFORM=windows ARCH=amd64
# List all available tasks
wails3 task --list
# Run multiple tasks in parallel
wails3 task -p task1 task2 task3
# Watch for changes and re-run task
wails3 task -w dev
```
### `doctor`
Performs a system check and displays a status report.
```bash
wails3 doctor
```
## Generate Commands
Generate commands help create various project assets like bindings, icons, and build files. All generate commands use the base command: `wails3 generate <command>`.
### `generate bindings`
Generates bindings and models for your Go code.
```bash
wails3 generate bindings [flags] [patterns...]
```
#### Flags
| Flag | Description | Default |
|-------------|---------------------------|---------------------|
| `-f` | Additional Go build flags | |
| `-d` | Output directory | `frontend/bindings` |
| `-models` | Models filename | `models` |
| `-index` | Index filename | `index` |
| `-ts` | Generate TypeScript | `false` |
| `-i` | Use TS interfaces | `false` |
| `-b` | Use bundled runtime | `false` |
| `-names` | Use names instead of IDs | `false` |
| `-noevents` | Skip custom event types | `false` |
| `-noindex` | Skip index files | `false` |
| `-dry` | Dry run | `false` |
| `-silent` | Silent mode | `false` |
| `-v` | Debug output | `false` |
| `-clean` | Clean output directory | `true` |
### `generate build-assets`
Generates build assets for your application.
```bash
wails3 generate build-assets [flags]
```
#### Flags
| Flag | Description | Default |
|----------------|---------------------|--------------------|
| `-name` | Project name | |
| `-dir` | Output directory | `build` |
| `-silent` | Suppress output | `false` |
| `-company` | Company name | |
| `-productname` | Product name | |
| `-description` | Product description | |
| `-version` | Product version | |
| `-identifier` | Product identifier | `com.wails.[name]` |
| `-copyright` | Copyright notice | |
| `-comments` | File comments | |
### `generate icons`
Generates application icons.
```bash
wails3 generate icons [flags]
```
#### Flags
| Flag | Description | Default |
|--------------------|------------------------------|-----------------------|
| `-input` | Input PNG file | Required |
| `-windowsfilename` | Windows output filename | |
| `-macfilename` | macOS output filename | |
| `-sizes` | Icon sizes (comma-separated) | `256,128,64,48,32,16` |
| `-example` | Generate example icon | `false` |
### `generate syso`
Generates Windows .syso file.
```bash
wails3 generate syso [flags]
```
#### Flags
| Flag | Description | Default |
|-------------|---------------------------|----------------------------|
| `-manifest` | Path to manifest file | Required |
| `-icon` | Path to icon file | Required |
| `-info` | Path to version info file | |
| `-arch` | Target architecture | Current GOARCH |
| `-out` | Output filename | `rsrc_windows_[arch].syso` |
### `generate .desktop`
Generates a Linux .desktop file.
```bash
wails3 generate .desktop [flags]
```
#### Flags
| Flag | Description | Default |
|------------------|---------------------------|------------------|
| `-name` | Application name | Required |
| `-exec` | Executable path | Required |
| `-icon` | Icon path | |
| `-categories` | Application categories | `Utility` |
| `-comment` | Application comment | |
| `-terminal` | Run in terminal | `false` |
| `-keywords` | Search keywords | |
| `-version` | Application version | |
| `-genericname` | Generic name | |
| `-startupnotify` | Show startup notification | `false` |
| `-mimetype` | Supported MIME types | |
| `-output` | Output filename | `[name].desktop` |
### `generate runtime`
Generates the pre-built version of the runtime.
```bash
wails3 generate runtime
```
### `generate constants`
Generates JavaScript constants from Go code.
```bash
wails3 generate constants [flags]
```
#### Flags
| Flag | Description | Default |
|------|--------------------------------|----------------|
| `-f` | The Go constants filename | `constants.go` |
| `-o` | The output JavaScript file | `constants.js` |
### `generate appimage`
Generates a Linux AppImage.
```bash
wails3 generate appimage [flags]
```
#### Flags
| Flag | Description | Default |
|-------------|-----------------------|----------------|
| `-binary` | Path to binary | Required |
| `-icon` | Path to icon file | Required |
| `-desktop` | Path to .desktop file | Required |
| `-builddir` | Build directory | Temp directory |
| `-output` | Output directory | `.` |
Base command: `wails3 service`
## Service Commands
Service commands help manage Wails services. All service commands use the base command: `wails3 service <command>`.
### `service init`
Initializes a new service.
```bash
wails3 service init [flags]
```
#### Flags
| Flag | Description | Default |
|------|---------------------|-------------------|
| `-n` | Service name | `example_service` |
| `-d` | Service description | `Example service` |
| `-p` | Package name | |
| `-o` | Output directory | `.` |
| `-q` | Suppress output | `false` |
| `-a` | Author name | |
| `-v` | Version | |
| `-w` | Website URL | |
| `-r` | Repository URL | |
| `-l` | License | |
Base command: `wails3 tool`
## Tool Commands
Tool commands provide utilities for development and debugging. All tool commands use the base command: `wails3 tool <command>`.
### `tool checkport`
Checks if a port is open. Useful for testing if vite is running.
```bash
wails3 tool checkport [flags]
```
#### Flags
| Flag | Description | Default |
|---------|---------------|-------------|
| `-port` | Port to check | `9245` |
| `-host` | Host to check | `localhost` |
### `tool watcher`
Watches files and runs a command when they change.
```bash
wails3 tool watcher [flags]
```
#### Flags
| Flag | Description | Default |
|------------|---------------------|----------------------|
| `-config` | Config file path | `./build/config.yml` |
| `-ignore` | Patterns to ignore | |
| `-include` | Patterns to include | |
### `tool cp`
Copies files.
```bash
wails3 tool cp
```
### `tool buildinfo`
Shows build information about the application.
```bash
wails3 tool buildinfo
```
### `tool version`
Bumps a semantic version based on the provided flags.
```bash
wails3 tool version [flags]
```
#### Flags
| Flag | Description | Default |
|---------------|--------------------------------------------------|---------|
| `-v` | Current version to bump | |
| `-major` | Bump major version | `false` |
| `-minor` | Bump minor version | `false` |
| `-patch` | Bump patch version | `false` |
| `-prerelease` | Bump prerelease version (e.g., alpha.5 to alpha.6) | `false` |
The command follows the precedence order: major > minor > patch > prerelease. It preserves the "v" prefix if present in the input version, as well as any prerelease and metadata components.
Example usage:
```bash
wails3 tool version -v 1.2.3 -major # Output: 2.0.0
wails3 tool version -v v1.2.3 -minor # Output: v1.3.0
wails3 tool version -v 1.2.3-alpha -patch # Output: 1.2.4-alpha
wails3 tool version -v v3.0.0-alpha.5 -prerelease # Output: v3.0.0-alpha.6
```
### `tool package`
Generates Linux packages (deb, rpm, archlinux).
```bash
wails3 tool package [flags]
```
#### Flags
| Flag | Description | Default |
|----------------|------------------------------------------------|---------|
| `-format` | Package format (deb, rpm, archlinux, dmg) | `deb` |
| `-name` | Executable name | `myapp` |
| `-config` | Config file path | |
| `-out` | Output directory | `.` |
| `-background` | Path to optional background image for DMG | |
| `-create-dmg` | Create a DMG file (macOS only) | `false` |
### `tool lipo`
Creates a macOS universal binary from multiple architecture-specific binaries.
```bash
wails3 tool lipo [flags]
```
#### Flags
| Flag | Description | Default |
|------------|-------------------------------------------------|---------|
| `-output` (`-o`) | Output path for the universal binary | |
| `-input` (`-i`) | Input binaries to combine (specify multiple times) | |
### `tool capabilities`
Checks system build capabilities (GTK4/GTK3 availability on Linux).
```bash
wails3 tool capabilities
```
### `tool sign`
Signs a binary or package directly.
```bash
wails3 tool sign [flags]
```
#### Flags
| Flag | Description | Default |
|----------------------|------------------------------------------------------|---------|
| `-input` | Path to the file to sign | |
| `-output` | Output path (defaults to in-place signing) | |
| `-verbose` | Enable verbose output | `false` |
| `-certificate` | Path to PKCS#12 (.pfx/.p12) certificate file | |
| `-password` | Certificate password | |
| `-thumbprint` | Certificate thumbprint in Windows certificate store | |
| `-timestamp` | Timestamp server URL | |
| `-identity` | macOS signing identity | |
| `-entitlements` | Path to entitlements plist file | |
| `-hardened-runtime` | Enable hardened runtime | `false` |
| `-notarize` | Submit for Apple notarization after signing | `false` |
| `-keychain-profile` | Keychain profile for notarization credentials | |
| `-pgp-key` | Path to PGP private key file | |
| `-pgp-password` | PGP key password | |
| `-role` | DEB signing role (origin, maint, archive, builder) | |
Base command: `wails3 update`
## Update Commands
Update commands help manage and update project assets. All update commands use the base command: `wails3 update <command>`.
### `update cli`
Updates the Wails CLI to a new version.
```bash
wails3 update cli [flags]
```
#### Flags
| Flag | Description | Default |
|-------------|---------------------------------|---------|
| `-pre` | Update to latest pre-release | `false` |
| `-version` | Update to specific version | |
| `-nocolour` | Disable colored output | `false` |
The update cli command allows you to update your Wails CLI installation. By default, it updates to the latest stable release.
You can use the `-pre` flag to update to the latest pre-release version, or specify a particular version using the `-version` flag.
After updating, remember to update your project's go.mod file to use the same version:
```bash
require github.com/wailsapp/wails/v3 v3.x.x
```
### `update build-assets`
Updates the build assets using the given config file.
```bash
wails3 update build-assets [flags]
```
#### Flags
| Flag | Description | Default |
|----------------|---------------------|---------|
| `-config` | Config file path | |
| `-dir` | Output directory | `build` |
| `-silent` | Suppress output | `false` |
| `-company` | Company name | |
| `-productname` | Product name | |
| `-description` | Product description | |
| `-version` | Product version | |
| `-identifier` | Product identifier | |
| `-copyright` | Copyright notice | |
| `-comments` | File comments | |
Base command: `wails3`
## Utility Commands
Utility commands provide helpful shortcuts for common tasks. Use these commands directly with the base command: `wails3 <command>`.
### `docs`
Opens the Wails documentation in your default browser.
```bash
wails3 docs
```
### `releasenotes`
Shows the release notes for the current or specified version.
```bash
wails3 releasenotes [flags]
```
#### Flags
| Flag | Description | Default |
|------|-----------------------------------|---------|
| `-v` | Version to show release notes for | |
| `-n` | Disable colour output | `false` |
### `version`
Prints the current version of Wails.
```bash
wails3 version
```
### `sponsor`
Opens the Wails sponsorship page in your default browser.
```bash
wails3 sponsor
```
### `doctor-ng`
System status report using the new TUI interface.
```bash
wails3 doctor-ng
```
## Setup Commands
Setup commands provide project configuration wizards. All setup commands use the base command: `wails3 setup <command>`.
### `setup signing`
Configures code signing for your project.
```bash
wails3 setup signing [flags]
```
#### Flags
| Flag | Description | Default |
|--------------|--------------------------------------------------------------|---------|
| `-platform` | Platform(s) to configure (darwin, windows, linux). Auto-detects if not specified. | |
### `setup entitlements`
Configures macOS entitlements for your project.
```bash
wails3 setup entitlements [flags]
```
#### Flags
| Flag | Description | Default |
|------------|--------------------------------------|-------------------------------------|
| `-output` | Output path for entitlements.plist | `build/darwin/entitlements.plist` |
## Sign Command
### `sign`
Signs binaries and packages for the current or specified platform. This is a wrapper that calls platform-specific signing tasks.
```bash
wails3 sign [flags] [args...]
```
## Generate Commands (continued)
### `generate template`
Generates a new Wails template from an existing project.
```bash
wails3 generate template [flags]
```
#### Flags
| Flag | Description | Default |
|-----------------|--------------------------|----------|
| `-name` | Template name | |
| `-shortname` | Short name | |
| `-author` | Template author | |
| `-description` | Template description | |
| `-helpurl` | Help URL | |
| `-version` | Template version | `v0.0.1` |
| `-dir` | Output directory | `.` |
| `-frontend` | Frontend directory to migrate | |
### `generate webview2bootstrapper`
Generates the WebView2 bootstrapper executable for Windows.
```bash
wails3 generate webview2bootstrapper [flags]
```
#### Flags
| Flag | Description | Default |
|--------|---------------------------------|---------|
| `-dir` | Directory to write the file to | |
## iOS Commands
iOS commands provide tooling for iOS development. All iOS commands use the base command: `wails3 ios <command>`.
### `ios overlay:gen`
Generates a Go overlay for the iOS bridge shim.
```bash
wails3 ios overlay:gen
```
### `ios xcode:gen`
Generates an Xcode project in the output directory.
```bash
wails3 ios xcode:gen
```

View file

@ -0,0 +1,113 @@
---
title: Cross-Platform Building
description: Build for multiple platforms from a single machine
sidebar:
order: 2
---
## Overview
Wails supports building for Windows, macOS, and Linux. The build process is driven by platform-specific Taskfiles that handle the details of compilation, icon generation, and packaging for each platform.
## Supported Platforms
Wails supports the following target platforms:
- `windows/amd64`, `windows/arm64`
- `darwin/amd64`, `darwin/arm64` (universal binaries via `wails3 tool lipo`)
- `linux/amd64`, `linux/arm64`
## How It Works
When you run `wails3 build`, the main `Taskfile.yml` dispatches to the platform-specific Taskfile based on the current OS using `{{OS}}`:
```yaml
tasks:
build:
cmds:
- task: "{{OS}}:build"
```
Each platform Taskfile (in `build/<platform>/Taskfile.yml`) handles the compilation with the appropriate flags and environment variables.
## Cross-Compilation
To cross-compile, set the `GOOS` and `GOARCH` environment variables. Note that CGo cross-compilation may require additional tooling (e.g., a cross-compiler like `mingw-w64` for Windows builds from Linux/macOS).
### Building for Windows from macOS/Linux
```bash
# Install cross-compiler (one-time)
# macOS: brew install mingw-w64
# Linux: apt-get install mingw-w64
# Cross-compile
GOOS=windows GOARCH=amd64 go build -o myapp.exe
```
### Building for Linux from macOS/Windows
```bash
GOOS=linux GOARCH=amd64 go build -o myapp
```
### macOS Builds
macOS builds are best done on macOS due to code signing and notarization requirements. The macOS Taskfile supports building for different architectures:
```bash
# Build for the current architecture
wails3 build
# Create a universal binary using tool lipo
wails3 tool lipo -i bin/myapp-amd64 -i bin/myapp-arm64 -output bin/myapp-universal
```
## CI/CD Integration
### GitHub Actions
The recommended approach for cross-platform builds is to use CI/CD with platform-specific runners:
```yaml
name: Build
on: [push]
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.24'
- name: Install Wails
run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest
- name: Build
run: wails3 build
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: app-${{ matrix.os }}
path: bin/
```
## Best Practices
- **Build on target platform** - Use CI/CD runners matching target OS for reliable builds
- **Test on target platforms** - Cross-compiled binaries should be tested on their target OS
- **Use the Taskfile system** - Customize builds per platform in `build/<platform>/Taskfile.yml`
- **Version your builds** - Use `-ldflags` in your Taskfile to embed version info
## Next Steps
- [Build Customization](/guides/build/customization) - Customize the build process
- [Building](/guides/building) - Basic building guide

Some files were not shown because too many files have changed in this diff Show more