# risor: a scripting language that only exists inside your Go program

> Risor is an embeddable scripting language for Go applications that compiles expressions to bytecode for a small VM, so a Go service can evaluate user supplied rules without embedding V8 or Python. Its defining constraint is that the environment starts empty, which makes the host application the security boundary.

**deepnoodle-ai/risor** — Risor is a fast, embeddable scripting language for Go applications.  Add dynamic expressions, filters, and user-defined logic to your Go programs without embedding a heavy runtime like V8 or Python. Expressions compile to bytecode and run on a lightweight VM. Pure Go, minimal dependencies.

- Repository: https://github.com/deepnoodle-ai/risor
- Website: https://risor.io
- Stars: 916 · Forks: 40
- Language: Go
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/deepnoodle-ai-risor

## Filling the gap between hardcoded logic and a language runtime

Risor is described as a fast, embeddable scripting language for Go applications, and the README states its purpose plainly: add dynamic expressions, filters and user-defined logic to Go programs without embedding a heavy runtime like V8 or Python. Expressions compile to bytecode and run on a lightweight VM, in pure Go with minimal dependencies.

The framing of the gap is the useful part. The README says Risor fills the gap between hardcoded logic and a full language runtime, and it is for Go developers who need to evaluate user-provided expressions, rules or small scripts safely at runtime.

That middle ground is real. A Go service that hardcodes its validation rules needs a redeployment to change a threshold. A service that embeds a full interpreter inherits that interpreter's footprint, its attack surface and its concurrency model. Risor's proposition is a third option: a language small enough to audit, with no package manager and no third party ecosystem, whose only capability is the one the host chooses to expose.

The use case list makes the intent concrete: workflow engines evaluating conditions at each step, rules engines expressing business rules that change frequently without redeployment, expression evaluation for dynamic filters and validation, string templating with embedded expressions, configuration logic when static YAML is not enough, data transformation in pipelines, and plugin systems where users or tenants write small scripts.

The common pattern the README describes is worth repeating: a compiled Go host handles performance-critical work and exposes an API surface to Risor for flexible, safe, easy-to-change logic. The Go program stays the product; Risor is the extension point.

## Bytecode and a VM instead of an interpreter

The architecture is described in two sentences: expressions compile to bytecode and run on a lightweight VM.

That choice has consequences. A language that parses and walks an AST on every evaluation pays the parsing cost every time, which matters when the expression is evaluated in a hot loop or thousands of times a second. Compiling once to bytecode and running it on a small virtual machine moves that cost to the front, where it can be paid once.

The repository supports that reading. The Makefile has a pprof target that runs the interpreter over examples/scripts/fibonacci.risor and then opens the profile in the browser:

```bash
pprof:
	go build
	./risor --cpu-profile cpu.out ./examples/scripts/fibonacci.risor
	go tool pprof -http=:8080 ./cpu.out
```

There is also a bench target running go test with the benchmem flag over ./tests/benchmarks/go, so the project measures the interpreter rather than assuming it is quick.

The language surface itself is visible in the README's examples. There is an access control predicate, user.role == "admin" || resource.ownerId == user.id, a string template with interpolation, `Hello, ${user.name}! Your order ships ${order.shipDate}.`, optional chaining with a fallback, config?.defaults?.theme ?? "light", and a data transformation, orders.filter(o => o.status == "pending").map(o => o.total).reduce((a, b) => a + b, 0).

That last line is the clearest statement of the design: a familiar collection pipeline syntax over data the host passes in. The language is not trying to be general, it is trying to be small and unsurprising.

## An empty environment is the security model

The Go API section says embedding is straightforward and that by default the environment is empty, secure by default. The standard library is added deliberately with risor.Builtins():

```go
import "github.com/deepnoodle-ai/risor/v2"

env := risor.Builtins()
env["user"] = currentUser
env["resource"] = requestedResource

allowed, err := risor.Eval(ctx,
    `user.role == "admin" || resource.ownerId == user.id`,
    risor.WithEnv(env),
)
```

The shape of that snippet is the whole security argument. The script can see the variables the host placed in the environment and the functions the host chose to expose, and nothing else. Go primitives, slices and maps convert automatically, so the host passes ordinary Go values in and reads ordinary Go values out.

That default matters because the threat model is a script written by somebody else. A user-provided rule that runs with an empty environment cannot read the filesystem, cannot open a socket and cannot call a function the host did not deliberately hand it. Add risor.Builtins() and the host has consciously chosen to widen that boundary.

The corollary is that the extension story lives in Go. The README says extension happens through Go code: you add builtin functions, pass data into the script context and read results back out. That keeps the core small and lets each application tailor Risor to its needs, which is also why there is no plugin marketplace to think about.

## Installing and evaluating your first expression

The package is a Go module, and the module path carries the major version:

```bash
go get github.com/deepnoodle-ai/risor/v2
```

The go.mod file declares go 1.25.0, so the Go toolchain requirement is recent. The dependency list is short: github.com/deepnoodle-ai/wonton at v0.0.37 as the direct dependency, with go-cmp and golang.org/x/sys marked indirect.

There is also a standalone interpreter. The repository builds one with goreleaser and ships it in a small Alpine container image, and the Dockerfile builds cmd/risor with the version, commit and date stamped into the binary via ldflags:

