Method Bindings
Learn how to bind Go methods.
Wails automatically generates JavaScript/TypeScript classes from Go structs, providing full type safety when passing complex data between backend and frontend. Write Go structs, generate bindings, and get fully-typed frontend models complete with constructors, type annotations, and JSDoc comments.
Go struct:
type User struct { ID int `json:"id"` Name string `json:"name"` Email string `json:"email"` CreatedAt time.Time `json:"createdAt"`}
func (s *UserService) GetUser(id int) (*User, error) { // Return user}Generate:
wails3 generate bindingsJavaScript:
import { GetUser } from './bindings/myapp/userservice'import { User } from './bindings/myapp/models'
const user = await GetUser(1)console.log(user.Name) // Type-safe!That’s it! Full type safety across the bridge.
type Person struct { Name string Age int}Generated JavaScript:
export class Person { /** @type {string} */ Name = ""
/** @type {number} */ Age = 0
constructor(source = {}) { Object.assign(this, source) }
static createFrom(source = {}) { return new Person(source) }}type User struct { ID int `json:"id"` Name string `json:"name"` Email string `json:"email"` CreatedAt time.Time `json:"createdAt"`}Generated JavaScript:
export class User { /** @type {number} */ id = 0
/** @type {string} */ name = ""
/** @type {string} */ email = ""
/** @type {Date} */ createdAt = new Date()
constructor(source = {}) { Object.assign(this, source) }}JSON tags control field names in JavaScript.
// User represents an application usertype User struct { // Unique identifier ID int `json:"id"`
// Full name of the user Name string `json:"name"`
// Email address (must be unique) Email string `json:"email"`}Generated JavaScript:
/** * User represents an application user */export class User { /** * Unique identifier * @type {number} */ id = 0
/** * Full name of the user * @type {string} */ name = ""
/** * Email address (must be unique) * @type {string} */ email = ""}Comments become JSDoc! Your IDE shows them.
type Address struct { Street string `json:"street"` City string `json:"city"` Country string `json:"country"`}
type User struct { ID int `json:"id"` Name string `json:"name"` Address Address `json:"address"`}Generated JavaScript:
export class Address { /** @type {string} */ street = ""
/** @type {string} */ city = ""
/** @type {string} */ country = ""}
export class User { /** @type {number} */ id = 0
/** @type {string} */ name = ""
/** @type {Address} */ address = new Address()}Usage:
const user = new User({ id: 1, name: "Alice", address: new Address({ street: "123 Main St", city: "Springfield", country: "USA" })})type Team struct { Name string `json:"name"` Members []string `json:"members"`}Generated JavaScript:
export class Team { /** @type {string} */ name = ""
/** @type {string[]} */ members = []}Usage:
const team = new Team({ name: "Engineering", members: ["Alice", "Bob", "Charlie"]})type Config struct { Settings map[string]string `json:"settings"`}Generated JavaScript:
export class Config { /** @type {Record<string, string>} */ settings = {}}Usage:
const config = new Config({ settings: { theme: "dark", language: "en" }})| Go Type | JavaScript Type |
|---|---|
string | string |
bool | boolean |
int, int8, int16, int32, int64 | number |
uint, uint8, uint16, uint32, uint64 | number |
float32, float64 | number |
byte | number |
rune | number |
| Go Type | JavaScript Type |
|---|---|
time.Time | Date |
[]byte | Uint8Array |
*T | T (pointers transparent) |
interface{} | any |
| Go Type | JavaScript Type |
|---|---|
[]T | T[] |
[N]T | T[] |
map[string]T | Record<string, T> |
map[K]V | Map<K, V> |
chan T (channels)func() (functions)interface{})import { User } from './bindings/myapp/models'
// Empty instanceconst user1 = new User()
// With dataconst user2 = new User({ id: 1, name: "Alice", email: "alice@example.com"})
// From JSON stringconst user3 = User.createFrom('{"id":1,"name":"Alice"}')
// From objectconst user4 = User.createFrom({ id: 1, name: "Alice" })import { CreateUser } from './bindings/myapp/userservice'import { User } from './bindings/myapp/models'
const user = new User({ name: "Bob", email: "bob@example.com"})
const created = await CreateUser(user)console.log("Created user:", created.id)import { GetUser } from './bindings/myapp/userservice'
const user = await GetUser(1)
// user is already a User instanceconsole.log(user.name)console.log(user.email)console.log(user.createdAt.toISOString())import { GetUser, UpdateUser } from './bindings/myapp/userservice'
// Get userconst user = await GetUser(1)
// Modifyuser.name = "Alice Smith"user.email = "alice.smith@example.com"
// Saveawait UpdateUser(user)wails3 generate bindings -tsGenerated:
/** * User represents an application user */export class User { /** * Unique identifier */ id: number = 0
/** * Full name of the user */ name: string = ""
/** * Email address (must be unique) */ email: string = ""
/** * Account creation date */ createdAt: Date = new Date()
constructor(source: Partial<User> = {}) { Object.assign(this, source) }
static createFrom(source: string | Partial<User> = {}): User { const parsedSource = typeof source === 'string' ? JSON.parse(source) : source return new User(parsedSource) }}import { GetUser, CreateUser } from './bindings/myapp/userservice'import { User } from './bindings/myapp/models'
async function example() { // Create user const newUser = new User({ name: "Alice", email: "alice@example.com" })
const created: User = await CreateUser(newUser)
// Get user const user: User = await GetUser(created.id)
// Type-safe access console.log(user.name.toUpperCase()) // ✅ string method console.log(user.id + 1) // ✅ number operation console.log(user.createdAt.getTime()) // ✅ Date method}type User struct { ID int `json:"id"` Name string `json:"name"` Nickname *string `json:"nickname,omitempty"`}JavaScript:
const user = new User({ id: 1, name: "Alice", nickname: "Ally" // Optional})
// Check if setif (user.nickname) { console.log("Nickname:", user.nickname)}The binding generator automatically detects Go named types with constants and generates TypeScript enums or JavaScript const objects — including a $zero member for Go’s zero value and full JSDoc preservation.
type UserRole string
const ( RoleAdmin UserRole = "admin" RoleUser UserRole = "user" RoleGuest UserRole = "guest")Generated TypeScript:
export enum UserRole { $zero = "", RoleAdmin = "admin", RoleUser = "user", RoleGuest = "guest",}Usage:
import { User, UserRole } from './bindings/myapp/models'
const admin = new User({ name: "Admin", role: UserRole.RoleAdmin})For comprehensive coverage of string enums, integer enums, type aliases, imported package enums, and limitations, see the dedicated Enums page.
class User { validate() { if (!this.name) { throw new Error("Name is required") } if (!this.email.includes('@')) { throw new Error("Invalid email") } return true }}
// Useconst user = new User({ name: "Alice", email: "alice@example.com" })user.validate() // ✅
const invalid = new User({ name: "", email: "invalid" })invalid.validate() // ❌ Throws// To JSONconst json = JSON.stringify(user)
// From JSONconst user = User.createFrom(json)
// To plain objectconst obj = { ...user }
// From plain objectconst user2 = new User(obj)Go:
package main
import ( "time" "github.com/wailsapp/wails/v3/pkg/application")
type Address struct { Street string `json:"street"` City string `json:"city"` Country string `json:"country"`}
type User struct { ID int `json:"id"` Name string `json:"name"` Email string `json:"email"` Address Address `json:"address"` CreatedAt time.Time `json:"createdAt"`}
type UserService struct { users []User}
func (s *UserService) GetAll() []User { return s.users}
func (s *UserService) GetByID(id int) (*User, error) { for _, user := range s.users { if user.ID == id { return &user, nil } } return nil, fmt.Errorf("user %d not found", id)}
func (s *UserService) Create(user User) User { user.ID = len(s.users) + 1 user.CreatedAt = time.Now() s.users = append(s.users, user) return user}
func (s *UserService) Update(user User) error { for i, u := range s.users { if u.ID == user.ID { s.users[i] = user return nil } } return fmt.Errorf("user %d not found", user.ID)}
func main() { app := application.New(application.Options{ Services: []application.Service{ application.NewService(&UserService{}), }, })
app.Window.New() app.Run()}JavaScript:
import { GetAll, GetByID, Create, Update } from './bindings/myapp/userservice'import { User, Address } from './bindings/myapp/models'
class UserManager { async loadUsers() { const users = await GetAll() this.renderUsers(users) }
async createUser(name, email, address) { const user = new User({ name, email, address: new Address(address) })
try { const created = await Create(user) console.log("Created user:", created.id) this.loadUsers() } catch (error) { console.error("Failed to create user:", error) } }
async updateUser(id, updates) { try { const user = await GetByID(id) Object.assign(user, updates) await Update(user) this.loadUsers() } catch (error) { console.error("Failed to update user:", error) } }
renderUsers(users) { const list = document.getElementById('users') list.innerHTML = users.map(user => ` <div class="user"> <h3>${user.name}</h3> <p>${user.email}</p> <p>${user.address.city}, ${user.address.country}</p> <small>Created: ${user.createdAt.toLocaleDateString()}</small> </div> `).join('') }}
const manager = new UserManager()manager.loadUsers()*string for nullableMethod Bindings
Learn how to bind Go methods.
Services
Organise code with services.
Best Practices
Binding design patterns.
Go-Frontend Bridge
Understand the bridge mechanism.
Questions? Ask in Discord or check the binding examples.