Open-source project
justjake/quickjs-emscripten avatar
justjake/quickjs-emscripten

quickjs-emscripten: TypeScript Bindings for Sandboxed QuickJS

Safely execute untrusted Javascript in your Javascript, and execute synchronous code that uses async functions

1,732 stars140 forksTypeScriptNOASSERTION

At a glance

What is it?
quickjs-emscripten gives TypeScript and JavaScript applications a QuickJS runtime compiled to WebAssembly, with explicit context creation, value handling, resource limits, and host-function APIs. It is aimed at embedding untrusted JavaScript and bridging asynchronous host behavior into synchronous guest code.
Who is it for?
Choose quickjs-emscripten when a TypeScript application needs an isolated QuickJS runtime or controlled guest execution with explicit resource management. Do not choose it as a replacement for the host JavaScript environment, a general application runtime, or a turnkey security boundary without reviewing the QuickJS variant, lifecycle rules, and limits.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 92 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

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

Editorial analysis

Embedding QuickJS for untrusted JavaScript

quickjs-emscripten addresses a specific embedding problem: executing JavaScript supplied by another party without giving that code the host application's full JavaScript environment. The README describes the package as bindings for QuickJS, a JavaScript interpreter compiled to WebAssembly. The package is written primarily in TypeScript and can run in browsers, NodeJS, Deno, Bun, Cloudflare Workers, and QuickJS itself through a related package.

The main audience is application developers building plugin systems, expression evaluators, scriptable tools, or other features where the code being evaluated should be separated from the host process. The library supports most of ES2023 according to its README, but it does not present itself as a browser or a complete application runtime. The host still owns integration, data transfer, disposal, and the policy decisions around what guest code can access.

The security language should be read carefully. A separate QuickJS context provides its own globals and built-in classes, and actions in one context do not leak into another context or runtime, with Asyncify identified as an exception. That isolation is useful, but the package does not claim that embedding QuickJS automatically makes every host integration safe. Exposed host functions, module loaders, result conversion, and resource configuration remain part of the security boundary.

Context, runtime, and handle-based execution

The library starts with asynchronous module initialization. `getQuickJS()` returns a promise that resolves when the QuickJS WebAssembly module is ready. A context can then be created with `QuickJS.newContext()`. The context owns an interpreter environment, while a `QuickJSRuntime` represents a JavaScript heap and can contain multiple contexts. Values and functions are exposed to the guest as handles, so the integration follows an explicit ownership model rather than passing ordinary JavaScript objects across the boundary.

The documented basic flow creates a context, creates a string handle, places it on `vm.global`, disposes the original handle, evaluates code, and disposes the result handle. The result is checked for an error before `vm.dump()` reads the value. This design makes cleanup visible in application code. It also means that an integration can leak interpreter memory or retain handles if disposal is omitted.

There are two API levels for execution. `QuickJS.evalCode` provides a top-level convenience API, while `QuickJSContext` provides more direct control over globals, functions, properties, modules, and promises. The convenience API is suitable for a small isolated expression, but a context API is more useful when the guest needs a deliberate set of globals or host callbacks. The library also supports module evaluation. A module result can be a handle to exports or, when top-level await is involved, a promise resolving to those exports.

Resource limits for bounded guest execution

quickjs-emscripten exposes interruption and memory controls at both the evaluation and runtime levels. The documented `QuickJS.evalCode` example uses `shouldInterruptAfterDeadline(Date.now() + 1000)` and sets `memoryLimitBytes` to `1024 * 1024`. The first option places a time-based interruption check around execution, while the second caps the memory available to that evaluation.

At the runtime level, the README documents `setMemoryLimit`, `setMaxStackSize`, and `setInterruptHandler`. Memory and stack limits apply in aggregate to contexts within the runtime. The sample runtime limits memory to `1024 * 640` bytes and stack size to `1024 * 320` bytes, then installs an interrupt handler that counts calls and interrupts after 1024 calls. These controls are concrete mechanisms for reducing the chance that an untrusted computation will consume all available host resources.

They are not a substitute for process-level isolation, however. The README does not document a complete sandboxing policy for host callbacks, filesystem access, network access, or accidental data exposure. It also does not establish how an interrupt interacts with every asynchronous pattern. A developer should therefore treat the limits as interpreter controls inside a carefully designed embedding, not as permission to execute arbitrary guest code without additional host restrictions.

Host functions, promises, and asyncify

The package is more than an evaluator. `QuickJSContext.newFunction` creates a guest-callable host function, and `vm.setProp` can expose it on the global object. The documented `nextId` example keeps native state outside QuickJS. Each guest call increments that native counter and returns a new QuickJS number handle. The result is then read with `vm.getNumber`, which makes the host-to-guest direction explicit.

For asynchronous operations, the README documents promise inspection through `context.getPromiseState` and settlement through `context.resolvePromise`. This provides building blocks for applications that need to model asynchronous work inside a guest context.

The distinct feature is Asyncify, described as executing synchronous code that uses asynchronous functions. This is useful when existing guest code expects a synchronous API but the host must perform asynchronous work. It also introduces a deliberate architectural tradeoff. The README lists Asyncify as an exception to the normal statement that actions from one context do not leak into others, so integrations using this feature need to inspect its actual scope and lifecycle rather than assuming the ordinary context boundary applies unchanged.

A simpler alternative is to choose `QuickJS.evalCode` and keep host integration limited to returned values. The more capable alternative is the `QuickJSContext` path with explicit host functions, promise handling, or Asyncify. The first is easier to reason about. The second supports richer behavior but places more responsibility on the host developer.

Installing quickjs-emscripten and running a first evaluation

The README gives npm and yarn installation commands. The package can then be imported from the `quickjs-emscripten` entrypoint. In a TypeScript or JavaScript project, the first use is to await `getQuickJS`, create a context, expose a value, evaluate code, inspect the result, and dispose every handle and context.

bash
npm install --save quickjs-emscripten

This installs the package and records it in the project's npm dependencies. The README also documents the yarn alternative, but the two commands should not be run unless the project uses that package manager.

typescript
import { getQuickJS } from "quickjs-emscripten"

async function main() {
  const QuickJS = await getQuickJS()
  const vm = QuickJS.newContext()
  const world = vm.newString("world")
  vm.setProp(vm.global, "NAME", world)
  world.dispose()
  const result = vm.evalCode(`"Hello " + NAME + "!"`)
  if (result.error) {
    console.log("Execution failed:", vm.dump(result.error))
    result.error.dispose()
  } else {
    console.log("Success:", vm.dump(result.value))
    result.value.dispose()
  }
  vm.dispose()
}

main()

The README's expected output is a success message containing `Hello world!`. The example also demonstrates why the result must be branched. An error handle and a value handle are different, and the context must still be disposed after execution.

Platform packaging and maintenance considerations

The README documents browser, NodeJS, Deno, Bun, Cloudflare Workers, and QuickJS support. It also describes package-size reduction, WebAssembly loading, quickjs-ng, and browser use without a build step in its packaging documentation. Those features matter because a WebAssembly interpreter is distributed as part of an application, so the chosen loading strategy and target environment can affect integration work.

The repository is not archived. Its last push was on 2026-07-23, and the repository has no GitHub releases. That means the project is receiving repository changes, but there is no release history in the GitHub releases feature to use as an upgrade record. Upgrade planning should account for the package version and changelog rather than assuming that each repository push corresponds to a published release.

The repository metadata declares the root package as MIT, while the repository description classifies the repository license as a custom licence that GitHub cannot classify and points to the `LICENSE` file. The README excerpt does not reproduce the full license terms. Consumers should inspect `LICENSE` and the license metadata for the exact package being distributed before making compliance decisions. The repository also contains a `CHANGELOG.md`, but the excerpt does not provide upgrade details.

Editorial conclusion

Choose quickjs-emscripten when a TypeScript application needs an isolated QuickJS runtime or controlled guest execution with explicit resource management. Do not choose it as a replacement for the host JavaScript environment, a general application runtime, or a turnkey security boundary without reviewing the QuickJS variant, lifecycle rules, and limits. Verify the intended module loader, platform packaging, memory behavior, asyncify requirements, and the published package license first.

Frequently asked questions

How do I install quickjs-emscripten and run JavaScript in a TypeScript project?

Run `npm install --save quickjs-emscripten`, import `getQuickJS` from the package, await it, create a context with `QuickJS.newContext()`, evaluate code, inspect the result, and dispose the handles and context. The README provides a complete example.

Can quickjs-emscripten limit CPU execution and memory usage?

Yes. The documented API includes `shouldInterruptAfterDeadline`, `memoryLimitBytes`, `runtime.setMemoryLimit`, `runtime.setMaxStackSize`, and `runtime.setInterruptHandler`. These controls are available for bounding evaluation and runtime resource use.

How does quickjs-emscripten expose asynchronous host behavior to QuickJS?

The README documents promise state inspection with `context.getPromiseState`, settlement through `context.resolvePromise`, and Asyncify for synchronous code that uses asynchronous functions. Asyncify is specifically noted as an exception to the normal separation between contexts.

Official sources

  1. Issues
  2. justjake/quickjs-emscripten on GitHub
  3. Project website
  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/justjake-quickjs-emscripten.svg)](https://hysenlabs.com/projects/justjake-quickjs-emscripten)

Was this useful?