yaegi

An embeddable, pure-Go interpreter that evaluates Go code and plugins at runtime, on top of the standard Go runtime.

Library
Go
vv0.16.1
8,389stars
Apache License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
52/100Fair
Development Activity4
Maintenance44
Community60
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
80/100Excellent
Architecture85
Code Quality80
Innovation78
Learning Curve75

Yaegi (Yet Another Elegant Go Interpreter) is a Go interpreter written entirely in Go, distributed both as an embeddable library and as a standalone command-line REPL. Import github.com/traefik/yaegi/interp, create an interpreter with New(), and evaluate arbitrary Go source with Eval() — the interpreted code runs inside the same process, with full access to compiled Go values and functions passed in via Use(). This makes Yaegi a way to add a real scripting layer to a Go application without embedding a foreign language runtime or shelling out to go build.

The project is best known as the engine behind Traefik’s plugin system, where it lets users write and hot-load middleware in plain Go instead of a sandboxed DSL or WASM module. Beyond that, it’s used for Go-based configuration scripting, REPLs, and dynamic extension points in other tools. Yaegi implements a large surface of the Go specification and re-exports most of the standard library (regenerated per supported Go version under stdlib/), while deliberately keeping unsafe and syscall out of the default symbol set for safer embedding.

What You Get

  • A minimal embedding API — interp.New(), Eval(), and Use() are the only calls most integrations need
  • A command-line yaegi executable with an interactive REPL, plus the ability to run .go files, directories, or entire packages as scripts
  • Near-complete support of the Go language spec, including generics, closures, goroutines, and channels inside interpreted code
  • Pre-generated bindings for most of the Go standard library, versioned per supported Go release under stdlib/
  • A yaegi extract tool that auto-generates the symbol tables needed to expose any third-party Go package to the interpreter
  • Security-conscious defaults: unsafe and syscall are not exported to interpreted code unless explicitly enabled

Common Use Cases

  • Letting end users write and hot-reload Go plugins/middleware inside a running application (Traefik’s plugin system is the canonical example)
  • Building a Go-native scripting or rules layer for configuration, automation, or workflow logic without shipping a separate DSL
  • Running Go snippets or whole packages as executable shell scripts via the yaegi shebang interpreter
  • Powering interactive Go REPLs and playground-style tools for teaching or experimentation
  • Dynamically extending compiled binaries with new behavior at runtime, using compiled code and interpreted code side by side via reflect.Value

Under The Hood

Architecture Yaegi’s pipeline mirrors the front end of the real Go toolchain: source is tokenized and parsed into an AST (interp/ast.go) using the standard go/scanner/go/token packages, then a global type analysis pass (gta.go) and control-flow graph construction (cfg.go) resolve types, scopes, and generics before execution. Rather than compiling to machine code, run.go and the generator machinery in generic.go/op.go build a tree of pre-bound closures (bltn functions) that are threaded together and executed directly, with value.go/type.go bridging interpreted values to reflect.Value so compiled and interpreted code can call each other transparently. This closure-threading design is what lets Use() inject real Go functions and types into interpreted scope without a foreign-function-interface layer.

Tech Stack The module has zero external dependencies — go.mod declares only the Go version (1.21+) and pulls in nothing else, with parsing, reflection, and execution all built on the standard library. The stdlib/ and stdlib/unrestricted packages are large, versioned files (e.g. go1_21_*.go) of generated symbol tables that re-expose the real standard library to interpreted code, kept in sync per supported Go release via the internal/cmd/extract generator (also shipped as the public yaegi extract tool for third-party packages). cmd/yaegi is a small flag-based CLI wrapping the same interp package used for embedding.

Code Quality The test suite is substantial for an interpreter of this scope — dozens of files under interp/ cover language-eval semantics (interp_eval_test.go), generics, channels, and a dedicated interp_consistent_test.go that runs the same programs through both the interpreter and the real Go compiler to catch behavioral drift between the two, which is an unusually strong quality signal for this kind of project. Errors are returned explicitly rather than panicking across the public API, naming is consistent with Go conventions throughout, and GitHub Actions CI runs the suite on each push; there is no linter config checked in beyond go vet-style defaults.

API Design The public surface is deliberately tiny — New(), Eval(), and Use() cover the overwhelming majority of integrations, and the README’s embedded/plugin/CLI examples all fit in a dozen lines each. The main friction point is exposing arbitrary third-party packages to interpreted code, which requires pre-generating symbol tables with yaegi extract rather than working automatically, and full Go modules support inside the interpreted environment remains limited — both called out directly in the project’s own Limitations section rather than left for users to discover.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search