---
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.Mutex
}
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:**
- frontend/bindings
- myapp
- calculatorservice.js
- counterservice.js
- databaseservice.js
- index.js
### TypeScript Generation
```bash
wails3 generate bindings -ts
```
**Generates `.ts` files** with full TypeScript types.
### Custom Output Directory
```bash
wails3 generate bindings -d ./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
/**
* @param {number} a
* @param {number} b
* @returns {Promise}
*/
export function Add(a, b) {
return window.wails.Call('CalculatorService.Add', 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
export function Add(a: number, b: number): Promise
export function Subtract(a: number, b: number): Promise
export function Multiply(a: number, b: number): Promise
export function Divide(a: number, b: number): Promise
```
**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` |
| `map[K]V` | `Map` |
| `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:** <1ms
```
JavaScript → Bridge → Go → Bridge → JavaScript
↓ ↓ ↓ ↓ ↓
<0.1ms <0.1ms [varies] <0.1ms <0.1ms
```
**Compared to alternatives:**
- HTTP/REST: 5-50ms
- IPC: 1-10ms
- Wails: <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 => `
${todo.Title}
`).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
Deep dive into the service system.
[Learn More →](/features/bindings/services)
Bind complex data structures.
[Learn More →](/features/bindings/models)
Understand the bridge mechanism.
[Learn More →](/concepts/bridge)
Use events for pub/sub communication.
[Learn More →](/features/events/system)
---
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [binding examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/binding).