Events API

Overview

The Events API provides methods to emit and listen to events, enabling communication between different parts of your application.

Event Types:

  • Application Events - App lifecycle events (startup, shutdown)
  • Window Events - Window state changes (focus, blur, resize)
  • Custom Events - User-defined events for app-specific communication

Communication Patterns:

  • Go to Frontend - Emit events from Go, listen in JavaScript
  • Frontend to Go - Not directly (use service bindings instead)
  • Frontend to Frontend - Via Go or local runtime events
  • Window to Window - Target specific windows or broadcast to all

Event Methods (Go)

app.Event.Emit()

Emits a custom event to all windows. Returns true if a hook cancelled the emit.

func (em *EventManager) Emit(name string, data ...any) bool

Parameters:

  • name - Event name
  • data - Optional data to send with the event

Example:

// Emit simple event
app.Event.Emit("user-logged-in")

// Emit with data
app.Event.Emit("data-updated", map[string]interface{}{
    "count": 42,
    "status": "success",
})

// Emit multiple values
app.Event.Emit("progress", 75, "Processing files...")

app.Event.On()

Listens for custom events in Go.

func (em *EventManager) On(name string, callback func(*CustomEvent)) func()

Parameters:

  • name - Event name to listen for
  • callback - Function called when event is emitted

Returns: Cleanup function to remove the event listener

Example:

// Listen for events
cleanup := 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)
})

// Later, remove listener
cleanup()

Window-Specific Events

Emit events to a specific window:

// Emit to specific window
window.EmitEvent("notification", "Hello from Go!")

// Emit to all windows
app.Event.Emit("global-update", data)

Event Methods (Frontend)

On()

Listens for events from Go.

import { Events } from '@wailsio/runtime'

Events.On(eventName, callback)

Parameters:

  • eventName - Name of the event to listen for
  • callback - Function called when event is received

Returns: Cleanup function

Example:

import { Events } from '@wailsio/runtime'

// Listen for events
const cleanup = Events.On('data-updated', (data) => {
    console.log('Count:', data.count)
    console.log('Status:', data.status)
    updateUI(data)
})

// Later, remove listener
cleanup()

Once()

Listens for a single event occurrence.

import { Events } from '@wailsio/runtime'

Events.Once(eventName, callback)

Example:

import { Events } from '@wailsio/runtime'

// Listen for first occurrence only
Events.Once('initialization-complete', (data) => {
    console.log('App initialized!', data)
    // This will only fire once
})

Off()

Removes all listeners for one or more events. Off takes variadic event-name strings — it does not take a callback. To remove a single listener, keep the unsubscribe function returned by Events.On(...) and call it.

import { Events } from '@wailsio/runtime'

Events.Off(...eventNames: string[]): void

Example:

import { Events } from '@wailsio/runtime'

// Preferred: keep the unsubscribe fn from On()
const unsubscribe = Events.On('my-event', (data) => {
    console.log('Event received:', data)
})

// Later — remove just this listener
unsubscribe()

// Or: remove every listener for one or more events
Events.Off('my-event', 'another-event')

OffAll()

Removes every event listener. Takes no arguments.

import { Events } from '@wailsio/runtime'

Events.OffAll(): void

Example:

import { Events } from '@wailsio/runtime'

// Remove all listeners — typically used during teardown.
Events.OffAll()

OnMultiple()

Listens for an event up to max times, then automatically unsubscribes.

Events.OnMultiple(eventName: string, callback, max: number): () => void

Example:

Events.OnMultiple('progress', (data) => {
    console.log('progress', data)
}, 5)

Application Events

app.Event.OnApplicationEvent()

Listens for application lifecycle events.

func (em *EventManager) OnApplicationEvent(
    eventType events.ApplicationEventType,
    callback func(*ApplicationEvent),
) func()

Event constants live in the events package — events.Common.* for cross-platform events, events.Mac.* / events.Windows.* / events.Linux.* for platform-specific ones.

Common application events:

  • events.Common.ApplicationStarted - Application has finished launching.
  • events.Common.ThemeChanged - System theme switched between light and dark.
  • events.Common.ApplicationOpenedWithFile - Launched via a file association.
  • events.Common.ApplicationLaunchedWithUrl - Launched via a URL scheme.

