Skip to content

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.

Terminal window
wails3 build

Output: Native executable with everything embedded.

[Build Process Diagram Placeholder]

Wails scans your Go code to understand your services:

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

Wails generates type-safe bindings:

// Auto-generated: frontend/bindings/<full-go-import-path>/greetservice.js
// (TypeScript is also generated when you pass `-ts`. The shape below is the real
// runtime call format — numeric IDs via $Call.ByID, imported from /wails/runtime.js.)
import { Call as $Call } from "/wails/runtime.js";
export function Greet($0) {
return $Call.ByID(1234567890, $0);
}

Benefits:

  • Full type safety
  • IDE autocomplete
  • Compile-time errors
  • JSDoc comments

Your frontend bundler runs (Vite, webpack, etc.):

Terminal window
# 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/

Go code is compiled with optimisations:

Terminal window
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

Frontend assets are embedded into the Go binary:

//go:embed frontend/dist
var assets embed.FS

Result: Single executable with everything inside.

Single native binary:

  • Windows: myapp.exe (~15MB)
  • macOS: myapp.app (~15MB)
  • Linux: myapp (~15MB)

No dependencies (except system WebView).

Optimised for speed:

Terminal window
wails3 dev

What happens:

  1. Starts frontend dev server (Vite on port 9245 by default)
  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)

Terminal window
wails3 build

Output: bin/<APP_NAME> (or bin/<APP_NAME>.exe on Windows). The bin/ directory sits at the project root.

wails3 build is a thin wrapper around wails3 task build. The only build-time flag it forwards is --tags, which becomes the EXTRA_TAGS Taskfile variable:

Terminal window
# Build with extra Go build tags
wails3 build --tags "myfeature,gtk4"

There are no -platform, -o, -skipbindings, -clean, -debug, -devbuild, -icon, -ldflags, or -package flags on wails3 build. Cross-compilation, output paths, icons, and packaging are controlled through the project’s Taskfile (Taskfile.yml + build/config.yml).

Cross-platform and platform-specific builds

Section titled “Cross-platform and platform-specific builds”

Platform builds are exposed as Taskfile tasks under the darwin: / windows: / linux: namespaces (defined in build/Taskfile.<platform>.yml). For example:

Terminal window
# macOS — universal binary
wails3 task darwin:build:universal
# macOS — current arch
wails3 task darwin:build
# Windows
wails3 task windows:build
# Linux
wails3 task linux:build

To see every available task in the current project:

Terminal window
wails3 task --list

Generate platform icons (build/icons.icns, build/icon.ico, etc.) from a source PNG:

Terminal window
wails3 generate icons -input appicon.png

Build platform-specific installers/packages:

Terminal window
wails3 package # uses the current Go build env
wails3 task linux:create:deb
wails3 task windows:package
wails3 task darwin:package:universal

Wails 3 projects use Taskfile as their build orchestrator. The root Taskfile.yml includes per-platform task files from build/:

# Taskfile.yml (excerpt — the real templates are richer)
version: '3'
includes:
common: ./build/Taskfile.yml
darwin: ./build/Taskfile.darwin.yml
windows: ./build/Taskfile.windows.yml
linux: ./build/Taskfile.linux.yml
tasks:
build:
desc: Build the application
cmds:
- task: "{{OS}}:build"

Run tasks with wails3 task <name> or task <name>:

Terminal window
wails3 task windows:build
wails3 task darwin:package:universal
wails3 task linux:create:appimage

Project metadata (name, identifier, version, info-plist values, NSIS settings, .desktop fields, custom protocols, etc.) lives in build/config.yml. The Taskfile reads this file when generating icons, manifests, installers, and the like. There is no build/build.json file in Wails 3.

# build/config.yml (illustrative)
info:
productName: "My App"
productIdentifier: "com.example.myapp"
productVersion: "1.0.0"
companyName: "Example Ltd."
productDescription: "An application built with Wails"

Run wails3 generate build-assets (or wails3 update build-assets) to refresh the platform-specific build assets from this config.

Wails uses Go’s embed package:

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

Embed additional files:

