Solve complex models directly in the browser with Google OR-Tools CP-SAT running as multithreaded WebAssembly.
Used in PragmaPlanner.com.
npm install or-tools-wasmimport { CpSat } from 'or-tools-wasm';Currently supported solvers: CP-SAT.
Verified with:
- Vite 7 in browser contexts
- Webpack 5 in browser contexts
- Rollup 4 static browser builds
- Deno runtime solves
- A CP-SAT WebAssembly runtime built with Emscripten pthread support.
- A TypeScript API for solving, validating models, interrupting solves, and reading embedded proto schemas.
- A worker bridge for keeping browser UI threads responsive while solving.
- Generated TypeScript definitions for SAT parameters.
- Demo pages for Magic Square, Sports Scheduling, Steel Mill Slab, and schema inspection.
This flow is verified with Vite, Webpack, and Rollup. The worker script and
WebAssembly files are emitted automatically from the package import, with no
manual copying into public/ or static/ required.
const model = {
name: 'choose_one',
variables: [
{ name: 'x', domain: [0, 1] },
{ name: 'y', domain: [0, 1] },
],
constraints: [
{
name: 'exactly_one',
linear: {
vars: [0, 1],
coeffs: [1, 1],
domain: [1, 1],
},
},
],
objective: {
vars: [0, 1],
coeffs: [1, 2],
},
};
const modelBytes = await CpSat.createModel(model);
const validation = await CpSat.validate(modelBytes);
if (!validation.ok) {
throw new Error(validation.message);
}
const result = await CpSat.solve(modelBytes, {
numSearchWorkers: 1,
});
console.log(result.response);This package uses a threaded WebAssembly runtime. Browser pages that load it must be served with cross-origin isolation enabled:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corpWithout these headers, browsers may block the SharedArrayBuffer APIs required
by Emscripten pthreads, and solving can fail during WebAssembly runtime or
worker startup.
For Vite dev and preview servers, set the headers in vite.config.ts:
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
server: {
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
},
},
preview: {
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
},
},
});For Vite apps, keep or-tools-wasm out of dependency optimization so Vite
handles the worker and WebAssembly URLs through its normal asset pipeline.
protobufjs is CommonJS, so include it in dependency optimization. The worker
runtime also needs ES module worker output:
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
optimizeDeps: {
include: ['protobufjs'],
exclude: ['or-tools-wasm'],
},
worker: {
format: 'es',
},
});For Webpack 5, enable async WebAssembly, emit .wasm files as resources, and
use publicPath: 'auto' so worker and WebAssembly URLs resolve from the emitted
bundle location:
// webpack.config.cjs
const headers = {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
};
module.exports = {
output: {
publicPath: 'auto',
},
experiments: {
asyncWebAssembly: true,
},
module: {
rules: [
{
test: /\.wasm$/,
type: 'asset/resource',
},
],
},
devServer: {
headers,
},
};Rollup core does not bundle module workers or emit new URL(..., import.meta.url) assets by itself. The verified fixture uses Rollup's standard
plugin surface for those features:
// rollup.config.mjs
import { nodeResolve } from '@rollup/plugin-node-resolve';
import OMT from '@surma/rollup-plugin-off-main-thread';
import { importMetaAssets } from '@web/rollup-plugin-import-meta-assets';
function moduleRelativeFileUrls() {
return {
name: 'module-relative-file-urls',
resolveFileUrl({ fileName }) {
return `new URL(${JSON.stringify(fileName)}, import.meta.url).href`;
},
};
}
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'es',
},
plugins: [
nodeResolve({ browser: true }),
moduleRelativeFileUrls(),
OMT(),
importMetaAssets(),
],
};Other modern bundlers may also work if they support module workers and WebAssembly asset emission, but they are not yet officially verified.
The WebAssembly runtime is built with Emscripten pthread support. When the
runtime starts, Emscripten creates a pthread worker pool sized from
navigator.hardwareConcurrency. This pool is separate from CP-SAT's own search
worker setting.
SatParameters.numSearchWorkers controls how many CP-SAT search workers the
solver should use for a solve. It does not change how many Emscripten pthread
workers are created when the WebAssembly runtime is initialized.
By default, CpSat.solve runs through the package's worker bridge. The bridge
loads the CP-SAT runtime in a dedicated JavaScript worker, so the browser's main
thread remains available for rendering, input, progress UI, and cancellation.
If the worker bridge is disabled, solving runs directly on the main thread. The
solver still works, but the GUI can freeze until CP-SAT returns because the
browser cannot repaint or process UI events during the synchronous WebAssembly
call.
npm install
npm run devnpm run dev / npm run start builds the library and launches the Vite dev
server for the demo site.
npm run build
npm run previewnpm run build runs the full pipeline: Emscripten/CMake builds the low-level
CP-SAT WebAssembly runtime, Vite builds the package bundle, and Vite builds the
static demo site.
The Emscripten SDK is tracked as a pinned emsdk git submodule. The build
script initializes that submodule automatically if needed, so a normal clone can
run npm run build directly after npm install. If you prefer to fetch
submodules up front, clone with --recurse-submodules or run
git submodule update --init --recursive.
npm run build:wasmrebuilds thecp_sat_runtimewasm/js bundle via emsdk + CMake.npm run build:libregenerates SAT parameter types, type-checks withtsc, and builds the library bundle withvite.lib.config.ts.npm run build:sitebuilds the demo site withvite.site.config.ts. The site importsor-tools-wasmdirectly, so Vite emits the worker/runtime/wasm assets from the package bundle automatically.npm run buildrunsbuild:wasm,build:lib, andbuild:site.npm run previewserves the already-built Vite site frombuild/javascript/site.npm run cleanremoves the entirebuild/tree.npm run pack:librebuilds the library bundle and writes an npm tarball intobuild/javascript/lib.
javascript/libcontains the TypeScript package API and worker bridge.javascript/sitecontains the demo pages.javascript/cp_sat_api.cccontains the C++ binding layer compiled into WebAssembly.scripts/embed_proto.cmakeembeds CP-SAT proto schemas into the runtime.scripts/generate_sat_parameters_types.mjsgenerates TypeScript SAT parameter definitions.vite.lib.config.tsbuilds the distributable JS package.vite.site.config.tsbuilds the demo site.
- The Magic Square and Sports Scheduling pages let users pick a CP-SAT search worker count; that value becomes
SatParameters.num_search_workers, clamped tomin(navigator.hardwareConcurrency, 8). - Each demo exposes a "Use worker bridge" checkbox. Keep it enabled for interactive use; disabling it runs the solve on the main browser thread and can freeze the GUI until CP-SAT returns.
- Schema Viewer imports the bundled
CpSatAPI automatically; no extra script ordering is required.
The package ships two CP-SAT runtime builds:
cp_sat_runtimeuses Emscripten JSPI support (-sJSPI=2) for browsers that exposeWebAssembly.promising.cp_sat_runtime_asyncifyuses classic Asyncify stack rewriting for browsers without JSPI support.
javascript/lib/cp_sat_module_loader.ts chooses the runtime at startup. If
WebAssembly.promising is available, it imports the JSPI runtime, which is the
more modern and faster path. Otherwise it falls back to the Asyncify runtime.
Both paths expose the same TypeScript API, so application code does not need to
choose one manually. Current browser support for JSPI is tracked at
caniuse.com/wf-wasm-jspi.
This repository vendors Google OR-Tools and adds a JavaScript/WebAssembly packaging layer on top. OR-Tools is Google's open-source suite for solving combinatorial optimization problems, including CP-SAT, linear programming, routing, bin packing, and graph algorithms.
Upstream project:
- Source: github.com/google/or-tools
- Documentation: developers.google.com/optimization
- License: Apache License 2.0
Maintained by Axel Wickman.
This project is licensed under the Apache License 2.0. See LICENSE.