There is no generic “application shutdown” event constant — register shutdown cleanup via application.Options.OnShutdown or app.OnShutdown(func()).

Example:

import "github.com/wailsapp/wails/v3/pkg/events"

// Handle application startup
app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) {
    app.Logger.Info("Application started")
})

// React to theme changes
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) {
    if e.Context().IsDarkMode() {
        app.Logger.Info("Dark mode enabled")
    }
})

// Shutdown cleanup is configured on the application, not as an event:
app.OnShutdown(func() {
    database.Close()
    saveSettings()
})

Window Events

OnWindowEvent()

Listens for window-specific events.

func (w *WebviewWindow) OnWindowEvent(
    eventType events.WindowEventType,
    callback func(*WindowEvent),
) func()

Window event constants live in the events package: events.Common.* for cross-platform events (and events.Mac.* / events.Windows.* / events.Linux.* for platform-specific ones).

Common window events:

  • events.Common.WindowFocus - Window gained focus.
  • events.Common.WindowLostFocus - Window lost focus.
  • events.Common.WindowClosing - Window is about to close (cancellable via RegisterHook).
  • events.Common.WindowDidResize - Window was resized.
  • events.Common.WindowDidMove - Window was moved.
  • events.Common.WindowMinimise / WindowUnMinimise / WindowMaximise / WindowUnMaximise / WindowFullscreen / WindowUnFullscreen.
  • events.Common.WindowRuntimeReady - Safe to emit events to the in-window runtime.

Example:

import "github.com/wailsapp/wails/v3/pkg/events"

// Handle window focus
window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
    app.Logger.Info("Window focused")
})

// Handle window resize
window.OnWindowEvent(events.Common.WindowDidResize, func(e *application.WindowEvent) {
    width, height := window.Size()
    app.Logger.Info("Window resized", "width", width, "height", height)
})

Common Patterns

These patterns demonstrate proven approaches for using events in real-world applications. Each pattern solves a specific communication challenge between your Go backend and frontend, helping you build responsive, well-structured applications.

Request/Response Pattern

Use this when you want to notify the frontend about the completion of backend operations, such as after data fetching, file processing, or background tasks. The service binding returns data directly, while events provide additional notifications for UI updates like showing toast messages or refreshing lists.

Go:

// Service method
type DataService struct {
    app *application.App
}

func (s *DataService) FetchData(query string) ([]Item, error) {
    items := fetchFromDatabase(query)

    // Emit event when done
    s.app.Event.Emit("data-fetched", map[string]interface{}{
        "query": query,
        "count": len(items),
    })

    return items, nil
}

JavaScript:

import { FetchData } from './bindings/DataService'
import { Events } from '@wailsio/runtime'

// Listen for completion event
Events.On('data-fetched', (data) => {
    console.log(`Fetched ${data.count} items for query: ${data.query}`)
    showNotification(`Found ${data.count} results`)
})

// Call service method
const items = await FetchData("search term")
displayItems(items)

Progress Updates

Ideal for long-running operations like file uploads, batch processing, large data imports, or video encoding. Emit progress events during the operation to update progress bars, status text, or step indicators in the UI, providing users with real-time feedback.

Go:

func (s *Service) ProcessFiles(files []string) error {
    total := len(files)

    for i, file := range files {
        // Process file
        processFile(file)

        // Emit progress event
        s.app.Event.Emit("progress", map[string]interface{}{
            "current": i + 1,
            "total":   total,
            "percent": float64(i+1) / float64(total) * 100,
            "file":    file,
        })
    }

    s.app.Event.Emit("processing-complete")
    return nil
}

JavaScript:

import { Events } from '@wailsio/runtime'

// Update progress bar
Events.On('progress', (data) => {
    progressBar.style.width = `${data.percent}%`
    statusText.textContent = `Processing ${data.file}... (${data.current}/${data.total})`
})

// Handle completion
Events.Once('processing-complete', () => {
    progressBar.style.width = '100%'
    statusText.textContent = 'Complete!'
    setTimeout(() => hideProgressBar(), 2000)
})

Multi-Window Communication

Perfect for applications with multiple windows like settings panels, dashboards, or document viewers. Broadcast events to synchronize state across all windows (theme changes, user preferences) or send targeted events to specific windows for window-specific updates.

