Checkmate in the Browser with Stockfish.js
There are plenty of chess libraries out there for move validation and board rendering. But what if you want the actual brains of a 3500+ Elo engine running right inside a browser tab? That is exactly what Stockfish.js delivers. It is a WebAssembly port of the legendary Stockfish engine, compiled via Emscripten, and it powers the analysis board on Chess.com. Whether you are building an analysis tool, a training app, a puzzle generator, or just a really tough bot opponent, stockfish gives you grandmaster-level computation with zero backend required.
The Engine Under the Hood
Stockfish.js is not a simplified chess AI written in JavaScript. It is the real Stockfish engine, the same one that has dominated computer chess for over a decade, cross-compiled to run in browsers and Node.js. The current version ships Stockfish 18 with full NNUE (neural network) evaluation, meaning it plays at superhuman strength right out of the box.
The library communicates through the UCI (Universal Chess Interface) protocol, the same text-based interface used by every serious desktop chess engine. If you have ever connected Stockfish to a GUI like Arena or CuteChess, the commands will feel immediately familiar.
Key highlights worth knowing about:
- Five engine variants ranging from a full-strength 100MB+ build to a lightweight 7MB version for mobile
- Multi-threaded support via SharedArrayBuffer for maximum performance
- Single-threaded variants that work without special CORS headers
- An ASM.js fallback for legacy browser support
- Zero runtime dependencies
- CLI tool included when installed globally
- GPL-3.0 license
Getting It on the Board
Install from npm:
npm install stockfish
Or with yarn:
yarn add stockfish
The package includes a postinstall script that handles the necessary setup for the WASM binaries.
Opening Moves: Browser Setup
Spinning Up a Web Worker
Running a chess engine on the main thread is a terrible idea. It will freeze your UI faster than a mouse slip blunders your queen. Always run Stockfish in a Web Worker.
const wasmSupported = typeof WebAssembly === "object";
const engine = new Worker(
wasmSupported ? "stockfish-18.js" : "stockfish-18-asm.js"
);
engine.addEventListener("message", (event: MessageEvent<string>) => {
const line = event.data;
console.log("Engine:", line);
});
engine.postMessage("uci");
engine.postMessage("isready");
The engine responds with a stream of UCI messages. Once you see readyok, it is ready to accept positions and start calculating.
Analyzing a Position
Feed the engine a FEN string and ask it to think:
const fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1";
engine.postMessage(`position fen ${fen}`);
engine.postMessage("go depth 20");
The engine will output info lines with evaluation scores, principal variations, and search depth. When it finishes, it sends a bestmove response.
Parsing the Best Move
engine.addEventListener("message", (event: MessageEvent<string>) => {
const line = event.data;
if (line.startsWith("bestmove")) {
const move = line.split(" ")[1];
console.log("Best move:", move);
}
if (line.startsWith("info") && line.includes("score cp")) {
const cpMatch = line.match(/score cp (-?\d+)/);
if (cpMatch) {
const centipawns = parseInt(cpMatch[1], 10);
const evaluation = centipawns / 100;
console.log("Evaluation:", evaluation);
}
}
});
Moves come in UCI notation like e2e4 or g1f3. Evaluation is in centipawns, where 100 centipawns equals roughly one pawn of advantage.
Deep Calculation: Advanced Patterns
Choosing Your Engine Flavor
Not every use case needs the full 100MB engine. Stockfish.js ships five variants, and picking the right one matters:
type EngineVariant =
| "stockfish-18.js" // Large multi-threaded (strongest, needs CORS)
| "stockfish-18-single.js" // Large single-threaded (strong, no CORS)
| "stockfish-18-lite.js" // Lite multi-threaded (mobile-friendly, needs CORS)
| "stockfish-18-lite-single.js" // Lite single-threaded (mobile-friendly, no CORS)
| "stockfish-18-asm.js"; // ASM.js fallback (slow but universal)
function selectEngine(): EngineVariant {
const hasWasm = typeof WebAssembly === "object";
const hasSharedBuffer = typeof SharedArrayBuffer === "function";
const isMobile = /Android|iPhone|iPad/.test(navigator.userAgent);
if (!hasWasm) return "stockfish-18-asm.js";
if (isMobile && hasSharedBuffer) return "stockfish-18-lite.js";
if (isMobile) return "stockfish-18-lite-single.js";
if (hasSharedBuffer) return "stockfish-18.js";
return "stockfish-18-single.js";
}
const engine = new Worker(selectEngine());
The multi-threaded variants require specific CORS headers on your server:
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
Without these headers, SharedArrayBuffer is not available and the multi-threaded builds will fail to load. The single-threaded variants skip this requirement entirely.
Configuring Engine Options
Stockfish accepts UCI options to tune its behavior:
function configureEngine(engine: Worker, threads: number, hashMb: number) {
engine.postMessage("uci");
engine.postMessage(`setoption name Threads value ${threads}`);
engine.postMessage(`setoption name Hash value ${hashMb}`);
engine.postMessage("isready");
}
configureEngine(engine, 4, 256);
The Threads option only works with multi-threaded builds. The Hash option controls the transposition table size in megabytes, which affects how well the engine remembers previously evaluated positions.
Building a Move-by-Move Analyzer
A common pattern is analyzing an entire game move by move:
interface AnalysisResult {
fen: string;
bestMove: string;
evaluation: number;
depth: number;
}
function analyzeGame(
engine: Worker,
moves: string[],
depth: number
): Promise<AnalysisResult[]> {
return new Promise((resolve) => {
const results: AnalysisResult[] = [];
let currentMoveIndex = 0;
let currentEval = 0;
let currentDepth = 0;
engine.addEventListener("message", (event: MessageEvent<string>) => {
const line = event.data;
if (line.startsWith("info") && line.includes("score cp")) {
const cpMatch = line.match(/score cp (-?\d+)/);
const depthMatch = line.match(/depth (\d+)/);
if (cpMatch) currentEval = parseInt(cpMatch[1], 10) / 100;
if (depthMatch) currentDepth = parseInt(depthMatch[1], 10);
}
if (line.startsWith("bestmove")) {
const bestMove = line.split(" ")[1];
results.push({
fen: "",
bestMove,
evaluation: currentEval,
depth: currentDepth,
});
currentMoveIndex++;
if (currentMoveIndex >= moves.length) {
resolve(results);
return;
}
const movesUpToNow = moves.slice(0, currentMoveIndex + 1).join(" ");
engine.postMessage(`position startpos moves ${movesUpToNow}`);
engine.postMessage(`go depth ${depth}`);
}
});
engine.postMessage(`position startpos moves ${moves[0]}`);
engine.postMessage(`go depth ${depth}`);
});
}
const gameMoves = ["e2e4", "e7e5", "g1f3", "b8c6", "f1b5"];
analyzeGame(engine, gameMoves, 18).then((results) => {
results.forEach((r, i) => {
console.log(`Move ${i + 1}: best=${r.bestMove}, eval=${r.evaluation}`);
});
});
Node.js Usage
On the server side, the API is slightly different but just as straightforward:
const stockfish = require("stockfish");
const engine = stockfish();
engine.onmessage = (msg: string) => {
if (msg.startsWith("bestmove")) {
console.log("Engine recommends:", msg.split(" ")[1]);
}
};
engine.postMessage("uci");
engine.postMessage("position startpos moves e2e4 e7e5 g1f3 b8c6");
engine.postMessage("go depth 15");
You can also use the CLI by installing globally:
npm install -g stockfish
stockfishjs
This drops you into an interactive UCI session right in the terminal.
The Endgame
Stockfish.js is one of those libraries that feels almost impossible. The strongest chess engine in history, running at full strength inside a browser tab, with zero server-side computation needed. The five-variant approach means you can deploy the full 100MB beast on desktop or slim it down to 7MB for mobile. The UCI protocol keeps everything familiar for anyone who has worked with chess engines before.
The main things to watch out for are the GPL-3.0 license, which has implications for commercial projects, and the CORS header requirement for multi-threaded builds. But if you are building anything chess-related on the web, from analysis boards to training tools to puzzle solvers, stockfish is the engine you want under the hood. Chess.com trusts it for millions of users. Your side project can too.