//go:embed frontend/dist
var frontendAssets embed.FS
//go:embed data/*.json
var dataAssets embed.FS
//go:embed templates/*.html
var templateAssets embed.FS

Vite (default):

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

Compiler flags:

Terminal window
-ldflags="-s -w"
  • -s: Strip symbol table (~10% reduction)
  • -w: Strip DWARF debug info (~20% reduction)

Additional optimisations:

Terminal window
-ldflags="-s -w -X main.version=1.0.0"
  • -X: Set variable values at build time
  • Useful for version numbers, build dates

UPX (optional):

Terminal window
# After building
upx --best bin/myapp.exe

Results:

  • ~50% size reduction
  • Slightly slower startup (~100ms)
  • Not recommended for macOS (code signing issues)

Output: myapp.exe

Includes:

  • Application icon
  • Version information
  • Manifest (UAC settings)

Icon:

Terminal window
# Generate platform icons from a source PNG
wails3 generate icons -input appicon.png -windowsfilename build/icon.ico

The Windows tool package step then embeds the generated .ico into the executable.

Manifest:

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>

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 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 ships a darwin:build:universal (and darwin:package:universal) task that builds both architectures and combines them via wails3 tool lipo:

Terminal window
wails3 task darwin:build:universal

Output: myapp (ELF binary)

Dependencies:

  • GTK3
  • WebKitGTK

Desktop file:

# myapp.desktop
[Desktop Entry]
Name=My App
Exec=/usr/bin/myapp
Icon=myapp
Type=Application
Categories=Utility;

Installation:

Terminal window
# 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
PhaseTimeNotes
Analysis<1sGo code scanning
Binding Generation<1sTypeScript generation
Frontend Build5-30sDepends on project size
Go Compilation2-10sDepends on code size
Asset Embedding<1sEmbedding frontend
Total10-45sFirst build
Incremental5-15sSubsequent builds

1. Use build cache:

Terminal window
# Go build cache is automatic
# Frontend cache (Vite)
npm run build # Uses cache by default

2. Run only what you need:

Terminal window
# Pick the specific Taskfile target you actually need
wails3 task common:build:frontend # rebuild only the frontend
wails3 task windows:build # rebuild only the Windows binary

3. Parallel builds (multi-machine / CI):

Cross-compilation between Linux/Windows/macOS in v3 generally happens in a Docker wails-cross container or on dedicated runners per platform — wails3 build itself targets the host OS. See Cross-platform Builds for the supported workflows.

4. Use faster tools:

Terminal window
# Use esbuild instead of webpack
# (Vite uses esbuild by default)

Symptom: wails3 build exits with error

Common causes:

  1. Go compilation error

    Terminal window
    # Check Go code compiles
    go build
  2. Frontend build error

    Terminal window
    # Check frontend builds
    cd frontend
    npm run build
  3. Missing dependencies

    Terminal window
    # Install dependencies
    npm install
    go mod download

Symptom: Binary is >50MB

Solutions:

  1. Strip debug symbols (the shipped Taskfile already passes -ldflags="-s -w" to go build).

  2. Check embedded assets

    Terminal window
    # Remove unnecessary files from frontend/dist/
    # Check for large images, videos, etc.
  3. Use UPX compression

    Terminal window
    upx --best bin/myapp.exe

Symptom: Builds take >1 minute

Solutions:

  1. Use build cache

    • Go cache is automatic
    • Frontend cache (Vite) is automatic
  2. Run only the task you need

    Terminal window
    wails3 task common:build:frontend
    wails3 task windows:build
  3. Optimise frontend build

    vite.config.js
    export default {
    build: {
    minify: 'esbuild', // Faster than terser
    },
    }
  • Use wails3 dev during development - Fast iteration
  • Use wails3 build for releases - Optimised output
  • Version your builds - Use -ldflags 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 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

Building Applications - Detailed guide to building and packaging
Learn More →

Cross-Platform Builds - Build for all platforms from one machine
Learn More →

Creating Installers - Create installers for end users
Learn More →


Questions about building? Ask in Discord or check the build examples.