CLI tool
mattn/anko avatar
mattn/anko

Anko: a scriptable interpreter you can embed in a Go program

Scriptable interpreter written in golang

1,585 stars135 forksGoMIT

At a glance

What is it?
Anko is an MIT licensed scriptable interpreter written in Go, with an environment object for defining values and a virtual machine package for executing scripts, so a Go program can run user or operator supplied code without shelling out. The master branch is explicitly not stable and tagged branches are the recommended way to depend on it.
Who is it for?
Anko is a good pick for a Go service that needs to let operators or users express behaviour in a script without giving them a shell, because the environment object and the vm.Execute entry point are small enough to embed in a few lines and the language already exposes Go-shaped values such as maps, slices and structs; it is the wrong pick if you need a language with a specification and a compatibility promise, since the project states that the master branch language and API may change at any time, and before you depend on it you should pin a tagged branch rather than the master branch, as the project itself advises.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 105 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The embedded case is the primary one

Anko is a scriptable interpreter written in Go, and the README leads with the embedded usage example rather than the command line one. That ordering tells you what the project is for.

The idea is that a Go program can execute scripts without shelling out to another process. You create an environment, define the functions and values the script is allowed to use, and then run a script string against it.

go
package main

import (
	"fmt"
	"log"

	"github.com/mattn/anko/env"
	"github.com/mattn/anko/vm"
)

func main() {
	e := env.NewEnv()

	err := e.Define("println", fmt.Println)
	if err != nil {
		log.Fatalf("Define error: %v\n", err)
	}

	script := `
println("Hello World :)")
`

	_, err = vm.Execute(e, nil, script)
	if err != nil {
		log.Fatalf("Execute error: %v\n", err)
	}

	// output: Hello World :)
}

The design is worth reading closely. The environment is the security and capability boundary. Nothing is available to the script unless the host program puts it there, so the println function used in the example exists only because e.Define("println", fmt.Println) was called. A script that tries to reach something else gets an error at runtime rather than access to the host process.

The vm.Execute call takes the environment, an options value, and the script source, and returns a result plus an error. The error is the mechanism a host program uses to catch a script failure without the script taking the process down with it.

The repository structure follows that split. There is an env/ directory, a vm/ directory, a parser/ and an ast/ for the front end, core/ for the shared types, packages/ for the built-in library, cmd/ for the command line entry point, and misc/ and _example/ around the edges. The GoDoc reference points at the vm package specifically, and the README says more examples are located there.

The language is Go-shaped, including maps and structs

The quick start script shows a language whose data model is borrowed from Go, which is the thing that makes anko scripts easy to write for Go developers.

Variables are assigned without a type keyword, and arithmetic works as you would expect:

code
x = 1
y = x + 1

// print using outside the script defined println function
println(x + y) // 3

Conditionals use the C-family if with braces and the usual operators, and the else if branch shows that the syntax is not a simplified subset:

code
if x < 1 || y < 1 {
	println(x)
} else if x < 1 && y < 1 {
	println(y)
} else {
	println(x + y)
}

Slices are declared with the interface type, and printed the way Go prints them:

code
a = []interface{1, 2, 3}
println(a) // [1 2 3]
println(a[0]) // 1

Maps are the same, and the interesting part is that both the index syntax and the field syntax work on them:

code
a = map[interface]interface{"x": 1}
println(a) // map[x:1]
a.b = 2
a["c"] = 3
println(a["b"]) // 2
println(a.c) // 3

Structs can be constructed with make, and then their fields are assigned and read with the usual dot notation:

code
a = make(struct {
	A int64,
	B float64
})
a.A = 4
a.B = 5.5
println(a.A) // 4
println(a.B) // 5.5

Functions are declared with func, and defer follows Go's last-in first-out ordering, which the comment in the example makes explicit:

code
func b () {
	defer println("world")
	defer println("hello")
	println("start")
}
b() // start, hello, world

So the language is not a Lisp or a Scheme with a Go veneer. It reads as a scripting dialect of Go, with the same slice, map and struct semantics, which means a host program can hand a script a Go value and get a Go value back without conversion.

Running it as a command line tool

The command line route is the quick one to try, and it is two commands to install.

code
go get github.com/mattn/anko
go install github.com/mattn/anko

And then a script file named script.ank is run with the resulting binary:

code
./anko script.ank

The anko.go file at the repository root and the cmd/ directory are the command line side, and the anko_test.go at the root is the test for the top level package.

The .ank extension is the convention for script files, which is a small but useful signal: a file with that extension in a directory of yours is probably meant for this interpreter, and syntax highlighting and file associations can be set up around it.

