extrudo

API docs

The Extrudo document API

@extrudo/api builds and changes an Extrudo design from code. A design is JSON (ADR-0003) and every call here is one of the app's own commands on it, so what code writes is exactly what the app opens: the same timeline, the same names, the same parameters.

It is pure TypeScript. No DOM, no WASM, no geometry kernel: it runs in Node, in a worker and in the browser, and computing the solids a design makes is the consumer's business, not this package's.

Getting started

import { Design } from '@extrudo/api';

const d = Design.create({ name: 'Plate' });
const width = d.parameter('width', '80 mm', { customizer: { min: 20, max: 200, step: 5 } });

const s = d.sketch(d.origin.xy, (k) => {
  const plate = k.rectangle([0, 0], [40, 20]);
  k.circle([20, 10], '3 mm');
  k.dimension(plate.bottom, width, { name: 'width' });
});

const solid = d.extrude({ profiles: s.profileAt([5, 5]), distance: '10 mm' });
solid.face('cap:end');

const document = d.toJSON();
const bytes = await d.toFile();

Three calls make a part: a sketch, a solid, a reference to one of its faces. The Wall bracket and benchmark B1 are longer versions of the same thing, and both are tests: the repository runs them and checks the geometry they describe.

Where it comes from

The package lives in this repository (packages/api) and imports @extrudo/core, @extrudo/sketch and @extrudo/storage. There is nothing to install and nothing to publish: a fork adds it with a workspace link, a script or a CLI imports it by name.

import { API_VERSION, Design, type FeatureHandle } from '@extrudo/api';

const version: number = API_VERSION;
const handle: FeatureHandle<'extrude'> = Design.create().extrude();

Determinism and IDs

The same calls on the same starting document give the same JSON, byte for byte. IDs come from a counting factory (f1, f2, … for features, s1 for sketch entities, p1 for parameters), seeded from the document it started with so a new ID never collides with a stored one, and names follow the app's (Extrude1, then Extrude2). Nothing reads a clock unless the caller gives one:

import { Design } from '@extrudo/api';

const options = { now: '2026-10-05T00:00:00.000Z' };
const first = Design.create(options).toJSON();
const second = Design.create(options).toJSON();

console.log(JSON.stringify(first) === JSON.stringify(second)); // true

Any call takes { id } and { name } when its own IDs matter, and options.ids replaces the factory altogether:

import { Design } from '@extrudo/api';

const ids = (() => {
  let next = 1;
  return () => `f${next++}`;
})();
const d = Design.create({ ids: () => ids() });
d.box({ length: '40 mm' });
d.cylinder({ diameter: '20 mm' });

Units and expressions

Every length, angle and plain number an input takes is an expression with a unit (ADR-0004): '10 mm', '2 * tolerance', '90 deg', 3. A number is taken in the input's own unit, and a ParameterHandle stands for its name, so the design follows the parameter:

import { Design } from '@extrudo/api';

const d = Design.create({ units: 'mm' });
const wall = d.parameter('wall', '2.4 mm');
d.box({ length: '40 mm', width: '20 mm', height: wall });

d.setParameter('wall', '3 mm');

A parameter's name is what expressions use, so `${wall}` is 'wall'. A named driving dimension in a sketch becomes a parameter too (ADR-0016): give it { name } and a sketch dimension drives itself with that name.

References

A feature is not much use on its own — what a design is made of is geometry, and geometry is named. Every reference the API builds is one of the persistent names the kernel resolves (ADR-0005), computed without any geometry:

What How
An origin plane, axis or the world origin d.origin.xy, d.origin.z, d.origin.point
A profile a sketch closed s.profileAt([x, y]), s.profiles(), s.profilesInside(points)
A curve or a whole text handle.ref() on what the builder returned
A body a feature made handle.body(), handle.bodies()
A face, an edge, a vertex handle.face(role), handle.edge(faces), handle.vertex(faces)
A construction plane, axis or point offsetPlane.constructionRef()
Anything else d.ref('face', 'f3:side:front')

References has the whole table, the naming grammar and what happens to a name the kernel cannot resolve.

The rest of Design

Call What it does
d.feature(id) A handle for a feature that is already there
d.rename(f, name), d.suppress(f), d.move(f, index), d.remove(f) The timeline, as the app's chip menu does it
d.group(first, last) A run of features under one name (ADR-0065)
d.transaction(label, fn) Several calls as one undo step; a throw rolls it all back
d.configuration(name, values), d.applyConfiguration(name) Named value sets to switch between (ADR-0059)
d.validate() What is wrong with the document, as ApiErrors
d.toJSON(), await d.toFile() The document, and a .extrudo archive
d.state Core's own document state, for what the API does not wrap

A bad call throws ApiError with the input's path and the schema's own words, and changes nothing:

import { Design } from '@extrudo/api';

const d = Design.create();
try {
  d.extrude({ distance: '10 mm', taper: '5 deg' });
} catch (error) {
  console.log(error instanceof Error ? error.message : 'it failed');
}

Every feature type has a method of its own — d.extrude, d.fillet, d.hole, d.thread, d.pattern and the rest — generated from the same registry the app and the kernel use, so a new feature in the app reaches the API and its reference by running one command. They are all listed under Features, with their inputs, their face roles and an example each.

Stability

API_VERSION is 1. The feature methods follow the document's feature inputs, which never break: a stored document migrates instead (ADR-0003). A change that would break a call — an input renamed or retyped — keeps the old form working and maps it, and says so in the changelog.

Links