TinyGo targets an Arduino Uno, a WASI runtime or a CLI from one Go tree
Go compiler for small places. Microcontrollers, WebAssembly (WASM/WASI), and command-line tools. Based on LLVM.
At a glance
- What is it?
- TinyGo is a Go compiler that lowers through LLVM for microcontrollers, WebAssembly and command-line tools, reusing the Go language tools along the way. Its own non-goals are the useful part: it does not promise gc speed, efficiency with thousands of goroutines, or a build for every Go program.
- Who is it for?
- TinyGo earns its place when the target is small: a microcontroller board, or a WASI Preview 1 host that wants a Go function instead of a JavaScript bundle. Skip it for a service built on thousands of goroutines, for code that assumes every standard library package exists, or when you need the speed of the standard gc compiler.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- tinygo-org/tinygo17,912 ★ · +155
| Repository | 2026-09-21 | 2026-11-14 |
|---|---|---|
| tinygo-org/tinygo | 17.8k | 17.9k |
155 Stars in the last 54 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
One Go file, one target string, four very different machines
The embedded example is a `main` that grabs `machine.LED`, configures it with `machine.PinConfig{Mode: machine.PinOutput}`, and toggles it once a second with `time.Sleep`. That single source file compiles and runs on an Arduino Uno, an Adafruit Circuit Playground Express or a Seeed Studio XIAO-ESP32S3. Nothing in the program changes between them. The only thing that changes is the compiler target, and the project claims over 150 supported microcontroller boards.
That portability is real but narrow, and the shape of it matters. What the README promises is a program written against the `machine` package compiling for a board whose API matches. It does not promise that every `machine` API exists on every board, so porting a program that touches a specific peripheral means checking the reference for that target rather than assuming a shared surface. A target string is a promise about one board, not about the class of board it resembles.
The board list is the reason the project exists at all. The reasoning goes back to the observation that if Python runs on microcontrollers, Go should reach even lower level micros, and the target list is the long tail of that bet.
buildmode=c-shared exports a function, and GOOS can replace -target
The WebAssembly path exports a Go function to a host rather than starting a program of its own:
package main
//go:wasmexport add
func add(x, y uint32) uint32 {
return x + y
}Compiling it for any WASI Preview 1 runtime takes an explicit target:
tinygo build -buildmode=c-shared -o add.wasm -target=wasip1 add.goThe same output is available through the standard Go environment variables, which the README notes works the same way as Go 1.24 and later:
GOOS=wasip1 GOARCH=wasm tinygo build -buildmode=c-shared -o add.wasm add.goTwo spellings for one artifact is a small thing until a build script has to work on both. The result is a `.wasm` file with an exported symbol, and the named hosts are Fastly Compute, Fermyon Spin and wazero. wazero is not only a deployment target: `github.com/tetratelabs/wazero` at v1.9.0 is a direct dependency of the compiler itself.
Building the compiler needs submodules, gen-device and an LLVM 22 image
No install command appears in the README. It sends you to the getting started page, which also covers running the compiler from a Docker container. For anyone building the compiler itself, the Dockerfile is more informative than the prose. It expects a prebuilt LLVM base image, passed in as a build argument whose default value is `tinygo-llvm-build`, and that image is itself built from a second file in the repository:
docker build -t tinygo-llvm-build -f Dockerfile.llvm .The alternative is the image CI publishes, referenced as `ghcr.io/tinygo-org/llvm-22`, so the LLVM major version is baked into the image name. The compiler stage then does three things in order:
git submodule update --init
make gen-device -j4
make build/releaseSkipping the middle step is why a fresh checkout produces no device files. The final stage copies `build/release/tinygo` into a `golang:1.27` image, puts `/tinygo/bin` on `PATH` and sets `CMD ["tinygo"]`. Note the two version numbers: `go.mod` requires `go 1.26`, the container ships 1.27, and `llvm-version.txt` sits at the repository root. `flake.nix` covers the Nix path as well.
The non-goals say what gc gives you that TinyGo will not
Most compiler projects hide their limits. This one writes them down under a heading called Non-goals: do not expect to be efficient while using zillions of goroutines, though good goroutine support is a goal; do not expect to be as fast as `gc`, though the project notes LLVM may optimize some things better and that TinyGo might turn out faster for number crunching; and do not expect to compile every Go program out there.
Read as a buyer, those are three disqualifiers. Code whose design leans on thousands of concurrent goroutines is out of scope by the project's own account. Code that needs the speed of the standard `gc` compiler is out of scope. And a codebase that imports broadly across the standard library may hit a package that does not build.
The last one is harder to check in advance than it should be. Instead of listing supported features in the repository, the README links tinygo.org/lang-support, so the authoritative list of what compiles is on a website, and the practical answer to will my program build is to try. Treat a failed build on an unsupported package as expected rather than surprising.
machine.LED is the portability contract, and flash bundles compile with upload
Compiling and uploading are one command, not two:
tinygo flash -target arduino-uno examples/blinky1That verb is why a first run needs no separate build step. The same example directory is referenced by the README as `examples/blinky1`, and the blinky program it points at is the one using `machine.LED`.
For the ESP32 family the flashing path stays inside Go code. `tinygo.org/x/espflasher` and `go.bug.st/serial` are both direct dependencies in `go.mod`. Other direct dependencies describe the rest of the surface: `github.com/blakesmith/ar` for static archives, `github.com/aykevl/go-wasm` for the WebAssembly tooling, `google/shlex` for command line parsing, and `golang.org/x/tools` for the package loading that stands in front of the compiler.
emgo emits C, TinyGo keeps the Go memory model and lowers through LLVM
The project names its own comparison point, and it is worth reading that paragraph closely because it defines the design rather than the feature list. TinyGo is similar to emgo, the README says, with two stated differences. The first is that it wants to keep the Go memory model, which implies garbage collection of some sort. The second is that it uses LLVM internally instead of emitting C, which the README says hopefully leads to smaller and more efficient code and certainly leads to more flexibility.
That is the whole trade in two sentences. Keep the Go memory model and you keep goroutines, channels and a collector on hardware that has to fit them. Emit C instead and you inherit a C toolchain as your backend, which is the shape emgo takes.
MicroPython appears in the project's history as the existence argument rather than the competitor: if Python can run on microcontrollers, then Go should be able to run on even lower level micros. The stated ambition is small binaries where you do not pay for what you do not use, most common boards, WebAssembly, CGo without more overhead than a regular function call, and most of the standard library.
BSD 3-clause over LLVM and PJRC code, on a dev branch ahead of v0.42.0
The license is BSD 3-clause, the same terms as the Go project itself. Two exceptions are spelled out rather than buried: code copied from the LLVM project is under a variant of the Apache 2.0 license, and code copied or ported from Paul Stoffregen's Teensy libraries is under PJRC's license. Both are indicated in the headers of the affected files, which means a redistributed binary carries those notices with it and stripping them is not a neutral act.
The release line is young and the default branch is `dev`, not master. v0.44.0 was published on 2026-10-28, v0.43.0 on 2026-10-09 and v0.42.0 on 2026-09-01, and the last push to the repository landed on 2026-11-02. A clone of the default branch is therefore a development state, not v0.44.0, and a reproducible build wants a tag rather than a branch. The project is still on a 0.x version line, so target names and flags belong to a surface that has not settled.
For questions there is a #TinyGo channel on Gophers Slack, and governance, changelog, contributing guides and an AGENTS.md all live in the repository root.
Editorial conclusion
TinyGo earns its place when the target is small: a microcontroller board, or a WASI Preview 1 host that wants a Go function instead of a JavaScript bundle. Skip it for a service built on thousands of goroutines, for code that assumes every standard library package exists, or when you need the speed of the standard gc compiler. Before you commit, run `tinygo build -target=wasip1` on the real program rather than the sample, since the supported feature list lives at tinygo.org/lang-support and not in the repository, and pin a tag such as v0.44.0 instead of tracking the dev branch.
Frequently asked questions
What is TinyGo?
A Go compiler for small places: microcontrollers, WebAssembly and WASI, and command-line tools. It reuses the libraries used by the Go language tools and adds LLVM on top, which is what makes it a separate compiler rather than a flag on gc.
How do you install TinyGo?
The README gives no install command and points at the getting started instructions, which also cover running the compiler from a Docker container. Building the compiler from source needs a prebuilt LLVM base image, submodules and a device generation step.
How do you use TinyGo?
Choose a target and compile in one step: `tinygo flash -target arduino-uno examples/blinky1` builds and uploads the blinky example, and `tinygo build -buildmode=c-shared -o add.wasm -target=wasip1 add.go` produces a WASI module exporting one function.
TinyGo versus Go on a normal machine
TinyGo reuses the Go language tools and adds LLVM to target small systems with small binaries. The project's non-goals state that it will not be as fast as gc, will not be efficient with zillions of goroutines, and will not compile every Go program.
How does TinyGo handle C compared to a C toolchain?
Good CGo support is a stated goal, with no more overhead than a regular function call, and TinyGo lowers through LLVM instead of emitting C. The project it compares itself to, emgo, is the one that emits C, and the stated difference is that TinyGo keeps the Go memory model.
Does Go use LLVM?
LLVM is TinyGo's own dependency, listed in go.mod as tinygo.org/x/go-llvm. The project describes itself as reusing the libraries used by the Go language tools and adding LLVM, which is what makes it an alternative way to compile Go rather than a configuration of the standard toolchain.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/tinygo-org-tinygo)Star history chart for your README
[](https://hysenlabs.com/projects/tinygo-org-tinygo)Was this useful?