Raw Messages
Raw messages provide a low-level communication channel between your frontend and backend, bypassing the standard binding system. This trades convenience for speed.
When to Use Raw Messages
Section titled “When to Use Raw Messages”Raw messages are best suited for extreme edge cases:
- Ultra-high-frequency updates - Thousands of messages per second where every microsecond matters
- Custom message protocols - When you need complete control over the wire format
Backend Setup
Section titled “Backend Setup”Configure the RawMessageHandler in your application options:
package main
import ( "encoding/json" "fmt"
"github.com/wailsapp/wails/v3/pkg/application")
func main() { app := application.New(application.Options{ Name: "Raw Message Demo", Assets: application.AssetOptions{ Handler: application.BundledAssetFileServer(assets), }, RawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) { fmt.Printf("Raw message from window '%s': %s (origin: %+v)\n", window.Name(), message, originInfo.Origin)
// Process the message and respond via events response := processMessage(message) window.EmitEvent("raw-response", response) }, })
app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "My App", Name: "main", })
app.Run()}
func processMessage(message string) map[string]any { // Your custom message processing logic return map[string]any{ "received": message, "status": "processed", }}Handler Signature
Section titled “Handler Signature”RawMessageHandler func(window Window, message string, originInfo *application.OriginInfo)| Parameter | Type | Description |
|---|---|---|
window | Window | The window that sent the message |
message | string | The raw message content |
originInfo | *application.OriginInfo | Origin information about the message source |
OriginInfo Structure
Section titled “OriginInfo Structure”type OriginInfo struct { Origin string TopOrigin string IsMainFrame bool}| Field | Type | Description |
|---|---|---|
Origin | string | The origin URL of the document that sent the message |
TopOrigin | string | The top-level origin URL (may differ from Origin in iframes) |
IsMainFrame | bool | Whether the message originated from the main frame |
Platform-Specific Availability
Section titled “Platform-Specific Availability”- macOS:
OriginandIsMainFrameare provided - Windows:
OriginandTopOriginare provided - Linux: Only
Originis provided
Origin Validation
Section titled “Origin Validation”Always verify the origin of incoming messages before processing them. The originInfo parameter provides critical security information that must be validated to prevent unauthorized access.
Malicious content, compromised content, or unintended scripts could send raw messages. Without origin validation, you may process commands from untrusted sources. Use originInfo to ensure messages come from expected sources.
Key Validation Points
Section titled “Key Validation Points”- Always check
Origin- Verify the origin matches your expected trusted sources (typicallywails://wailsorhttp://wails.localhostfor local assets or your app’s specific origin) - Validate
IsMainFrame(macOS) - Be aware if the message comes from an iframe, as this may indicate embedded content with different security contexts - Use
TopOrigin(Windows) - Verify the top-level origin when dealing with framed content - Reject unexpected origins - Fail securely by rejecting messages from origins you don’t explicitly allow
Frontend Setup
Section titled “Frontend Setup”Send raw messages using System.invoke():
<!DOCTYPE html><html><head> <script type="module"> import { System, Events } from '@wailsio/runtime'
// Send raw message document.getElementById('send').addEventListener('click', () => { const message = document.getElementById('input').value System.invoke(message) })
// Listen for response Events.On('raw-response', (event) => { console.log('Response:', event.data) }) </script></head><body> <input type="text" id="input" placeholder="Enter message" /> <button id="send">Send</button></body></html>Using the Pre-built Bundle
Section titled “Using the Pre-built Bundle”If you’re not using npm, access invoke via the global wails object:
<script type="module" src="/wails/runtime.js"></script><script> window.onload = function() { document.getElementById('send').onclick = function() { wails.System.invoke('my-message') } }</script>Structured Messages
Section titled “Structured Messages”For complex data, serialize to JSON:
Frontend
Section titled “Frontend”import { System } from '@wailsio/runtime'
const command = { action: 'update', payload: { id: 123, value: 'new value' }}
System.invoke(JSON.stringify(command))Backend
Section titled “Backend”RawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) { var cmd struct { Action string `json:"action"` Payload struct { ID int `json:"id"` Value string `json:"value"` } `json:"payload"` }
if err := json.Unmarshal([]byte(message), &cmd); err != nil { window.EmitEvent("error", err.Error()) return }
switch cmd.Action { case "update": // Handle update result := handleUpdate(cmd.Payload.ID, cmd.Payload.Value) window.EmitEvent("update-complete", result) default: window.EmitEvent("error", "unknown action") }}Performance Comparison
Section titled “Performance Comparison”| Approach | Overhead | Type Safety | Use Case |
|---|---|---|---|
| Service Bindings | Higher | Full | General purpose |
| Raw Messages | Minimal | Manual | High-frequency, performance-critical |
Benchmark Example
Section titled “Benchmark Example”Raw messages can process significantly more messages per second compared to service bindings for simple payloads:
// Raw message handler - minimal overheadRawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) { // Direct string processing, no reflection or marshaling counter++}Complete Example
Section titled “Complete Example”Here’s a full example implementing a simple command protocol:
main.go
Section titled “main.go”package main
import ( "embed" "encoding/json" "fmt" "time"
"github.com/wailsapp/wails/v3/pkg/application")
//go:embed assetsvar assets embed.FS
type Command struct { Type string `json:"type"` Data json.RawMessage `json:"data"`}
func main() { app := application.New(application.Options{ Name: "Raw Message Demo", Assets: application.AssetOptions{ Handler: application.BundledAssetFileServer(assets), }, Mac: application.MacOptions{ ApplicationShouldTerminateAfterLastWindowClosed: true, }, RawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) { var cmd Command if err := json.Unmarshal([]byte(message), &cmd); err != nil { window.EmitEvent("error", map[string]string{"error": err.Error()}) return }
switch cmd.Type { case "ping": window.EmitEvent("pong", map[string]any{ "time": time.Now().UnixMilli(), "window": window.Name(), }) case "echo": var text string json.Unmarshal(cmd.Data, &text) window.EmitEvent("echo", text) default: window.EmitEvent("error", map[string]string{ "error": fmt.Sprintf("unknown command: %s", cmd.Type), }) } }, })
app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "Raw Message Demo", Name: "main", Width: 400, Height: 300, })
app.Run()}assets/index.html
Section titled “assets/index.html”<!DOCTYPE html><html><head> <title>Raw Message Demo</title> <style> body { font-family: sans-serif; padding: 20px; } button { margin: 5px; padding: 10px 20px; } #output { margin-top: 20px; padding: 10px; background: #f0f0f0; } </style></head><body> <h1>Raw Message Demo</h1>
<button id="ping">Ping</button> <button id="echo">Echo "Hello"</button>
<div id="output">Waiting for response...</div>
<script type="module"> import { System, Events } from '@wailsio/runtime'
const output = document.getElementById('output')
function send(type, data) { System.invoke(JSON.stringify({ type, data })) }
document.getElementById('ping').onclick = () => send('ping') document.getElementById('echo').onclick = () => send('echo', 'Hello')
Events.On('pong', (e) => { output.textContent = `Pong from ${e.data.window} at ${e.data.time}` })
Events.On('echo', (e) => { output.textContent = `Echo: ${e.data}` })
Events.On('error', (e) => { output.textContent = `Error: ${e.data.error}` }) </script></body></html>Best Practices
Section titled “Best Practices”- Use raw messages for genuinely performance-critical paths
- Implement proper error handling in your handler
- Use events to send responses back to the frontend
- Consider JSON for structured data
- Keep message processing fast to avoid blocking
- Use raw messages when service bindings would suffice
- Forget to validate incoming messages
- Block in the handler with long-running operations (use goroutines)
- Ignore the window parameter when responses need to target specific windows
Multi-Window Considerations
Section titled “Multi-Window Considerations”The window parameter identifies which window sent the message, allowing you to:
- Send responses to the correct window
- Implement window-specific behavior
- Track message sources for debugging
RawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) { // Respond only to the sending window window.EmitEvent("response", result)
// Or broadcast to all windows app.Event.Emit("broadcast", result)}Next Steps
Section titled “Next Steps”- Service Bindings - Standard approach for most applications
- Events - Event system for backend-to-frontend communication
- Performance - General performance optimization