JavaScript script runner in CAD
The JavaScript… entry of the toolbar's workbench dropdown (the last entries of that menu, after the workbenches, are Plugins… and JavaScript…) opens an editor for scripts that inspect the active document and build or edit its feature history. It works in the desktop and browser applications. Each successful run commits its feature edits as one undo step. This page describes the editor's complete supported host API.
Where a script runs
Run uses the same script runner an AI agent's run_script does (see
Driving BREP from an AI agent). On the desktop it is the app's own
server, in a worker process. In the browser it is a Web Worker. Either way:
- Every app tool is a function.
await cad.brep.<group>.<tool>(args)runs it, for examplecad.brep.features.feature_add,cad.brep.scene.model_inspectandcad.brep.document.document_export. The functions are listed and typed indocs/developer/mcp/generated/brep-cad.d.ts. - A run is one undo step. Every edit the run makes, staged or direct, is grouped. A run that throws, hits a limit or is cancelled is rolled back whole.
- Cancel stops the script at once. Its worker is killed wherever it is, any history run it started is cancelled, and its edits are rolled back.
- Switching to another document tab halts the run and rolls it back, because the app's tools act on the active document.
- A module runs as written. If the source has a line starting
importorexport, it is an ES module whoseexport default async function main()returns the result, withimport { brep } from "@brep/cad".
Scripts that use cad.geometry (below) run on the plugin runner instead,
exactly as before. So does every script in a build without the script runner.
Plugin features, actions and annotations always run on the plugin runner, and
their geometry API is unchanged.
Run a script
- Open the document you want to edit, open the workbench dropdown at the left of the toolbar and choose JavaScript….
- Enter JavaScript statements in the source editor, or expand CAD API and examples and press Load box example.
- Press Run script and wait for Completed. in Output. Errors appear there instead; staged edits are committed only after successful validation and history replay.
- Inspect the feature history. Use the application's Undo to remove the whole run, or edit the resulting features normally.
cad.document.addFeature("P.CU", { sizeX: 30, sizeY: 20, sizeZ: 10 });
console.log("Created a box");
ui.notify("Edit its dimensions in the feature history");
return { featuresBefore: cad.document.features.length };
The source is a function body: local variables, loops, helper functions,
await and a top-level return are allowed. You do not need an entry
function or plugin manifest. Each press of Run script executes it again;
the example adds another box each time. The return value is displayed as a
Result: line using JSON.stringify; returning nothing omits that line.
Prefer JSON values for results. A cyclic object or a BigInt cannot be stringified
and fails the run.
Cancel stops the run and rolls back its edits. Closing the run's document reports that it was closed. Run is disabled while the editor is waiting for a run or the active document is busy. Locked or read-only documents refuse edits.
Save and exchange source
Save draft explicitly saves one local editor draft, shared across documents. It is kept in desktop application configuration storage or browser storage, outside model files. Source edits are not automatically saved. Reopening the application loads the saved draft without executing it.
Where file interchange is available, Open .js… loads a UTF-8 script into the
editor and Export .js… exports the current source as script.js. Opening a
script does not run it. Saving a CAD model saves the resulting features, not
the editor source; built-in features replay without the script. Adding an
installed plugin feature still requires that feature's package to be available.
Document and selection snapshots
cad is the context for this run. cad.document and cad.selection contain
snapshots taken when Run is submitted. Staged commands do not update those
snapshots, and changing their nested JavaScript objects does not edit the model.
Only the document methods below stage persistent edits.
| Value | Contents |
|---|---|
cad.document.features | Ordered history rows. Each has type, inputParams, and persistentData; the feature instance ID is inputParams.id. Saved rows may contain additional fields. |
cad.document | The saved history document, including other stored blocks when present, such as expressions, configurator, assembly, wireHarness, pmi, plugins, and partsLibrary. Optional blocks need existence checks. These are data, not live editor APIs. |
cad.selection.solids | Selected solid names in pick order. |
cad.selection.faces | Selected face names in pick order. |
cad.selection.edges | Selected edge names in pick order. |
cad.selection.datums | Selected datum names. |
cad.selection.vertices | Number of selected vertices; vertex coordinates are not exposed here. |
cad.params | An empty object for editor scripts. Named plugin actions have their own parameter forms, but the scratch editor supplies none. |
const rows = cad.document.features;
console.log("History rows:", rows.length);
for (const row of rows) {
console.log(row.inputParams.id, row.type);
}
return { solids: cad.selection.solids, faces: cad.selection.faces };
Feature edit methods
All three methods return undefined, stage commands in call order, and accept
JSON data. The staged commands apply in order after the body returns, each
through the app's own tool (feature_add, feature_set_params,
feature_delete). Up to 256 commands may be staged in one run. Feature IDs and output
names are different: an update targets a history ID such as Cube1, rather
than a face name or a plugin output name such as Ball1:body.
| Method | Behavior |
|---|---|
cad.document.addFeature(type, inputParams = {}, persistentData = {}) | Append a built-in or available plugin feature. Supplied parameters override the feature's defaults. The host generates inputParams.id, overriding any supplied ID. It does not return the new ID. The optional third argument supplies persistent feature data, for example a sketch's saved data. |
cad.document.updateFeature(id, inputParams) | Merge inputParams into an existing feature's parameters (feature_set_params), preserving its instance ID and persistent data. Keys you do not pass keep their values. (On the plugin runner, for scripts that use cad.geometry, it replaces the whole parameter object instead.) |
cad.document.deleteFeature(id) | Remove the matching history row. Dependent features must still replay successfully for the transaction to commit. |
Type IDs identify feature implementations; labels in the interface do not
replace them. Common primitive types are P.CU (box: sizeX, sizeY, sizeZ),
P.S (sphere: radius), and P.CY (cylinder: radius, height). See the
feature documentation for supported inputs and behavior.
The live catalogue and each feature's schema are
await cad.brep.features.feature_catalogue({}) and
await cad.brep.features.feature_schema({ type }).
Add several features
for (const radius of [5, 10, 15]) {
cad.document.addFeature("P.S", { radius });
}
ui.notify("Added three sphere features");
These spheres share the origin. Use the primitive's transform parameter for
placement; cad.geometry handles do not automatically become history features.
Update a feature while preserving its other parameters
const box = cad.document.features.find(row => row.type === "P.CU");
if (!box) throw new Error("Add a box first");
cad.document.updateFeature(box.inputParams.id, {
...box.inputParams,
sizeX: 50
});
return { updated: box.inputParams.id };
Delete features by type
const spheres = cad.document.features.filter(row => row.type === "P.S");
for (const row of spheres) {
cad.document.deleteFeature(row.inputParams.id);
}
return { deleted: spheres.length };
Geometry helpers
cad.geometry exposes Rust-backed BREP operations for temporary geometry and
queries. A script that names it runs on the plugin runner, which provides it,
with that runner's semantics throughout. Handles are opaque objects owned by this invocation. They cannot be
serialized, saved in parameters or persistent data, or reused in a later run.
Creating a handle does not insert a feature or display a solid; use
cad.document.addFeature to change the model. There is no editor method to
publish a temporary handle as a history output.
| Method | Arguments and result |
|---|---|
cad.geometry.sphere({ radius }) | Sphere centered at the origin; returns a handle. |
cad.geometry.cube({ sizeX, sizeY, sizeZ }) | Box starting at the origin; returns a handle. |
cad.geometry.cylinder({ radius, height }) | Cylinder along positive Y; returns a handle. |
cad.geometry.transform(handle, options) | Returns a transformed handle. Optional position, rotationEuler, and scale are arrays of three finite numbers, defaulting to [0,0,0], [0,0,0], and [1,1,1]. Rotation is in degrees. |
cad.geometry.boolean(operation, left, right) | Returns a handle for UNION, SUBTRACT, or INTERSECT (case insensitive). Kernel refusals fail the call. |
cad.geometry.query(handle) | Returns { volume }, the solid's signed volume in cubic model units. |
cad.geometry.reference(name) | Present in the shared geometry API, but editor runs do not supply resolved scene references. It fails here with an unresolved/undeclared reference error, including for names in cad.selection. |
Dimensions and positions use model units. Geometry calls are limited to 256 operations per invocation, including queries. Transform and boolean operations leave the input handles usable.
const block = cad.geometry.cube({ sizeX: 10, sizeY: 20, sizeZ: 30 });
const moved = cad.geometry.transform(block, { position: [5, 0, 0] });
return { volume: cad.geometry.query(moved).volume }; // 6000
This example reports volume and makes no document edits.
Output methods
| Method | Behavior |
|---|---|
console.log(...values) | Capture a line in Output; values are converted to text and joined with spaces. Use JSON.stringify(value) when you want explicit JSON. |
console.info(...values) | Same as console.log. |
console.warn(...values) | Capture a line prefixed with warn: . |
console.error(...values) | Capture a line prefixed with error: ; it does not throw or abort the run. |
ui.notify(message) | Queue String(message) for the completed run's Output. Accepts one message. |
cad.notify(message) | Same notification method as ui.notify. |
These methods return undefined. On success, notification messages are listed
before console lines, followed by any Result: line. On script failure,
captured console lines accompany the error; queued notifications are not a
guaranteed failure log. Use throw new Error("message") to refuse a run.
JavaScript environment and limits
Each run uses a fresh Boa interpreter in its own worker. Ordinary JavaScript
values and built-ins such as arrays, objects, strings, Map, Set, JSON,
and deterministic Math operations are available as supported by Boa.
Variables and globals do not persist between runs.
There is no browser DOM, window, Node.js, filesystem, network/fetch or
timers. The app's tools (cad.brep.*) are the way to the document, the
files and the view. The only importable module is @brep/cad.
The limits are the runner's:
- source, 256 KiB;
- a returned value, 64 KiB;
- console output, 64 KiB;
- wall clock, 15 minutes.
On the desktop, JavaScript CPU time (5 s) and memory (64 MiB) are limited too; a browser enforces the wall clock in their place. The interpreter also limits each loop to 10,000,000 iterations and recursion depth to 512. At a limit the worker is stopped and the run is rolled back.
On the plugin runner (scripts that use cad.geometry), the limits are those of
plugin actions: source 1 MiB, no Promise results, and await and
cad.brep are not available.
If a run fails, check Output for syntax errors, unknown feature types, missing instance IDs, invalid parameters, unsupported Promise results, or failed history replay. For repeatable custom feature callbacks and installed workbench actions, use a plugin package; editor runs save only their resulting document edits.