```dockerfile
FROM golang:1.25-alpine AS build

ARG TARGETARCH

ARG RISOR_VERSION="dev"
ARG GIT_REVISION="-"
ARG BUILD_DATE="-"
```

The final stage copies the binary into alpine:3.23, adds ca-certificates and tzdata, and sets the entrypoint to risor.

For development, the Makefile exposes the usual set of targets: test runs gotestsum with a JUnit report and a coverage profile, excluding examples, benchmarks and the vscode directory from the test run, and cover produces an HTML coverage report, format runs gofumpt, and release runs goreleaser.

The repository also contains a risor-lsp command under cmd/, and the vscode/ directory holds a Visual Studio Code extension that the Makefile can package and publish, so there is editor support for the language rather than just a runtime.

## What Risor deliberately does not have

The README has a section called What Risor Isn't, and it is the most persuasive part of the document for an adopter.

Risor is not a general-purpose programming language. It is not trying to replace Python or TypeScript for writing applications. There is no package manager, no module imports and no third-party ecosystem, by design.

That is an unusual thing for a language project to state so plainly, and it removes most of the usual reasons to be sceptical about a small language. A language with no package ecosystem cannot be the supply chain risk that a package ecosystem creates. A language that is not trying to replace Python will not be judged against Python.

The cost is equally plain. If you need a script that pulls a library, reads a file and calls an HTTP API, Risor cannot do it and will not grow to do it. The host application has to expose whatever the script needs, and that exposure is work. For a rules engine evaluating order totals and customer tiers, that is a small list. For a scripting environment where users write arbitrary automation, it is not.

The documentation set matches the narrowed scope: a Language Semantics guide covering type coercion, equality and iteration, an Exception Handling guide covering try, catch, finally and error types, and a v1 to v2 migration guide.

## v2 is a narrowed product, and v1 is still there

The README describes v2 as a major evolution focused entirely on the embedded scripting use case. v1 also served DevOps scripting, cloud tooling and CLI usage; v2 narrows the focus to doing one thing well, giving Go developers a safe, fast way to evaluate user-provided expressions and scripts at runtime.

That is a significant narrowing for anyone who used the earlier version, and the migration guide exists because of it. Risor v1 remains available at tag v1.8.1, so a team that depended on the CLI or cloud tooling side of v1 has a fallback rather than a dead end.

The v2 line also carries the beginnings of a broader host story. The README says implementations for TypeScript and Rust host programs are being prototyped, with good initial results. That is a sentence to watch rather than to rely on: the module is Go only today, and the promise is that the same VM and language can be embedded elsewhere.

The release record shows a v2 line with steady work in it: v2.2.0 on 2026-08-18, v2.1.0 on 2026-03-10 and v2.0.1 on 2026-02-10. The repository is not archived and the last push was on 2026-10-12.

The licence is Apache License 2.0, with the LICENSE file at the top of the tree. That is the permissive grant with an explicit patent clause, which is the sensible choice for a library you intend to embed in a product, and it means an application that vendors Risor keeps no obligation beyond attribution and notice.

## Conclusion

risor suits a Go team that needs to evaluate rules, filters or small scripts supplied by users or tenants without shipping a language runtime, and that wants the evaluated result to be a normal Go value. It does not suit a team that wants a general purpose scripting language with a package ecosystem, since Risor explicitly has none of that, and it does not suit an application that must accept arbitrary code with a large standard library. Before adopting, evaluate one real expression with risor.Eval against risor.Builtins() and confirm the Go value conversions work for your types, then decide how much of the standard library to expose, because that choice is the whole security model.

## FAQ

### What is Risor and what is it for?

Risor is a fast, embeddable scripting language for Go applications. It compiles expressions to bytecode and runs them on a lightweight VM, so a Go program can evaluate user provided expressions, rules, filters or small scripts at runtime without embedding a runtime like V8 or Python.

### How do I install Risor in a Go project?

Run go get github.com/deepnoodle-ai/risor/v2. The module requires Go 1.25.0, and the only direct dependency is github.com/deepnoodle-ai/wonton.

### How do I evaluate a Risor expression from Go?

Build an environment with risor.Builtins(), add the values the script should see as map entries, then call risor.Eval with the context, the expression and risor.WithEnv(env). Go primitives, slices and maps convert automatically, and the result comes back as an ordinary Go value.

### Is Risor a general purpose scripting language?

No. The README states it is not trying to replace Python or TypeScript, and that there is no package manager, no module imports and no third-party ecosystem, by design. Extension happens through Go code, by adding builtin functions and passing data into the script context.

### What is the difference between Risor v1 and v2?

The README says v2 is a major evolution focused entirely on the embedded scripting use case, while v1 also served DevOps scripting, cloud tooling and CLI usage. Risor v1 remains available at tag v1.8.1, and a migration guide is provided.

## Sources

- [deepnoodle-ai/risor on GitHub](https://github.com/deepnoodle-ai/risor)
- [License: Apache-2.0](https://github.com/deepnoodle-ai/risor/blob/main/LICENSE)
- [Project website](https://risor.io)
- [README](https://github.com/deepnoodle-ai/risor/blob/main/README.md)
- [Releases](https://github.com/deepnoodle-ai/risor/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/deepnoodle-ai-risor
