quickjs package - modernc.org/quickjs - Go Packages

20 min read Original article ↗

Package quickjs is a pure Go embeddable Javascript engine. It supports the ECMA script 14 (ES2023) specification including modules, asynchronous generators, proxies and BigInt.

See also the original C QuickJS library.

Performance

Geomeans of time/op over a set of benchmarks, relative to CCGO, lower number is better. Detailed results available in the testdata/benchmarks directory.

 CCGO: modernc.org/quickjs@v0.18.1
 GOJA: github.com/dop251/goja@v0.0.0-20260311135729-065cd970411c
  QJS: github.com/fastschema/qjs@v0.0.6

	                        CCGO     GOJA     QJS
	-----------------------------------------------
	        darwin/amd64    1.000    1.131    0.891
	        darwin/arm64    1.000    0.951    0.935
	       freebsd/amd64    1.000    1.382    1.071    (qemu)
	       freebsd/arm64    1.000    1.571    0.813    (qemu)
	           linux/386    1.000    1.729   56.635    (qemu)
	         linux/amd64    1.000    1.423    1.139
	           linux/arm    1.000    2.235   86.618
	         linux/arm64    1.000    1.372    1.030
	       linux/loong64    1.000    1.788   75.851
	       linux/ppc64le    1.000    1.368   43.548
	       linux/riscv64    1.000    1.668   60.587
	         linux/s390x    1.000    1.219   45.730    (qemu)
	       windows/amd64    1.000    1.330    1.050
	       windows/arm64    1.000    1.511    1.200
	-----------------------------------------------
	                        CCGO     GOJA     QJS

Notes

Parts of the documentation were copied from the quickjs documentation, see LICENSE-QUICKJS for details.

Supported platforms and architectures

These combinations of GOOS and GOARCH are currently supported

OS      Arch
-------------
darwin  amd64
darwin  arm64
freebsd amd64
freebsd arm64
linux   386
linux   amd64
linux   arm
linux   arm64
linux   loong64
linux   ppc64le
linux   riscv64
linux   s390x
windows amd64
windows arm64

Builders

Builder results are available here.

Change Log

2026-07-27: Upgrade to QuickJS release 2026-06-04. The bytecode format changed, code produced by VM.Compile using an older release will no longer load.

2025-10-10: Upgrade to QuickJS release 2025-09-13.

Multiple concurrent Javascript virtual machines communicating via Go channels.

package main // import "modernc.org/quickjs"

import (
	"fmt"
)

// Multiple concurrent Javascript virtual machines communicating via Go channels.
func main() {
	tx := make(chan string, 1)
	rx := make(chan string, 1)
	client, _ := NewVM()
	defer client.Close()
	registerFuncs(client, tx, rx)
	go func() { // Start the server.
		server, _ := NewVM()
		defer server.Close()
		registerFuncs(server, rx, tx)
		server.Eval("send(receive()+' reply');", EvalGlobal)
	}()
	fmt.Println(client.Eval("send('ping'); receive();", EvalGlobal)) // Ping the server.
}

func registerFuncs(m *VM, tx, rx chan string) {
	m.RegisterFunc("send", func(s string) { tx <- s }, false)
	m.RegisterFunc("receive", func() string { return <-rx }, false)
}
Output:
ping reply <nil>

Eval flags.

Version returns the underlying QuickJS version.

Atom is an unique identifier of, for example, a string value. Atom values are VM-specific.

Error holds structured information from a JavaScript exception. The error returned by VM.Eval, VM.Call and the other eval-family methods is an *Error; recover the fields with errors.As:

var e *quickjs.Error
if errors.As(err, &e) {
	fmt.Println(e.Stack)
}

Its Error method reports the same message string the engine has always returned, so existing code comparing the error text keeps working.

Only parse/syntax errors carry the FileName, LineNumber and ColumnNumber properties. For runtime errors those fields are zero and the source location is available only from Stack.

func ErrorFromValue(v Value) *Error

ErrorFromValue extracts structured error information from JavaScript error value 'v', typically an Error instance caught from a JS exception.

'v' is borrowed: ErrorFromValue does not free it and the caller remains responsible for its lifetime. Like all VM operations it must be called from the VM's own goroutine. See Error for which fields are populated.