Go:

// Broadcast to all windows
app.Event.Emit("theme-changed", "dark")

// Send to specific window
preferencesWindow.EmitEvent("settings-updated", settings)

// Per-window listener — receive on the global event bus, but
// gate by the event's source-window name (set automatically when
// a window emits via window.EmitEvent).
app.Event.On("request-data", func(e *application.CustomEvent) {
    if e.Sender != window1.Name() {
        return
    }
    window1.EmitEvent("data-response", data)
})

JavaScript:

import { Events } from '@wailsio/runtime'

// Listen in any window
Events.On('theme-changed', (theme) => {
    document.body.className = theme
})

State Synchronization

Use when you need to keep frontend and backend state in sync, such as user sessions, application configuration, or collaborative features. When state changes on the backend, emit events to update all connected frontends, ensuring consistency across your application.

Go:

type StateService struct {
    app   *application.App
    state map[string]interface{}
    mu    sync.RWMutex
}

func (s *StateService) UpdateState(key string, value interface{}) {
    s.mu.Lock()
    s.state[key] = value
    s.mu.Unlock()

    // Notify all windows
    s.app.Event.Emit("state-updated", map[string]interface{}{
        "key":   key,
        "value": value,
    })
}

func (s *StateService) GetState(key string) interface{} {
    s.mu.RLock()
    defer s.mu.RUnlock()
    return s.state[key]
}

JavaScript:

import { Events } from '@wailsio/runtime'
import { GetState } from './bindings/StateService'

// Keep local state in sync
let localState = {}

Events.On('state-updated', async (data) => {
    localState[data.key] = data.value
    updateUI(data.key, data.value)
})

// Initialize state
const initialState = await GetState("all")
localState = initialState

Event-Driven Notifications

Best for displaying user feedback like success confirmations, error alerts, or info messages. Instead of calling UI code directly from services, emit notification events that the frontend handles consistently, making it easy to change notification styles or add features like notification history.

Go:

type NotificationService struct {
    app *application.App
}

func (s *NotificationService) Success(message string) {
    s.app.Event.Emit("notification", map[string]interface{}{
        "type":    "success",
        "message": message,
    })
}

func (s *NotificationService) Error(message string) {
    s.app.Event.Emit("notification", map[string]interface{}{
        "type":    "error",
        "message": message,
    })
}

func (s *NotificationService) Info(message string) {
    s.app.Event.Emit("notification", map[string]interface{}{
        "type":    "info",
        "message": message,
    })
}

JavaScript:

import { Events } from '@wailsio/runtime'

// Unified notification handler
Events.On('notification', (data) => {
    const toast = document.createElement('div')
    toast.className = `toast toast-${data.type}`
    toast.textContent = data.message

    document.body.appendChild(toast)

    setTimeout(() => {
        toast.classList.add('fade-out')
        setTimeout(() => toast.remove(), 300)
    }, 3000)
})

Complete Example

Go:

package main

import (
    "sync"
    "time"

    "github.com/wailsapp/wails/v3/pkg/application"
    "github.com/wailsapp/wails/v3/pkg/events"
)

type EventDemoService struct {
    app *application.App
    mu  sync.Mutex
}

func NewEventDemoService(app *application.App) *EventDemoService {
    service := &EventDemoService{app: app}

    // Listen for custom events
    app.Event.On("user-action", func(e *application.CustomEvent) {
        data := e.Data.(map[string]interface{})
        app.Logger.Info("User action received", "data", data)
    })

    return service
}

func (s *EventDemoService) StartLongTask() {
    go func() {
        s.app.Event.Emit("task-started")

        for i := 1; i <= 10; i++ {
            time.Sleep(500 * time.Millisecond)

            s.app.Event.Emit("task-progress", map[string]interface{}{
                "step":    i,
                "total":   10,
                "percent": i * 10,
            })
        }

        s.app.Event.Emit("task-completed", map[string]interface{}{
            "message": "Task finished successfully!",
        })
    }()
}

func (s *EventDemoService) BroadcastMessage(message string) {
    s.app.Event.Emit("broadcast", message)
}

