Skip to content

Advanced Binding

This guide covers advanced techniques for customizing and optimizing the binding generation process in Wails v3.

Customizing Generated Code with Directives

Section titled “Customizing Generated Code with Directives”

The //wails:inject directive allows you to inject custom JavaScript/TypeScript code into the generated bindings:

//wails:inject console.log("Hello from Wails!");
type MyService struct {}
func (s *MyService) Greet(name string) string {
return "Hello, " + name
}

This will inject the specified code into the generated JavaScript/TypeScript file for the MyService service.

You can also use conditional injection to target specific output formats:

//wails:inject j*:console.log("Hello JS!"); // JavaScript only
//wails:inject t*:console.log("Hello TS!"); // TypeScript only

The //wails:include directive allows you to include additional files with the generated bindings:

//wails:include js/*.js
package mypackage

This directive is typically used in package documentation comments to include additional JavaScript/TypeScript files with the generated bindings.

The //wails:internal directive marks a type or method as internal, preventing it from being exported to the frontend:

//wails:internal
type InternalModel struct {
Field string
}
//wails:internal
func (s *MyService) InternalMethod() {}

This is useful for types and methods that are only used internally by your Go code and should not be exposed to the frontend.

The //wails:ignore directive completely ignores a method during binding generation:

//wails:ignore
func (s *MyService) IgnoredMethod() {}

This is similar to //wails:internal, but it completely ignores the method rather than marking it as internal.

The //wails:id directive specifies a custom ID for a method, overriding the default hash-based ID:

//wails:id 42
func (s *MyService) CustomIDMethod() {}

This can be useful for maintaining compatibility when refactoring code.

The binding generator handles nested structs automatically:

type Address struct {
Street string
City string
State string
Zip string
}
type Person struct {
Name string
Address Address
}
func (s *MyService) GetPerson() Person {
return Person{
Name: "John Doe",
Address: Address{
Street: "123 Main St",
City: "Anytown",
State: "CA",
Zip: "12345",
},
}
}

The generated JavaScript/TypeScript code will include classes for both Person and Address.

Maps and slices are also handled automatically:

type Person struct {
Name string
Attributes map[string]string
Friends []string
}
func (s *MyService) GetPerson() Person {
return Person{
Name: "John Doe",
Attributes: map[string]string{
"hair": "brown",
"eyes": "blue",
},
Friends: []string{"Jane", "Bob", "Alice"},
}
}

In JavaScript, maps are represented as objects and slices as arrays. In TypeScript, maps are represented as Record<K, V> and slices as T[].

The binding generator supports generic types:

type Result[T any] struct {
Data T
Error string
}
func (s *MyService) GetResult() Result[string] {
return Result[string]{
Data: "Hello, World!",
Error: "",
}
}

The generated TypeScript code will include a generic class for Result:

export class Result<T> {
"Data": T;
"Error": string;
constructor(source: Partial<Result<T>> = {}) {
if (!("Data" in source)) {
this["Data"] = null as any;
}
if (!("Error" in source)) {
this["Error"] = "";
}
Object.assign(this, source);
}
static createFrom<T>(source: string | object = {}): Result<T> {
let parsedSource = typeof source === "string" ? JSON.parse(source) : source;
return new Result<T>(parsedSource as Partial<Result<T>>);
}
}

The binding generator can generate TypeScript interfaces instead of classes using the -i flag:

Terminal window
wails3 generate bindings -ts -i

This will generate TypeScript interfaces for all models:

export interface Person {
Name: string;
Attributes: Record<string, string>;
Friends: string[];
}

By default, the binding generator uses hash-based IDs for method calls. You can use the -names flag to use names instead:

Terminal window
wails3 generate bindings -names

This will generate code that uses method names instead of IDs:

export function Greet(name) {
let $resultPromise = $Call.ByName("Greet", name);
return $resultPromise;
}

This can make the generated code more readable and easier to debug, but it may be slightly less efficient.

By default, the generated code imports the Wails runtime from the @wailsio/runtime npm package. You can use the -b flag to bundle the runtime with the generated code:

Terminal window
wails3 generate bindings -b

This will include the runtime code directly in the generated files, eliminating the need for the npm package.

If you don’t need the index files, you can use the -noindex flag to disable their generation:

Terminal window
wails3 generate bindings -noindex

This can be useful if you prefer to import services and models directly from their respective files.

Here’s an example of an authentication service with custom directives:

package auth
//wails:inject console.log("Auth service initialized");
type AuthService struct {
// Private fields
users map[string]User
}
type User struct {
Username string
Email string
Role string
}
type LoginRequest struct {
Username string
Password string
}
type LoginResponse struct {
Success bool
User User
Token string
Error string
}
// Login authenticates a user
func (s *AuthService) Login(req LoginRequest) LoginResponse {
// Implementation...
}
// GetCurrentUser returns the current user
func (s *AuthService) GetCurrentUser() User {
// Implementation...
}
// Internal helper method
//wails:internal
func (s *AuthService) validateCredentials(username, password string) bool {
// Implementation...
}

Here’s an example of a data processing service with generic types:

package data
type ProcessingResult[T any] struct {
Data T
Error string
}
type DataService struct {}
// Process processes data and returns a result
func (s *DataService) Process(data string) ProcessingResult[map[string]int] {
// Implementation...
}
// ProcessBatch processes multiple data items
func (s *DataService) ProcessBatch(data []string) ProcessingResult[[]map[string]int] {
// Implementation...
}
// Internal helper method
//wails:internal
func (s *DataService) parseData(data string) (map[string]int, error) {
// Implementation...
}

Here’s an example of conditional code injection for different output formats:

//wails:inject j*:/**
//wails:inject j*: * @param {string} arg
//wails:inject j*: * @returns {Promise<void>}
//wails:inject j*: */
//wails:inject j*:export async function CustomMethod(arg) {
//wails:inject t*:export async function CustomMethod(arg: string): Promise<void> {
//wails:inject await InternalMethod("Hello " + arg + "!");
//wails:inject }
type Service struct{}

This injects different code for JavaScript and TypeScript outputs, providing appropriate type annotations for each language.