Install & Initialize
Pick an init style, run npm install, and you are modelling. The first call to any brepjs function requires the WASM kernel to be loaded — the only choice is who triggers the load.
Prerequisites
- Node.js 24+ or a modern browser with WASM support
- TypeScript 5.9+ recommended (for the
usingkeyword and stricter branded types). 5.0+ works withDisposalScopeas the fallback.
Install
npm install brepjs brepjs-opencascadebrepjs is the API. brepjs-opencascade ships the OpenCascade WASM build; you install it as a peer because alternative kernels (occt-wasm, brepkit-wasm) exist and are interchangeable. See Custom Kernels for swapping.
Three init styles
All three give you the same API. They differ only in who calls the kernel init.
brepjs/quick — zero ceremony
import { box, cylinder, shape } from 'brepjs/quick';
const b = box(30, 20, 10); // works immediately
const cyl = cylinder(5, 20);
const part = shape(b).cut(cyl).val;Best for scripts, prototypes, and any ESM environment with top-level await. The brepjs/quick entry runs await opencascade(); initFromOC(oc) at module-load time, then re-exports the full API.
init() — auto-detect
import { init, box, cylinder, shape } from 'brepjs';
await init(); // resolves with 'occt' or 'brepkit'
const b = box(30, 20, 10);
const cyl = cylinder(5, 20);Best for apps where you control startup. init() is idempotent and returns the kernel ID it picked ('occt' if brepjs-opencascade is installed, 'brepkit' if brepkit-wasm is installed, or whichever is available).
initFromOC(oc) — manual
import opencascade from 'brepjs-opencascade';
import { initFromOC, box, cylinder, shape } from 'brepjs';
const oc = await opencascade();
initFromOC(oc);
const b = box(30, 20, 10);Best for apps that need a loading indicator, explicit error handling, or environments without top-level await. initFromOC only needs to be called once per app lifetime.
Bundler notes
Vite
Vite bundles WASM correctly out of the box. The only quirk: if you load WASM from a Worker, you may want to import the file URL explicitly so Vite emits it as an asset — gridfinitylayouttool.com does this:
import singleWasm from 'brepjs-opencascade/src/brepjs_single.wasm?url';See Vite, Next.js, Astro for full bundler patterns.
Next.js, Nuxt, Remix (SSR frameworks)
brepjs requires WASM and cannot run during server-side rendering. Wrap your CAD components in client-only dynamic imports:
import dynamic from 'next/dynamic';
const BrepViewer = dynamic(() => import('./BrepViewer'), { ssr: false });Frameworks and Compatibility cover the framework-specific patterns.
Webpack 5
Set experiments.asyncWebAssembly = true in webpack.config.js. Older webpack 4 is not supported.
Verify the install
A two-line smoke test:
import { box, shape } from 'brepjs/quick';
console.log('Volume:', shape(box(10, 10, 10)).volume()); // 1000If you see Volume: 1000, you are wired up correctly. If you see a kernel-not-initialized error, switch to init() or initFromOC and await it before any shape call.
Browser loading indicator
When using manual init, you can show a loading UI while the kernel downloads:
import opencascade from 'brepjs-opencascade';
import { initFromOC, box, shape } from 'brepjs';
async function initCAD() {
const loader = document.getElementById('loader');
if (loader) loader.textContent = 'Loading CAD kernel...';
try {
const oc = await opencascade();
initFromOC(oc);
loader?.remove();
const b = box(10, 10, 10);
console.log('Initialized! Volume:', shape(b).volume());
} catch (err) {
if (loader) loader.textContent = 'Failed to load CAD kernel: ' + (err as Error).message;
}
}
initCAD();init() and brepjs/quick handle the load themselves; initFromOC is the only path where you can wrap progress UI around the load.
Sub-path imports (smaller autocomplete)
To reduce import noise, import from focused sub-paths:
import { box, fuse, fillet } from 'brepjs/topology';
import { extrude, linearPattern } from 'brepjs/operations';
import { drawRectangle } from 'brepjs/sketching';
import { edgeFinder, faceFinder } from 'brepjs/query';
import { exportSTEP } from 'brepjs/io';
import { measureVolume } from 'brepjs/measurement';
import { ok, isOk, unwrap, type Result } from 'brepjs/core';All sub-paths re-export a subset of the main brepjs entry. You can mix and match.
Next steps
- Your First Solid — the canonical drill-fillet-export workflow
- Cheat Sheet — single-page quick reference
- B-Rep vs Mesh — why brepjs is different from Three.js