func main() {
    app := application.New(application.Options{
        Name: "Event Demo",
    })

    // Handle application lifecycle
    app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) {
        app.Logger.Info("Application started!")
    })

    app.OnShutdown(func() {
        app.Logger.Info("Application shutting down...")
    })

    // Register service (RegisterService returns nothing).
    service := NewEventDemoService(app)
    app.RegisterService(application.NewService(service))

    // Create window
    window := app.Window.New()

    // Handle window events
    window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
        window.EmitEvent("window-state", "focused")
    })

    window.OnWindowEvent(events.Common.WindowLostFocus, func(e *application.WindowEvent) {
        window.EmitEvent("window-state", "blurred")
    })

    window.Show()
    app.Run()
}

JavaScript:

import { Events } from '@wailsio/runtime'
import { StartLongTask, BroadcastMessage } from './bindings/EventDemoService'

// Task events
Events.On('task-started', () => {
    console.log('Task started...')
    document.getElementById('status').textContent = 'Running...'
})

Events.On('task-progress', (data) => {
    const progressBar = document.getElementById('progress')
    progressBar.style.width = `${data.percent}%`
    console.log(`Step ${data.step} of ${data.total}`)
})

Events.Once('task-completed', (data) => {
    console.log('Task completed!', data.message)
    document.getElementById('status').textContent = data.message
})

// Broadcast events
Events.On('broadcast', (message) => {
    console.log('Broadcast:', message)
    alert(message)
})

// Window state events
Events.On('window-state', (state) => {
    console.log('Window is now:', state)
    document.body.dataset.windowState = state
})

// Trigger long task
document.getElementById('startTask').addEventListener('click', async () => {
    await StartLongTask()
})

// Send broadcast
document.getElementById('broadcast').addEventListener('click', async () => {
    const message = document.getElementById('message').value
    await BroadcastMessage(message)
})

Built-in Events

Wails provides built-in system events for application and window lifecycle. These events are emitted automatically by the framework.

Common Events vs Platform-Native Events

Wails provides two types of system events:

Common Events (events.Common.*) are cross-platform abstractions that work consistently across macOS, Windows, and Linux. These are the events you should use in your application for maximum portability.

Platform-Native Events (events.Mac.*, events.Windows.*, events.Linux.*) are the underlying OS-specific events that Common Events are mapped from. These provide access to platform-specific behaviors and edge cases.

How They Work:

import "github.com/wailsapp/wails/v3/pkg/events"

// ✅ RECOMMENDED: Use Common Events for cross-platform code
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
    // This works on all platforms
})

// Platform-specific events for advanced use cases
window.OnWindowEvent(events.Mac.WindowWillClose, func(e *application.WindowEvent) {
    // macOS-specific "will close" event (before WindowClosing)
})

window.OnWindowEvent(events.Windows.WindowClosing, func(e *application.WindowEvent) {
    // Windows-specific close event
})

Event Mapping:

Platform-native events are automatically mapped to Common Events:

  • macOS: events.Mac.WindowShouldCloseevents.Common.WindowClosing
  • Windows: events.Windows.WindowClosingevents.Common.WindowClosing
  • Linux: events.Linux.WindowDeleteEventevents.Common.WindowClosing

This mapping happens automatically in the background, so when you listen for events.Common.WindowClosing, you’ll receive it regardless of the platform.

When to Use Each:

  • Use Common Events for 99% of your application code - they provide consistent behavior across platforms
  • Use Platform-Native Events only when you need platform-specific functionality that isn’t available in Common Events (e.g., macOS-specific window lifecycle events, Windows power management events)

Application Events

Event Description When Emitted Cancellable
ApplicationOpenedWithFile Application opened with a file When app is launched with a file (e.g., from file association) No
ApplicationStarted Application has finished launching After app initialization is complete and app is ready No
ApplicationLaunchedWithUrl Application launched with a URL When app is launched via URL scheme No
ThemeChanged System theme changed When OS theme switches between light/dark mode No
SystemWillSleep System is about to suspend Just before the OS suspends (macOS / Windows / Linux-with-logind) No
SystemDidWake System resumed from suspend Immediately on resume from sleep (macOS / Windows / Linux-with-logind) No

Usage:

import "github.com/wailsapp/wails/v3/pkg/events"

app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) {
    app.Logger.Info("Application ready!")
})

app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) {
    // Update app theme
})