The go.mod file is minimal, with the module path github.com/mattn/anko and a go directive of 1.13. That is a notably old Go version requirement, and it means the interpreter itself does not need a modern toolchain to build, which is useful if you are compiling it into something that has to work on older environments.

The distinction between the two usage modes matters for evaluation. The command line tool is a convenience for experimenting. The embedded mode is the product. When you install the command line binary you get an interpreter with whatever the packages/ directory provides built in, and the real design decision is what the host program puts into the environment when the script is untrusted or semi-trusted.

Master is not stable, and the project says so

There is a short section in the README that any reader should not skip, and it changes how you should depend on this project.

The master branch is not stable. The language and API may change at any time. To mitigate breaking changes, the project asks you to use tagged branches, and new tagged branches will be created for breaking changes.

That is an unusual and reasonable arrangement, and it is worth understanding what it implies. The default branch of the repository is where the work happens and where things are allowed to move. A tagged branch is a stable point. So when you add anko to a project, you are expected to pin a tag, and when the maintainer makes a breaking change, a new tag appears and your dependency is unaffected until you deliberately move to it.

This is the opposite convention from most Go modules, where the module path carries a major version suffix and breaking changes mean a new module path. Here the module path stays the same and the branch is the version selector. The practical consequence is that a plain go get against master will give you something that may not compile against your code tomorrow.

On the state of the project, the repository is not archived and the last push was on 2026-07-10, so it is still being worked on. There are no GitHub releases, which is consistent with the tagged-branch scheme rather than with abandonment. The badges show CI through GitHub Actions, coverage through codecov, a Go Report Card score, and the project is funded through Open Collective with both individual and organisational contributors.

Where Anko is the wrong choice

The most obvious wrong choice is wanting a scripting language with a specification. Anko has no language specification document, no versioned standard, and an explicit statement that the language and API may change. If your use case involves writing scripts that other people depend on, or that you need to keep working for years, the instability is the deal breaker, and the fact that the recommended mitigation is a tagged branch rather than a versioned spec is the tell.

The second wrong choice is expecting a batteries-included scripting language. The example script has to be told about println before it can print, because the environment is empty until the host program fills it. A script author in an environment where the host defines twenty functions and three data structures is not writing a general-purpose script; they are writing against a specific API that the host program invented. That is fine for an internal operations console and wrong for a general script engine.

The third is performance expectations. This is a tree-walking interpreter written in Go, and the repository carries a Go Report Card badge, which is about static analysis quality rather than speed. If your requirement is running a hot loop over large data structures, a Go plugin, a compiled function invoked over RPC, or just plain Go code behind an interface is likely to be the better design.

The fourth is the sandboxing question. The environment controls which names a script can see, which is a capability model rather than a memory or filesystem sandbox. A script that can call a function you defined can do whatever that function does, including writing files or making network calls, because the host put that power in the script's hands. For untrusted code from the internet, that is not enough isolation, and the project does not claim to offer it.

Editorial conclusion

Anko is a good pick for a Go service that needs to let operators or users express behaviour in a script without giving them a shell, because the environment object and the vm.Execute entry point are small enough to embed in a few lines and the language already exposes Go-shaped values such as maps, slices and structs; it is the wrong pick if you need a language with a specification and a compatibility promise, since the project states that the master branch language and API may change at any time, and before you depend on it you should pin a tagged branch rather than the master branch, as the project itself advises.

Frequently asked questions

How do I embed Anko in a Go program?

Create an environment with env.NewEnv(), define the values and functions the script may use with e.Define, and run the script with vm.Execute(e, nil, script). The import paths are github.com/mattn/anko/env and github.com/mattn/anko/vm, and the execute call returns a result and an error so a host can handle a script failure.

How do I install and run the Anko command line tool?

Run go get github.com/mattn/anko and go install github.com/mattn/anko, then execute a script file with ./anko script.ank. The .ank extension is the convention for anko script files.

What data types does the Anko script language support?

It is Go-shaped. The quick start shows variable assignment, if and else if, slices declared as []interface, maps as map[interface]interface with both index and dot access, structs made with make and assigned with dot notation, functions with func, and defer with Go's last-in first-out ordering.

Is the Anko master branch safe to depend on?

Not according to the project itself. The README states that the master branch is not stable and that the language and API may change at any time, and recommends using tagged branches to mitigate breaking changes, with new tagged branches created for breaking changes.

What licence is Anko released under?

MIT, with the licence file at the repository root. The module path is github.com/mattn/anko and the go.mod requires Go 1.13. The repository is not archived, the last push was on 2026-07-10, and there are no GitHub releases.

Official sources

  1. Issues
  2. License: MIT
  3. mattn/anko on GitHub
  4. README
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/mattn-anko.svg)](https://hysenlabs.com/projects/mattn-anko)

Was this useful?