Skip to content

Getting Started

This guide walks through adding xript to an application from scratch. By the end you’ll have a working sandboxed expression evaluator that users can safely extend.

The universal runtime uses QuickJS compiled to WebAssembly: it works in browsers, Node.js, Deno, and more.

Terminal window
npm install @xriptjs/runtime

Create a manifest.json describing what your application exposes to scripts. Start with a few safe bindings:

{
"$schema": "https://xript.dev/schema/manifest/v0.6.json",
"xript": "0.6",
"name": "my-app",
"version": "1.0.0",
"bindings": {
"greet": {
"description": "Returns a greeting for the given name.",
"params": [{ "name": "name", "type": "string" }],
"returns": "string"
},
"add": {
"description": "Adds two numbers.",
"params": [
{ "name": "a", "type": "number" },
{ "name": "b", "type": "number" }
],
"returns": "number"
}
}
}

Only xript and name are required. Everything else is optional and layered on as needed.

Each binding in the manifest needs a host-side implementation. These are regular JavaScript functions:

const hostBindings = {
greet: (name) => `Hello, ${name}!`,
add: (a, b) => a + b,
};

Initialize the WASM sandbox, then wire the manifest and bindings together:

import { initXript } from "@xriptjs/runtime";
const xript = await initXript();
const runtime = xript.createRuntime(manifest, {
hostBindings,
console: { log: console.log, warn: console.warn, error: console.error },
});

initXript() loads the QuickJS WASM module once. After that, createRuntime() is synchronous: create as many runtimes as you need.

Now you can safely evaluate user expressions:

runtime.execute('greet("World")'); // { value: "Hello, World!", duration_ms: ... }
runtime.execute('add(2, 3)'); // { value: 5, duration_ms: ... }
runtime.execute('add(1, add(2, 3))'); // { value: 6, duration_ms: ... }

Scripts can compose your bindings with standard JavaScript:

runtime.execute('[1, 2, 3].map(n => add(n, 10))'); // { value: [11, 12, 13], ... }

When you’re done with a runtime, free its WASM resources:

runtime.dispose();

Anything not declared in the manifest is inaccessible:

runtime.execute('process.exit(1)'); // Error: process is not defined
runtime.execute('require("fs")'); // Error: require is not defined
runtime.execute('eval("1 + 1")'); // Error: eval() is not permitted
runtime.execute('fetch("https://x")'); // Error: fetch is not defined

The sandbox guarantees that user scripts cannot escape the boundaries you define.

  • Scaffold a new project with npx xript init. See Init CLI.
  • Add capabilities to gate sensitive operations. See the Capabilities spec.
  • Add namespaces to organize related bindings. See the Manifest spec.
  • Expose extension points by declaring typed slots that mods fill, and broadcast named events the host emits. Bindings are what mods call, slots are what they fill, events are what the host emits. See the Manifest and Mod Manifest specs.
  • Inherit from a base manifest with extends; fill a base’s abstract holes, refine its concrete pieces. See the Manifest spec.
  • Measure moddability with xript score, which rates how much extension surface your host exposes. See Extensibility Score.
  • Drive the toolchain from an agent by running the CLI as an MCP server with xript mcp. See MCP Server.
  • Read the doctrine behind xript’s open-by-default posture with xript guide. See “More extensible, not less”.
  • Generate TypeScript definitions from your manifest with xript typegen. See Type Generator.
  • Generate documentation from your manifest with xript docgen. See Doc Generator.
  • Use the Node.js runtime for file-based workflows and native V8 performance. See Node.js Runtime.
  • Explore the runtime API in depth. See JS/WASM Runtime.
  • Run the full example in the repository: examples/expression-evaluator/.