---
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 { Events } from '@wailsio/runtime'

Events.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
app := application.New(application.Options{
    Name: "My App",
    
    // Called before windows are created
    OnStartup: func(ctx context.Context) {
        // Initialise database, load config, etc.
    },
    
    // Called when app is about to quit
    OnShutdown: func() {
        // Save state, close connections, etc.
    },
})
```

[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).
