A TypeScript chess library: PGN parser, move generator, position model, and a navigable game player. Zero runtime dependencies.
Live demo: bmarini.github.io/jchess
- PGN parser — handles annotations, NAGs, recursive variations (RAV), and
[%clk]/[%eval]metadata - Position model — FEN parsing, move application, en passant, castling, promotion, check detection
- SAN ↔ coordinates —
toSAN()for board moves,parseSAN()for SAN strings, full disambiguation - Legal move generation —
legalMovesFrom(), pin detection, checkmate/stalemate - Game navigation —
GamePlayerwith forward/backward/jump, variation entry/exit, makeMove - PGN export — round-trip serialization with annotations, variations, and metadata
- Strict types —
Squareis a template literal of all 64 valid squares; nothing slips past the type checker - Zero dependencies — pure TypeScript, ~38kB compressed
npm install jchessimport { Position, parsePGN, buildTransitions, GamePlayer } from 'jchess'
// Parse a PGN
const game = parsePGN(`
[White "Fischer"]
[Black "Spassky"]
1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 *
`)
// Build a navigable player
const result = buildTransitions(game, Position.starting())
const player = new GamePlayer(result)
// Navigate
player.stepForward() // play e4
player.stepForward() // play e5
console.log(player.currentSAN) // 'e5'
console.log(player.positionAt(2).toFEN())import { Position } from 'jchess'
const pos = Position.starting()
const after = pos.applyMove('e4')
console.log(after?.position.get('e4')) // { color: 'w', type: 'P', id: ... }import { Position } from 'jchess/board'
import { hasAnyLegalMove } from 'jchess/movegen'
// Fool's mate position (black to move, white just played Qh5#)
const pos = Position.fromFEN('rnb1kbnr/pppp1Qpp/8/4p3/4P3/8/PPPP1PPP/RNB1KBNR b KQkq - 0 2')
console.log(pos.isInCheck()) // true
console.log(hasAnyLegalMove(pos)) // false → checkmateimport { Position } from 'jchess/board'
import { toSAN } from 'jchess/movegen'
const pos = Position.starting()
console.log(toSAN(pos, 'g1', 'f3')) // 'Nf3'
console.log(toSAN(pos, 'e2', 'e4')) // 'e4'import { parsePGN, buildTransitions, exportPGN, Position } from 'jchess'
const game = parsePGN('1. e4 e5 2. Nf3 Nc6 *')
const { transitions } = buildTransitions(game, Position.starting())
console.log(exportPGN(game.headers, transitions, game.preAnnotation))The package is organized into focused modules. Import from the barrel for everything, or from subpaths for clarity:
// Barrel — everything
import { Position, parsePGN, GamePlayer } from 'jchess'
// Subpaths
import { Position, STARTING_FEN, opponent } from 'jchess/board'
import { toSAN, legalMovesFrom, hasAnyLegalMove } from 'jchess/movegen'
import { parsePGN, tokenize } from 'jchess/pgn'
import { exportPGN } from 'jchess/export'
import { buildTransitions, GamePlayer, Line } from 'jchess/transitions'
import { parseAnnotation, serializeAnnotation, extractNAG } from 'jchess/annotation'
import type { Square, Color, PieceType, Piece, Board, ParsedGame, Transition } from 'jchess/types'| Module | Exports |
|---|---|
jchess/board |
Position, STARTING_FEN, squareToCoord, coordToSquare, boardGet, boardSet, parseSAN, findMoveSource, opponent, toSquare |
jchess/movegen |
toSAN, legalMovesFrom, hasAnyLegalMove |
jchess/pgn |
parsePGN, tokenize |
jchess/export |
exportPGN |
jchess/transitions |
buildTransitions, GamePlayer, Line, buildSingleTransition |
jchess/annotation |
parseAnnotation, serializeAnnotation, extractNAG, hasDisplayText |
jchess/types |
Square, Color, PieceType, Piece, Board, CastlingRights, Transition, ParsedGame, ParsedMove, MoveMetadata |
The package also bundles a vanilla DOM-based viewer (ChessViewer) under a separate subpath. React/Vue consumers don't need it; tree-shaking keeps it out of your bundle unless you import it explicitly.
import { ChessViewer } from 'jchess/viewer'
const viewer = new ChessViewer(document.getElementById('board')!, {
pgn: '1. e4 e5 2. Nf3 *',
})
viewer.next()types.ts ← shared types (Square, Piece, Board, etc.)
↑
board.ts ← Layer 1: Position class, FEN, applyMove, isInCheck
↑
movegen.ts ← Layer 2: toSAN, legalMovesFrom, hasAnyLegalMove
↑
transitions.ts ← Layer 3: buildTransitions, GamePlayer, Line
pgn.ts, export.ts, and annotation.ts are independent modules that work alongside the layers.
Position owns all move logic and is immutable — applyMove returns a new Position. This makes testing straightforward and lets the transition list pre-compute every position in a game without worrying about shared state.
When a PGN is loaded, buildTransitions walks every move and records { forward, backward } command lists per half-move. Single-step navigation replays these in O(1). GamePlayer manages a stack of Line instances — the main line plus any variations the user has entered.
Each Piece has a stable numeric id. This lets a renderer track DOM elements across captures, promotions, and variation switches without re-rendering the whole board. Transitions carry piece IDs, so the renderer always knows which element to move/remove/add.
PGN commands like [%clk 0:30:00] and [%eval +1.5] are parsed into structured MoveMetadata separately from display text. The exporter re-embeds them for round-trip fidelity.
board[0]= rank 8 (black's back rank)board[7]= rank 1 (white's back rank)board[row][col], col 0 = a-file, col 7 = h-filesquareToCoord('e4')→[4, 4]coordToSquare(4, 4)→'e4'
Square is a template literal type — 'a1' | 'a2' | ... | 'h8'. Constructing a square from string concatenation requires toSquare(file, rank) or an explicit cast.
jchess ships with .d.ts files generated from source. Strict mode and noUncheckedIndexedAccess are enabled — every public API is fully typed.
The repo also contains a Next.js demo app (web/) that consumes the library directly via path aliases. To work on jchess locally:
git clone https://github.com/benmarini/jchess
cd jchess
npm install
npm test # run all 217 tests
npm run build # build to dist/
# Run the demo
cd web
npm install
npm run dev # http://localhost:3000The demo includes Stockfish WASM analysis, opening detection, full-game review, bot play, and PGN sharing — see the live site for what's possible on top of jchess.
MIT — see LICENSE.
The bundled demo additionally uses Stockfish (GPL-3.0), the Lichess chess-openings database (CC0), and Phosphor icons (MIT). These are not part of the npm package.