Error implements the error interface. It returns the same string the engine produces for the exception, unchanged from earlier releases.

HostFunc is a Go function exposed to JavaScript by RegisterHostFunc. It receives the call arguments already converted to Go values and returns a single value to convert back to JS, or a non-nil error which is thrown as a JavaScript exception.

ModuleLoaderFunc defines the signature for a custom module loader. It must return the module's source code (Javascript).

type ModuleNormalizeFunc func(vm *VM, moduleBaseName, moduleName string) (string, error)

ModuleNormalizeFunc defines the signature for a custom module normalizer. It receives the base name (the module making the import) and the requested name, and should return the normalized, absolute module name.

Object represents the value of a Javascript object, but not the javascript object instance itself. Do not compare instances of Object.

Into helps you quickly deserialize an Object into a native Go type. This uses encoding/json.Unmarshal internally, so the target type must be compatible with that package.

JSON unmarshalling into native Go struct.

m, _ := NewVM()
defer m.Close()
obj, _ := m.Eval("obj = {a: 42+314, b: 'foo'}; obj;", EvalGlobal)
var dst struct {
	A int    `json:"a"`
	B string `json:"b"`
}
obj.(*Object).Into(&dst)
fmt.Printf("%#v\n", dst)
Output:
struct { A int "json:\"a\""; B string "json:\"b\"" }{A:356, B:"foo"}

MarshalJSON implements encoding/json.Marshaler.

JSON marshalling.

m, _ := NewVM()
defer m.Close()
obj, _ := m.Eval("obj = {a: 42+314, b: 'foo'}; obj;", EvalGlobal)
s, _ := (obj.(*Object).MarshalJSON())
fmt.Printf("%s\n", s)
Output:
{"a":356,"b":"foo"}

String implements fmt.Stringer.

JSON marshalling.

m, _ := NewVM()
defer m.Close()
obj, _ := m.Eval("obj = {a: 42+314, b: 'foo'}; obj;", EvalGlobal)
fmt.Printf("%s\n", obj)
Output:
{"a":356,"b":"foo"}
type PromiseCapability struct {
	Promise Value
	Resolve Value
	Reject  Value
}

PromiseCapability holds a Promise along with its resolve and reject functions, obtained without evaluating JS source.

Dup duplicates the capability — its promise and both resolving functions — returning a new PromiseCapability that must itself be released via Free.

func (pc *PromiseCapability) Free()

Free releases all values held by the capability.

Undefined represents the Javascript value "undefined".

String implements fmt.Stringer.

type Unsupported struct{}

Unsupported represents an unsupported Javascript value.

String implements fmt.Stringer.

VM represents a Javascript context (or Realm). Each VM has its own global objects and system objects.

Note: VM is not safe for concurrent use by multiple goroutines.

NewVM returns a newly created VM.

Call evaluates 'function(args...)' and returns the resulting (value, error).

Argument types must be one of:

Go argument type                        Javascript argument type
----------------------------------------------------------------
nil                                     null
Undefined                               undefined
string                                  string
int*/uint* (value in int32 range)       int
int*/uint* (value out of int32 range)   float
bool                                    bool
float64                                 float64
*math/big.Int                           BigInt
*Object                                 object
Value                                   native Javascript Value
any other type                          object from JSON produced by encoding.json/Marshall(arg)

Call a Javascript function.

m, _ := NewVM()
defer m.Close()
fmt.Println(m.Call("parseInt", "1234"))
Output:
1234 <nil>

Call a Javascript method.

m, _ := NewVM()
defer m.Close()
fmt.Println(m.Call("Math.abs", -1234))
Output:
1234 <nil>

CallValue is like Call but returns (Value, error) like EvalValue

Note: See the Value documentation for details about manual memory management of Value objects.

Close releases the resources held by 'm'.

Compile compiles the bytecode representation of the passed script.

The returned bytecode can be later executed using EvalBytecode or EvalBytecodeValue.

Note: The bytecode format is linked to a given QuickJS version.

Eval evaluates a script or module source in 'javascript'.

Javascript result type  Go result type                          Go result error
-------------------------------------------------------------------------------
exception               nil                                     non-nil
null                    nil                                     nil
undefined               Undefined                               nil
string                  string                                  nil
int                     int                                     nil
bool                    bool                                    nil
float64                 float64                                 nil
BigInt                  *math/big.Int                           nil
object                  *Object                                 nil
any other type          Unsupported                             nil

Getting exception.

m, _ := NewVM()
defer m.Close()
fmt.Println(m.Eval("throw new Error('failed');", EvalGlobal))
Output:
<nil> Error: failed

Evaluate a simple Javascript expression.

m, _ := NewVM()
defer m.Close()
fmt.Println(m.Eval("1+2", EvalGlobal))
Output:
3 <nil>

Object example.

m, _ := NewVM()
defer m.Close()
fmt.Println(m.Eval("obj = {a: 42+314, b: 'foo'}; obj;", EvalGlobal))
Output:
{"a":356,"b":"foo"} <nil>

EvalBytecode is like Eval but uses QuickJS bytecode.

Note: The bytecode format is linked to a given QuickJS version. Moreover, no security check is done before its execution. Hence the bytecode should not be loaded from untrusted sources.

Note: Javascript 'this' is always set to the global context.

func (m *VM) EvalBytecodeValue(bytecode []byte) (r Value, err error)

EvalBytecodeValue is like EvalValue but operates on QuickJS bytecode.

Exceptions thrown during evaluation of the script are returned as Go errors.

If no error is returned, the caller must properly handle the returned Value using Dup/Free.

Note: See the Value documentation for details about manual memory management of Value objects.

EvalThis is like eval but sets javascript 'this' to 'obj'.

func (m *VM) EvalThisValue(obj Value, javascript string, flags int) (r Value, err error)

EvalThisValue is like EvalValue but sets javascript 'this' to obj.

EvalValue evaluates a script or module source in 'javascript' and returns the resulting Value, or an error, if any.

Exceptions thrown during evaluation of the script are returned as Go errors.

If no error is returned, the caller must properly handle the returned Value using Dup/Free.

Note: See the Value documentation for details about manual memory management of Value objects.

func (m *VM) ExecutePendingJobs() (n int, err error)

ExecutePendingJobs runs queued Javascript jobs — Promise reactions (the .then/.catch/.finally callbacks) and async/await continuations — until the queue is empty, returning the number of jobs executed. The runtime does not run these itself: an embedding event loop calls ExecutePendingJobs at the end of each turn to drain the microtask queue (so a resolved Promise's .then runs, a pending await resumes, etc.). An exception thrown by a job is returned as an error — the first one — while the remaining jobs still run; a self-feeding job queue is bounded so it cannot hang the host.

func (m *VM) GetProperty(this Value, prop Atom) (r any, err error)

GetProperty returns this.prop.

func (m *VM) GetPropertyValue(this Value, prop Atom) (r Value, err error)

GetPropertyValue returns this.prop.

Note: See the Value documentation for details about manual memory management of Value objects.

func (m *VM) GlobalObject() (r Value)

GlobalObject returns m's global object.

Note: See the Value documentation for details about manual memory management of Value objects.

Interrupt requests 'm' to interrupt Javascript evaluation. Interrupt can be called asynchronously at any time m is evaluating a script.

const timeout = time.Second
m, _ := NewVM()
defer m.Close()

go func() {
	time.Sleep(timeout)
	m.Interrupt()
}()

t0 := time.Now()
r, err := m.Eval(`
function f() {
	var sink;
	for (var i = 0; i < 10000; i++) {
		sink += 42;
		sink -= 42;
	}
}

(function() {
	for (var i = 0; i < 10000; i++) {
		f();
	}
	return 42;
})();
`, EvalGlobal)
d := time.Since(t0)
step := timeout / 5
d = d / step * step
fmt.Println(r, err, d)
Output:
<nil> InternalError: interrupted 1s

NewAtom returns an unique indentifier of 's' or an error, if any.

NewFloat64 returns a new Value from 'n'.

Note: See the Value documentation for details about manual memory management of Value objects.

func (m *VM) NewInt(n int) Value

NewInt returns a new Value from 'n'.

Note: See the Value documentation for details about manual memory management of Value objects.

func (m *VM) NewObjectValue() (r Value, err error)

NewObjectValue returns a new Value representing '{}'.

func (m *VM) NewPromiseCapability() (*PromiseCapability, error)

NewPromiseCapability creates a new JavaScript Promise along with its resolve and reject functions.

The returned PromiseCapability must be freed via Free when no longer needed.

To expose a promise to JS from a Go function registered with RegisterFunc, return pc.Promise from the Go function. The resolve/reject values captured by the closure need no Dup because the C API already holds a reference for each:

m.RegisterFunc("delayed", func(delay int) Value {
    pc, err := m.NewPromiseCapability()
    if err != nil { panic(err) }
    youreventloop.Schedule(func() {
        time.Sleep(time.Duration(delay) * time.Millisecond)
        pc.Resolve.Call(UndefinedValue, "done")
        pc.Resolve.Free()
        pc.Reject.Free()
        // pc.Promise.Free() // <- only if returned pc.Promise.Dup()!
    })
    return pc.Promise
}, false)

NewString returns a new Value from 's'.

Note: See the Value documentation for details about manual memory management of Value objects.

RegisterFunc registers a Go function 'f' and makes it callable from Javascript.

The 'f' argument can be a regular Go function, a closure, a method expression or a method value. All of them are called 'Go function' below.

The Go function can have zero or more parameters. If 'wantThis' is true then the first parameter of the Go function will get the Javascript value of 'this'. Depending on context, 'this' can be Javascript null or undefined.

Go functions with multiple results return them as an Javascript array.

Go nil errors are converted to Javascript null.

Go non-nil errors are converted to Javascript strings using the Error() method.

Any Go <-> Javascript failing type conversion between arguments/return values throws a Javascript type error exception.

There is a limit on the total number of currently registered Go functions.

Note: The 'name' argument should be a valid Javascript identifier. It is not currently enforced but this may change later.

For binding host objects (a DOM, console, fetch, …) prefer RegisterHostFunc, which gives the more convenient single-return / error-throws-exception shape.

Call error returning Go function from Javascript.

m, _ := NewVM()
defer m.Close()
m.RegisterFunc("gofunc", func(a int) error {
	if a < 0 {
		return fmt.Errorf("negative")
	}
	return nil
}, false)
fmt.Println(m.Eval("gofunc(-1)", EvalGlobal))
fmt.Println(m.Eval("gofunc(1)", EvalGlobal))
Output:
negative <nil>
<nil> <nil>

Call multiple return Go function from Javascript.

m, _ := NewVM()
defer m.Close()
m.RegisterFunc("gofunc", func(a, b int) (int, int) { return 10 * a, 100 * b }, false)
fmt.Println(m.Eval("gofunc(2, 3)", EvalGlobal))
Output:
[20,300] <nil>

Call single return Go function from Javascript.

m, _ := NewVM()
defer m.Close()
m.RegisterFunc("gofunc", func(a, b, c int) int { return a + b*c }, false)
fmt.Println(m.Eval("gofunc(2, 3, 5)", EvalGlobal))
Output:
17 <nil>

Passing Javascript 'this' to a Go function.

m, _ := NewVM()
defer m.Close()
m.RegisterFunc("gofunc", func(this any) any { return this }, true)
fmt.Println(m.Eval("var obj = { foo: 314, method: gofunc }; obj.method()", EvalGlobal))
Output:
{"foo":314} <nil>

Passing Javascript 'this' to a Go function.

m, _ := NewVM()
defer m.Close()
m.RegisterFunc("gofunc", func(this any, n int) (any, int) { return this, 10 * n }, true)
fmt.Println(m.Eval("var obj = { foo: 314, method: gofunc }; obj.method(42)", EvalGlobal))
Output:
[{"foo":314},420] <nil>

Passing undefined Javascript 'this' to a Go function.

m, _ := NewVM()
defer m.Close()
m.RegisterFunc("gofunc", func(this any) any { return this }, true)
fmt.Println(m.Eval("gofunc()", EvalGlobal))
Output:
undefined <nil>

Passing undefined Javascript 'this' to a Go function.

m, _ := NewVM()
defer m.Close()
m.RegisterFunc("gofunc", func(this any, n int) (any, int) { return this, 10 * n }, true)
fmt.Println(m.Eval("gofunc(42)", EvalGlobal))
Output:
[null,420] <nil>

Call void Go function from Javascript.

m, _ := NewVM()
defer m.Close()
m.RegisterFunc("gofunc", func(a, b, c int) { fmt.Println(a + b*c) }, false)
fmt.Println(m.Eval("gofunc(2, 3, 5)", EvalGlobal))
Output:
17
undefined <nil>

RegisterHostFunc exposes fn to JavaScript as a global function named name, using the calling convention an embedding host wants: every JS call argument arrives in the single []any slice (already converted to Go values), fn returns exactly one value to convert back to JS, and a non-nil error is thrown as a JavaScript exception.

This differs from RegisterFunc, whose reflection mapping turns a Go multiple-return into a JS array and converts a returned error like any other value (so a (value, error) pair becomes the array [value, error] rather than a value-or-throw). RegisterHostFunc is the better fit for binding host objects; RegisterFunc remains for direct, typed Go calls.

func (m *VM) SetCanBlock(value bool)

SetCanBlock configures m's blocking mode.

func (m *VM) SetDefaultModuleLoader()

SetDefaultModuleLoader will enable loading module using the default module loader.

Enabling the module loader.

m, _ := NewVM()
defer m.Close()
m.SetDefaultModuleLoader()
// testdata/power.js:
//  export const name = "Power";
//
//  export function square(x) {
//  	return x*x;
//  }
//
//  export function cube(x) {
//  	return x*x*x;
//  }
m.Eval("import * as Power from './testdata/power.js'; globalThis.Power = Power;", EvalModule)
fmt.Println(m.Eval("[Power.square(2), Power.cube(2)];", EvalGlobal))
Output:
[4,8] <nil>

SetEvalTimeout sets the timeout for the various eval functions. Passing zero 'd' disables timeouts.

m, _ := NewVM()
defer m.Close()

for _, timeout := range []time.Duration{100 * time.Millisecond, time.Second, 3 * time.Second} {
	m.SetEvalTimeout(timeout)
	t0 := time.Now()
	r, err := m.Eval(`
function f() {
	var sink;
	for (var i = 0; i < 10000; i++) {
		sink += 42;
		sink -= 42;
	}
}

(function() {
	for (var i = 0; i < 10000; i++) {
		f();
	}
	return 42;
})();
`, EvalGlobal)
	d := time.Since(t0)
	min := timeout / 2
	max := timeout * 3 / 2
	fmt.Println(r, err, timeout, d >= min && d <= max)
}
Output:
<nil> InternalError: interrupted 100ms true
<nil> InternalError: interrupted 1s true
<nil> InternalError: interrupted 3s true
func (m *VM) SetGCThreshold(threshold uintptr)

SetGCThreshold sets the memory threshold at which a GC will be performed, in bytes.

func (m *VM) SetMaxStackSize(threshold uintptr)

SetMaxStackSize limits the JS call depth, in libc.TLS stack slots (~one per JS call frame in the CGo-free port). Deep recursion then throws a catchable RangeError instead of overflowing the Go goroutine stack. 0 restores the MaxStackSlots default.

func (m *VM) SetMemoryLimit(limit uintptr)

SetMemoryLimit limits m's maxmimum memory usage, in bytes. Small values of 'limit' are not honored because the VM needs to allocate memory also for the exception object itself. The particular value of "small" is unspecified and subject to change without notice.

func (m *VM) SetModuleLoader(loader ModuleLoaderFunc, normalize ModuleNormalizeFunc)

SetModuleLoader configures custom Go functions to load and normalize Javascript modules. Passing nil for 'loader' removes the custom loader. If 'normalize' is nil, the engine falls back to the default QuickJS module normalizer.

func (m *VM) SetProperty(this Value, prop Atom, val any) (err error)

SetProperty sets this.prop = val.

func (m *VM) SetPropertyValue(this Value, prop Atom, val Value) (err error)

SetPropertyValue sets this.prop = val.

func (m *VM) StdAddHelpers() error

StdAddHelpers adds the 'print' and 'console' global objects to 'm'.

Value represents a native Javascript value. Values are reference counted and their lifetime is managed by an independent Javascript garbage collector. To avoid memory corruption/leaks caused by tripping the Javascript GC, a Value must not

  • be copied. Use the Dup method instead.
  • become unreachable without calling its Free method.
  • be used after its Free() method was called.
  • outlive its VM.

It is recommended to use native Go values instead of Value where possible.

When passing a Value down the call stack use Dup. For example in main

v, _ := EvalValue(someScript)
defer v.Free()
foo(v.Dup()) // Instead of foo(v)

In 'foo' Free must be used. For example

func foo(v Value) {
        defer v.Free()
        ...
}

This ensures the/only topmost Free marks 'v' eligible for garbage collection.

Beware that the correct setup/handling becomes more complicated when using closures, Values are sent through a channel etc. In particular, if a goroutine 1 passes a Dup of 'v' to goroutine 2 and goroutine 1 completes and thus frees 'v' before goroutine 2 completes, the reference counting mechanism will fail. In other words, every Free must be strictly paired with the Dup that preceded obtaining the Value and the Dup/Free calls must respect the original nesting. This is correct.

Dup             // in main
        Dup     // in foo
        Free    // in foo
Free            // in main

This will fail, for example in the above discussed goroutines scenario.

Dup            // in g1
        Dup    // in g2
Free           // in g1
        Free   // in g2

The fix might be in this case to arrange goroutine 1 to wait for goroutine 2 to complete before executing Free in goroutine 1.

See TestMemgrind for an example of how to detect memory leaks caused not only by improper Value use.

Any attemtps to convert 'v' to any using the same rules as there are for the return value of VM.Eval.

Call evaluates 'v(args...)' with 'this' as the receiver and returns the resulting (value, error). 'v' must be a Javascript function. Pass UndefinedValue as 'this' when the function does not use it.

resolve.Call(quickjs.UndefinedValue, "done")
reject.Call(quickjs.UndefinedValue, err.Error())

Argument types are converted using the same rules as in VM.Call and the result is unpacked using the same rules as there are for the return value of VM.Eval. Note that a Go error is not one of the recognized types, so it is marshaled to JSON like any other value — and because the standard error types have no exported fields that yields '{}'. Pass err.Error() instead.

Note: A Value passed to a Go function registered by VM.RegisterFunc is valid only for the duration of that call. To call it later, for example to settle a promise after the Go function has returned, retain it using Value.Dup and release it using Value.Free when done.

Note: See the Value documentation for details about manual memory management of Value objects.

func (v Value) CallValue(this Value, args ...any) (r Value, err error)

CallValue is like Value.Call but returns (Value, error) like VM.EvalValue.

Note: See the Value documentation for details about manual memory management of Value objects.

func (v Value) Dup() Value

Dup returns a copy of 'v' while updating its reference count.

Free marks 'v' as no longer used and updates its reference count. 'v' must not be used afterwards.

func (v Value) GetProperty(this Value, prop Atom) (r any, err error)

GetProperty returns v.prop. The 'this' argument is ignored.

Deprecated: The 'this' argument is vestigial, the property is always looked up on 'v'. Use Value.Property instead.

func (v Value) GetPropertyValue(prop Atom) (r Value, err error)

GetPropertyValue returns v.prop.

func (v Value) IsUndefined() bool

IsUndefined reports whether 'v' represents the Javascript value 'undefined'.

MarshalJSON implements encoding/json.Marshaler.

Property returns v.prop. It is Value.GetProperty without the vestigial 'this' argument.

func (v Value) SetProperty(prop Atom, val any) (err error)

SetProperty sets v.prop = val.

func (v Value) SetPropertyValue(prop Atom, val Value) (err error)

SetPropertyValue sets v.prop = val.

func (v Value) Then(onFulfilled, onRejected func(Value)) (_ Value, err error)

Then attaches fulfillment and rejection handlers to the Promise v, returning the new promise for chaining. Either onFulfilled or onRejected may be nil.

VM returns the VM associated with 'v'.