Window Events

Event Description When Emitted Cancellable
WindowClosing Window is about to close Before window closes (user clicked X, Close() called) Yes
WindowDidMove Window moved to new position After window position changes (debounced) No
WindowDidResize Window was resized After window size changes No
WindowDPIChanged Window DPI scaling changed When moving between monitors with different DPI (Windows) No
WindowFilesDropped Files dropped via native OS drag-drop After files are dropped from OS onto window No
WindowFocus Window gained focus When window becomes active No
WindowFullscreen Window entered fullscreen After Fullscreen() or user enters fullscreen No
WindowHide Window was hidden After Hide() or window becomes occluded No
WindowLostFocus Window lost focus When window becomes inactive No
WindowMaximise Window was maximized After Maximise() or user maximizes Yes (macOS)
WindowMinimise Window was minimized After Minimise() or user minimizes Yes (macOS)
WindowRestore Window restored from min/max state After Restore() (Windows primarily) No
WindowRuntimeReady Wails runtime loaded and ready When JavaScript runtime initialization completes No
WindowShow Window became visible After Show() or window becomes visible No
WindowUnFullscreen Window exited fullscreen After UnFullscreen() or user exits fullscreen No
WindowUnMaximise Window exited maximized state After UnMaximise() or user unmaximizes Yes (macOS)
WindowUnMinimise Window exited minimized state After UnMinimise()/Restore() or user restores Yes (macOS)
WindowZoomIn Window content zoom increased After ZoomIn() called (macOS primarily) Yes (macOS)
WindowZoomOut Window content zoom decreased After ZoomOut() called (macOS primarily) Yes (macOS)
WindowZoomReset Window content zoom reset to 100% After ZoomReset() called (macOS primarily) Yes (macOS)
WindowDropZoneFilesDropped Files dropped on JS-defined drop zone When files dropped onto element with drop zone No

Usage:

import "github.com/wailsapp/wails/v3/pkg/events"

// Listen for window events
window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
    app.Logger.Info("Window focused")
})

// Cancel window close
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
    dlg := app.Dialog.Question().SetMessage("Close window?")
    yes := dlg.AddButton("Yes")
    no := dlg.AddButton("No")
    dlg.SetDefaultButton(yes)
    dlg.SetCancelButton(no)
    no.OnClick(func() { e.Cancel() })

    dlg.Show()
})

// Wait for runtime ready
window.OnWindowEvent(events.Common.WindowRuntimeReady, func(e *application.WindowEvent) {
    app.Logger.Info("Runtime ready, safe to emit events to frontend")
    window.EmitEvent("app-initialized", data)
})

Important Notes:

  • WindowRuntimeReady is critical - wait for this event before emitting events to the frontend
  • WindowDidMove and WindowDidResize are debounced (50ms default) to prevent event flooding
  • Cancellable events can be prevented by calling event.Cancel() in a RegisterHook() handler
  • WindowFilesDropped is for native OS file drops; WindowDropZoneFilesDropped is for web-based drop zones
  • Some events are platform-specific (e.g., WindowDPIChanged on Windows, zoom events primarily on macOS)

Event Naming Conventions

// Good - descriptive and specific
app.Event.Emit("user:logged-in", user)
app.Event.Emit("data:fetch:complete", results)
app.Event.Emit("ui:theme:changed", theme)

// Bad - vague and unclear
app.Event.Emit("event1", data)
app.Event.Emit("update", stuff)
app.Event.Emit("e", value)

Performance Considerations

Debouncing High-Frequency Events

type Service struct {
    app            *application.App
    lastEmit       time.Time
    debounceWindow time.Duration
}

func (s *Service) EmitWithDebounce(event string, data interface{}) {
    now := time.Now()
    if now.Sub(s.lastEmit) < s.debounceWindow {
        return // Skip this emission
    }

    s.app.Event.Emit(event, data)
    s.lastEmit = now
}

Throttling Events

import { Events } from '@wailsio/runtime'

let lastUpdate = 0
const throttleMs = 100

Events.On('high-frequency-event', (data) => {
    const now = Date.now()
    if (now - lastUpdate < throttleMs) {
        return // Skip this update
    }

    processUpdate(data)
    lastUpdate = now
})
Edit page

Last updated: