Recommended Free Tools
To load JavaScript held in a Go string, embed a JavaScript runtime and pass the string to its evaluator. The clearest current example uses Goja: create a runtime with goja.New(), call RunString, check the returned error, and export the resulting JavaScript value to Go.
Run a JavaScript string with Goja
Install Goja in your module, create a runtime, and evaluate the source text. This complete program evaluates an expression and prints its result:
package main
import (
"fmt"
"github.com/dop251/goja"
)
func main() {
vm := goja.New()
value, err := vm.RunString(`2 + 2`)
if err != nil {
panic(err)
}
fmt.Println(value.Export())
}
Initialize the module before compiling:
go mod init example.com/jsstring
go get github.com/dop251/goja
go run .
The output is 4. RunString executes the supplied source in the runtime’s global context and returns both a JavaScript Value and an error (package documentation). Always test the error before reading or exporting the value; it represents both parse failures and exceptions raised while the script runs.
Use a variable containing the source
The source can come from a file, database, HTTP request, or another Go value. A raw string literal is convenient when the script contains quotes or newlines:
#1 Best Overall
package main
import (
"fmt"
"log"
"github.com/dop251/goja"
)
func main() {
source := `
const first = 7;
const second = 5;
first * second;
`
vm := goja.New()
result, err := vm.RunString(source)
if err != nil {
log.Fatalf("JavaScript failed: %v", err)
}
fmt.Printf("JavaScript returned %v (Go type %T)n", result.Export(), result.Export())
}
If the script consists only of declarations and has no final expression, the returned value may be JavaScript’s undefined. Define an explicit result, such as result = ... or a final expression, when the Go caller needs a value.
Convert results into Go values
Simple export
Value.Export() converts a JavaScript value to a normal Go representation, as demonstrated in the Goja README. Numbers, strings, booleans, arrays, objects, and null can therefore be inspected or serialized by Go:
value, err := vm.RunString(`({ ok: true, count: 3, tags: ["go", "js"] })`)
if err != nil {
return err
}
fmt.Printf("%#vn", value.Export())
Export into a specified destination
When you need a particular Go type, Goja also documents ExportTo. This avoids relying on the default representation:
type Response struct {
OK bool `json:"ok"`
Count int `json:"count"`
Tags []string `json:"tags"`
}
value, err := vm.RunString(`({ ok: true, count: 3, tags: ["go", "js"] })`)
if err != nil {
return err
}
var response Response
if err := value.ExportTo(&response); err != nil {
return err
}
fmt.Printf("%+vn", response)
Keep conversion errors separate from evaluation errors: a script can execute successfully while producing a value that cannot be converted to your requested Go type.
Pass Go data into JavaScript
Goja exposes Runtime.Set and Runtime.ToValue for putting Go values into the JavaScript global object:
vm := goja.New()
vm.Set("userName", "Mina")
vm.Set("limit", 4)
value, err := vm.RunString(`userName + " has " + limit + " items"`)
if err != nil {
return err
}
fmt.Println(value.Export())
For explicit conversion, create a JavaScript value with ToValue:
input := map[string]any{"enabled": true, "retries": 2}
vm.Set("config", vm.ToValue(input))
_, err := vm.RunString(`config.enabled && config.retries > 0`)
Set inputs before calling RunString. Treat names added to the global object as part of the script’s interface and document them just as you would function arguments.
Call a function defined by the string
For reusable scripts, define a function, evaluate the source, retrieve the function from the runtime, and use Goja’s goja.AssertFunction as shown in the project README:
package main
import (
"fmt"
"log"
"github.com/dop251/goja"
)
func main() {
vm := goja.New()
_, err := vm.RunString(`
function multiply(a, b) {
return a * b;
}
`)
if err != nil {
log.Fatal(err)
}
callable, ok := goja.AssertFunction(vm.Get("multiply"))
if !ok {
log.Fatal("multiply is not a JavaScript function")
}
result, err := callable(goja.Undefined(), vm.ToValue(6), vm.ToValue(9))
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Export())
}
The first argument is the JavaScript this value; goja.Undefined() is appropriate when the function does not depend on a receiver. Check the boolean from AssertFunction so a missing or overwritten global produces a controlled error instead of a failed type assertion.
Understand syntax and runtime limits
Goja’s README describes it as a pure-Go implementation of ECMAScript 5.1, with most ES6 functionality still in progress (Goja README). It is an embedded language runtime, not a browser or Node.js process. Browser globals such as window, document, and fetch are not supplied automatically, and Node modules are not available unless your application implements equivalents.
Before accepting scripts, identify the exact language features they use. ES5 syntax is the safest baseline; newer syntax should be checked against the Goja version in your module and covered by tests. A parse error means the source is not supported or is malformed, while a runtime error usually means a missing global, bad input, or an exception in the script.
Otto as an alternative
Otto is another embedded Go interpreter. Its documented Run method accepts source text, parses it when needed, and returns a value and error. The basic shape is:
Free tools Windows power users keep installed
One-click scans. No signup required.
package main
import (
"fmt"
"log"
"github.com/robertkrimen/otto"
)
func main() {
vm := otto.New()
value, err := vm.Run(`2 + 2`)
if err != nil {
log.Fatal(err)
}
exported, err := value.Export()
if err != nil {
log.Fatal(err)
}
fmt.Println(exported)
}
Choose between Goja and Otto by checking the JavaScript features you require, the APIs you need for exchanging values and calling functions, dependency and project requirements, and the isolation model your application needs. The available documentation does not establish an apples-to-apples performance ranking or a comprehensive compatibility winner, so benchmark your own workload rather than assuming one.
Errors, limits, and safe operation
Handle both parse and execution errors
Never ignore the error returned by RunString (or Otto’s Run). Log enough context to identify the script version, but avoid logging secrets supplied as globals. Return a stable application error to callers instead of exposing internal source or credentials.
Do not assume an embedded runtime is a security sandbox
The reviewed Goja and Otto documentation does not prove that either interpreter isolates hostile JavaScript. Embedding a runtime does not by itself prevent data access, denial-of-service behavior, or abuse of functions you expose. For untrusted code, define a separate isolation boundary appropriate to your threat model, restrict the values and callbacks you provide, enforce resource and execution limits outside the assumption of language isolation, and review the runtime’s current security guidance before deployment.
Rank #4
Goja documents an interruption mechanism, but an interruption example is not a security guarantee. Treat interruption as a control feature, not proof that arbitrary code is safe.
Common problems and fixes
- “undefined” result: The source only declared variables or functions. Add a final expression or assign an explicit result.
- Syntax error: Log the parser error and test the exact source string. Check unsupported newer syntax against Goja’s ECMAScript 5.1 baseline.
- ReferenceError for a browser or Node name: The embedded runtime does not provide those environments. Pass a deliberately designed value with
Setor implement the required host function. - Conversion failure: The JavaScript shape does not match the Go destination. Inspect
value.Export()first, then adjust the script or use a matchingExportTotype. - Function lookup fails: The script did not define the expected global, or it replaced it with a non-function. Check the
okresult fromAssertFunction. - Unexpected state between runs: A runtime retains globals. Use a fresh runtime for independent evaluations, or deliberately reset and document shared state.
Performance, lifecycle, and repeatability
Create a runtime per isolation or state boundary, not casually for every expression in a hot path. Reusing a runtime can preserve compiled state and globals, but it also makes scripts influence later evaluations. If requests are concurrent, design ownership explicitly and verify the runtime’s concurrency requirements before sharing it between goroutines.
Measure parse time, execution time, allocations, and conversion cost with the scripts and data sizes you actually expect. The cited documentation does not provide a current performance comparison between Goja and Otto, so there is no evidence-based universal winner. Cache only when the cache key includes every input that affects the result and when retaining script state is acceptable.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If your real goal is to capture a rendered page rather than execute JavaScript inside your Go process, ScreenshotNeo provides a one-request website screenshot API and MCP server:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for request options. Before capture it accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and each response identifies its result with X-Page-Verdict and X-Billed headers. Its MCP server supplies take_screenshot, get_page_info, and capture_pdf tools to Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsFrequently asked questions
Can I run JavaScript without a third-party Go package?
Go’s standard library does not provide a JavaScript interpreter. You need an embedded runtime such as Goja or Otto, or an external JavaScript process.
Best Value
Does RunString execute modules or TypeScript?
It evaluates JavaScript source in the runtime context. Module loading and TypeScript transpilation are separate concerns that your application must implement.
Can JavaScript call arbitrary Go functions?
Only functions and values that your host application deliberately exposes should be callable. Define a narrow interface and validate all arguments before performing Go-side effects.
Frequently Asked Questions
Can I run JavaScript without a third-party Go package?
Go’s standard library does not include a JavaScript interpreter; use an embedded runtime such as Goja or Otto, or an external process.
Does RunString execute modules or TypeScript?
It evaluates JavaScript source in the runtime context. Module loading and TypeScript transpilation require additional application tooling.
Can JavaScript call arbitrary Go functions?
Only values and functions your host deliberately exposes are callable, so keep that interface narrow and validate arguments.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

