A Go JavaScript runtime powered by V8 with Node.js-compatible APIs.
Use Orbital as a CLI to run scripts, or embed it as a Go library to execute JavaScript with sandboxing, native modules, and CommonJS/ESM support.
On a standard glibc-based Linux or macOS system, no extra setup is needed beyond the binary itself. Linux builds dynamically link system libraries that are already present on most distros (libc, libstdc++, libatomic, etc.).
Orbital uses CGO and links a prebuilt V8 static library. You need:
- Go 1.24+
- A C toolchain for CGO (
clangon macOS,gccon Linux)
You do not need a C++ compiler or the V8 headers: the C++ bridge is shipped pre-compiled (see Linking model), so CGO only compiles a small pure-C shim and links the prebuilt archives.
The V8 static libraries are not committed to the repository. They are published as checksum-verified GitHub Release assets (currently V8
15.0.1240245) and fetched on demand bygo generate into a project-local .v8/ directory. Prebuilt libraries are
available for:
| Platform | Target |
|---|---|
| macOS ARM64 (Apple Silicon) | darwin/arm64 |
| Linux ARM64 | linux/arm64 |
| Linux x86_64 | linux/amd64 |
Intel macOS (
darwin/amd64) is not supported: current V8 requires the macOS 15+ SDK, which ships only on Apple Silicon.
See Installing the V8 runtime for the one-time setup an embedding project needs.
# On your platform (after `make build-native` on macOS/Linux)
./build/orbital script.js
# Evaluate inline code
./build/orbital -e "console.log('Hello!')"
# REPL
./build/orbitalPrebuilt Linux binaries (when available):
./build/orbital-linux-arm64 script.js
./build/orbital-linux-x64 script.jsCreate a full Node.js-compatible runtime with nodejs.New (same module set as the CLI):
package main
import (
"fmt"
"proto.zip/studio/orbital/pkg/nodejs"
"proto.zip/studio/orbital/pkg/runtime"
)
func main() {
inst, err := nodejs.New(runtime.DefaultConfig())
if err != nil {
panic(err)
}
defer inst.Runtime.Dispose()
result, err := inst.Runtime.RunScript(`console.log('Hello from Orbital!')`, "main.js")
if err != nil {
panic(err)
}
fmt.Println(result.String())
}Or register individual modules from pkg/nodejs/<module> when you only need a subset.
Fetch the V8 runtime once, then build with CGO enabled:
go generate ./... # downloads V8 into .v8/ and writes the cgo link file
CGO_ENABLED=1 go build -o myapp .See Installing the V8 runtime for the one-time
go generate wiring, and examples/native/modules, examples/native/esm,
or examples/sandbox-server for fuller examples.
Because the V8 libraries live in the (clearable) Go module cache when you go get
Orbital, they cannot be linked directly from there. Instead, a small setup tool
(cmd/v8setup) downloads the version-pinned, checksum-verified libraries into a
project-local .v8/ directory and writes a per-target cgo file that carries the
-L/-l link flags. Wire it into your project once:
- Add a tiny helper package that runs the setup tool via
go generateand is blank-imported by yourmainso its generated link flags are in the build:
// internal/v8dist/v8dist.go
//go:generate go run proto.zip/studio/orbital/cmd/v8setup -link-out .
package v8dist// main.go
import _ "yourmodule/internal/v8dist"-
Run
go generate ./...before building. It fetches the libraries for yourGOOS/GOARCHinto.v8/<version>/<goos>-<goarch>/and writeszz_generated_v8link_<goos>_<goarch>.gointo the helper package. -
Add
.v8/and**/zz_generated_v8link_*.goto your.gitignore(the libraries and link file are machine/target-specific and regenerated).
go build does not run go generate automatically — run it yourself after
go get, after bumping the Orbital version, or in CI before building.
go generate respects GOOS/GOARCH, so you can install multiple targets side
by side and cross-compile:
GOOS=linux GOARCH=arm64 go generate ./...
GOOS=linux GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc go build -o myapp-linux-arm64 .Each target gets its own .v8/<version>/<goos>-<goarch>/ directory and its own
build-tagged link file, so targets never collide.
.v8/
v1.4.2/ # pinned module version
linux-amd64/lib/*.a
darwin-arm64/lib/*.a
- Custom location: set
V8_HOME=/pathto install into a shared OS user-data directory instead of the project (the generated link file then uses an absolute path; keep it gitignored). - CI caching: cache the
.v8/directory keyed on the Orbital module version to skip re-downloading on every run. - Clearing: delete
.v8/(and re-rungo generate) to force a fresh, re-verified download.
If go build reports cannot find -lv8go_glue or undefined reference errors,
the V8 runtime hasn't been installed for that target. Run the exact command the
tool prints, e.g.:
GOOS=linux GOARCH=arm64 go generate ./...| Package | Purpose |
|---|---|
pkg/v8 |
Low-level V8 bindings (CGO) |
pkg/runtime |
JavaScript runtime, event loop, sandbox interfaces |
pkg/nodejs |
Node.js-compatible runtime constructor (nodejs.New) |
pkg/nodejs/* |
Individual Node.js standard library modules (fs, console, process, etc.) |
cmd/orbital |
CLI binary |
Use nodejs.New for a full Node.js-compatible runtime, or register individual modules from pkg/nodejs/<module> with runtime.RegisterModule().
After go generate has installed the runtime, when you go build:
- CGO compiles only the pure-C boundary (
pkg/v8/v8go.h) with your C compiler.pkg/v8itself carries no-L/-lflags. - The generated
zz_generated_v8link_<goos>_<goarch>.gofile in your helper package supplies the-L${SRCDIR}/…/.v8/<version>/<goos>-<goarch>/libpath and the-lflags. Because it is blank-imported into your build, its flags are aggregated at the final link step, pulling in the static archives:libv8go_glue.a,libv8_monolith.a, andlibv8_libcxx.a. - Standard system libraries are linked dynamically on Linux.
The C++ bridge (pkg/v8/csrc/v8go.cc) is not compiled by CGO. V8 is built
with Chromium's custom libc++ (the std::__Cr:: inline namespace), which is
ABI-incompatible with the system libstdc++ a stock g++ would use. The bridge
is therefore pre-compiled per platform into libv8go_glue.a with V8's own
toolchain (see scripts/build-glue.py) so its std:: symbols match
libv8_monolith.a. This keeps the V8 sandbox enabled while letting consumers
link with a plain C toolchain. Chromium's libc++ ships as a separate
libv8_libcxx.a.
Why not commit the archives? The Go module proxy / go get do not run Git LFS
smudge, so LFS is unusable, and V8's monolith exceeds GitHub's 100MB per-file Git
limit. GitHub Releases, by contrast, allow multi-GB assets — so the libraries
are published there and fetched on demand, and the repository stays small.
# Restrict filesystem to a directory
./build/orbital --root ./sandbox script.js
# Full sandbox (fake system info, blocked network)
./build/orbital -s --root ./sandbox script.js
# Network allow/deny lists
./build/orbital --allow-net=api.example.com script.jsIn library code, pass a runtime.Config with sandboxed implementations:
cfg := &runtime.Config{
Filesystem: runtime.NewLocalFilesystem("/sandbox"),
SystemInfo: runtime.NewSandboxedSystemInfo(nil),
HTTPClient: runtime.NewFilteredHTTPClient(runtime.DenyAllPolicy()),
ProcessSpawner: runtime.NewNoOpProcessSpawner(),
DocumentRoot: "/sandbox",
Timeout: 30 * time.Second,
}
rt, err := runtime.New(cfg)| Flag | Description |
|---|---|
-e, --eval <code> |
Evaluate JavaScript code |
-p, --print <code> |
Evaluate and print result |
-c, --check |
Syntax check without executing |
-i, --interactive |
Start REPL after script/stdin |
-r, --require <module> |
Preload module at startup |
--root <dir> |
Sandbox filesystem to directory |
-s, --sandbox |
Fake system info and block network |
--timeout <duration> |
Execution timeout (e.g. 30s, 5m) |
-N, --allow-net |
Allow all network access |
--allow-net=<hosts> |
Allow specific hosts |
--deny-net |
Deny all network access |
-h, --help |
Show help |
-v, --version |
Show version |
Orbital implements a growing subset of Node.js:
- Modules: CommonJS (
require) and ES Modules (import/export) - Globals:
console, timers,process,EventEmitter,Buffer,URL,TextEncoder/TextDecoder,atob/btoa - Built-ins:
fs,path,stream,url,os,util,crypto,http,worker_threads(isolate-backed),async_hooks, and more
Not yet implemented: cluster, full stream piping, async iterators. async_hooks
context does not yet cross native await boundaries (see docs/async-context.md).
Prebuilt V8 libraries are published as Release assets for macOS (ARM64) and Linux (ARM64/x86_64) — see the table under Requirements.
After go generate, build for your current platform with make build (or
make build-native), or cross-compile with GOOS/GOARCH (see
Cross-compiling). Refreshed V8 libraries are built by CI on
native runners per platform and published to a new Release.
See CONTRIBUTING.md for building V8 from source, testing, and development setup.
MIT