How to Use protobuf-go-lite with TinyGo for WebAssembly and Embedded Systems
Use protobuf-go-lite to generate static, reflection-free protobuf code that compiles seamlessly with TinyGo for WebAssembly and embedded targets by enabling only the marshal+unmarshal+size features during code generation.
The aperturerobotics/protobuf-go-lite repository provides a lightweight fork of the official Go protobuf implementation specifically designed to eliminate reflection-based code generation. This architectural choice makes it fully compatible with TinyGo, a Go compiler that targets constrained environments where the standard reflect package is unavailable or severely limited.
Why protobuf-go-lite Works with TinyGo
Traditional protobuf implementations rely heavily on Go's reflection package to handle message serialization dynamically. TinyGo, particularly when targeting WebAssembly or microcontrollers, either omits or significantly trims the reflect package to reduce binary size.
The protobuf-go-lite generator solves this by producing static code generation for all serialization logic. In features/marshal/marshal.go, the generator emits methods like MarshalVT() that unroll wire encoding logic at compile time rather than using reflection. Similarly, features/unmarshal/unmarshal.go generates UnmarshalVT() methods that parse protobuf wire formats using only primitive operations and the minimal protowire utilities.
Key architectural benefits include:
- Zero reflection dependencies: The generated code in
generator/helpers.gouses only wire-type constants and primitive sizing logic, neverreflect.TypeOfor similar calls. - Configurable build tags: The generator in
generator/generator.gosupports prepending custom//go:buildconstraints to generated files, allowing you to create TinyGo-specific variants. - Minimal runtime: The
protobuf-go-lite.goruntime file contains only thin utility functions, keeping the final binary size small for embedded targets. - Feature toggles: You can enable only the features you need via the
--featuresflag incmd/protoc-gen-go-lite/main.go, minimizing code size by excluding unnecessary functionality.
Generating TinyGo-Compatible Protobuf Code
To use protobuf-go-lite with TinyGo, you must generate code using specific feature flags that exclude reflection-dependent functionality.
First, install the plugin:
go install github.com/aperturerobotics/protobuf-go-lite/cmd/protoc-gen-go-lite@latest
Next, generate code with only the essential features enabled. The cmd/protoc-gen-go-lite/main.go entry point accepts a --features flag that controls which generators run:
protoc \
--go-lite_out=. \
--go-lite_opt=features=marshal+unmarshal+size \
message.proto
This command generates a .pb.go file containing MarshalVT, UnmarshalVT, and SizeVT methods. The features/size/size.go generator ensures buffer pre-allocation logic is also emitted, optimizing performance without requiring reflection.
Complete TinyGo Example: WebAssembly and Embedded
Below is a complete workflow demonstrating protobuf serialization with TinyGo for both WebAssembly and embedded ARM targets.
Protocol Definition
Create a message.proto file:
syntax = "proto3";
package example;
message SensorReading {
string sensor_id = 1;
int32 value = 2;
bool alert = 3;
}
TinyGo Application
After running the protoc command from the previous section, create a main.go file:
package main
import (
"fmt"
"example"
)
func main() {
// Create a message
reading := &example.SensorReading{
SensorId: "temp_01",
Value: 42,
Alert: true,
}
// Serialize using the generated MarshalVT method
data, err := reading.MarshalVT()
if err != nil {
panic(err)
}
fmt.Printf("Serialized %d bytes\n", len(data))
// Deserialize using the generated UnmarshalVT method
var decoded example.SensorReading
if err := decoded.UnmarshalVT(data); err != nil {
panic(err)
}
fmt.Printf("Decoded: sensor_id=%s value=%d alert=%t\n",
decoded.SensorId, decoded.Value, decoded.Alert)
}
Building for WebAssembly
Compile to WebAssembly using TinyGo's wasm target:
tinygo build -target wasm -o sensor.wasm .
The resulting sensor.wasm file contains the complete protobuf serialization logic without any reflection overhead, making it suitable for browser environments or Wasm edge runtimes.
Building for Embedded Systems
For ARM Cortex-M microcontrollers, specify the appropriate bare-metal target:
tinygo build -target=thumbv6m-none-eabi -o sensor.bin .
The thumbv6m-none-eabi target produces a bare-metal binary for Cortex-M0/M0+ processors. Because protobuf-go-lite generates only static code, the binary size remains minimal—critical for microcontrollers with limited flash memory.
Key Repository Files for TinyGo Integration
Understanding the source structure helps troubleshoot build issues and customize the generator for specific embedded constraints.
-
cmd/protoc-gen-go-lite/main.go– Entry point that parses the--featuresflag and initializes the code generator. Controls which serialization features are emitted. -
generator/generator.go– Core driver that emits the//go:buildheader and orchestrates feature-specific generators. Handles file-level code generation and build tag injection. -
features/marshal/marshal.go– Generates theMarshalVT()method family. Creates static wire-encoding logic without reflection. -
features/unmarshal/unmarshal.go– Generates theUnmarshalVT()method family. Parses protobuf wire format using only primitive operations. -
features/size/size.go– GeneratesSizeVT()for buffer pre-allocation. Optimizes memory allocation during serialization. -
protobuf-go-lite.go– Minimal runtime utilities. Contains only thin helper functions to keep binary size small. -
generator/helpers.go– Wire-type utilities and sizing logic used across all generators. Defines the mapping between protobuf types and wire formats.
Summary
- protobuf-go-lite generates static, reflection-free protobuf serialization code that compiles with TinyGo for WebAssembly and embedded targets.
- Enable only essential features (
marshal+unmarshal+size) via the--go-lite_opt=featuresflag to minimize binary size and eliminate reflection dependencies. - The generated
MarshalVTandUnmarshalVTmethods infeatures/marshal/marshal.goandfeatures/unmarshal/unmarshal.gooperate withoutreflectpackage usage. - TinyGo can compile the generated code to WebAssembly (
-target wasm) or bare-metal embedded targets (-target thumbv6m-none-eabi) without modification. - The minimal runtime in
protobuf-go-lite.goensures small binary sizes critical for microcontrollers and browser Wasm environments.
Frequently Asked Questions
Does protobuf-go-lite support JSON serialization with TinyGo?
Yes, but with caveats. The JSON feature in features/json/message.go generates code that relies on github.com/valyala/fastjson, which TinyGo can import. However, including JSON support increases binary size compared to using only marshal+unmarshal. For the smallest TinyGo binaries, disable JSON and use binary protobuf format exclusively.
Can I use the standard google.golang.org/protobuf library with TinyGo instead?
No. The standard google.golang.org/protobuf library relies heavily on the reflect package for message marshaling and unmarshaling. TinyGo either omits or severely limits reflection support, particularly in WebAssembly and embedded targets, causing compilation failures or runtime panics. Use protobuf-go-lite specifically because it generates static code without reflection.
How do I reduce binary size further for microcontrollers?
Enable only the minimal feature set using --go-lite_opt=features=marshal+unmarshal. Omit the size feature if you can tolerate slightly less efficient buffer allocation, or omit json to avoid pulling in additional dependencies. Additionally, use TinyGo's -opt=z optimization flag for aggressive size reduction: tinygo build -target=thumbv6m-none-eabi -opt=z -o firmware.bin ..
What build tags does protobuf-go-lite support?
The generator supports custom build tags via the --go-lite_opt=buildTag=<tag> flag. In generator/generator.go, this prepends //go:build <tag> to every generated file. This allows you to maintain separate TinyGo-specific generated files alongside standard Go builds, or exclude files that depend on unsupported features in specific environments.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →