Jesse A. Green / notebook

Tesseract.js Admin & Developer Guide

Generated June 2026 from naptha/tesseract.js at a1ca80d

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

This guide is for engineers integrating Tesseract.js at scale, operators running it in production (especially long-lived Node.js services), and contributors who need to understand the codebase deeply. It covers architecture, configuration, security, operations, the build/release pipeline, a module-by-module internals reference, and improvement opportunities.

For end-user task instructions (how to recognize an image, output formats, recipes), see the User Guide.

Note

This guide documents Tesseract.js v7.0.0 (package tesseract.js, Apache-2.0). It reflects the source at commit a1ca80d.


Table of Contents

  1. Executive Summary
  2. Architecture
  3. Repository Structure
  4. System Requirements
  5. Configuration Reference
  6. Module Reference
  7. Recognition Pipeline Internals
  8. Caching Subsystem
  9. Security Analysis
  10. Operations
  11. Build & Release Pipeline
  12. Testing Strategy
  13. Code Quality & Conventions
  14. Version Migration
  15. Potential Improvements
  16. Quick Reference

Executive Summary

Tesseract.js is a wrapper library, not an OCR engine. It brings the C++ Tesseract OCR engine to JavaScript by orchestrating a WebAssembly build of that engine (tesseract.js-core). The library's own code is small (~72 KB of source across ~52 code files); its job is transport, environment abstraction, resource management, and a clean async API — not recognition logic.

Key architectural facts an operator should internalize:

  • Two-thread model. Every "worker" runs the heavy engine off the main thread — a Web Worker in browsers, a worker_threads Worker in Node.js. All communication is message-passing.
  • Runtime asset loading. The WASM core and language .traineddata are downloaded from a CDN (jsDelivr by default) at runtime, then cached (IndexedDB in browsers, filesystem in Node.js). Nothing is bundled.
  • Build selection by capability. At load time the worker detects SIMD / relaxed-SIMD support and whether the Legacy model is needed, then loads the matching core build for best performance.
  • Memory is the operational risk. WASM memory only grows; workers accumulate dictionary state. Long-running services must recycle workers.
Property Value
Package tesseract.js
Version 7.0.0
License Apache-2.0
Module type CommonJS ("type": "commonjs")
Main entry src/index.js
Type definitions src/index.d.ts
CDN entry (UMD) dist/tesseract.min.js
Runtime dependencies 8 (see Dependencies)
Engine tesseract.js-core (separate WASM project)

Architecture

Tesseract.js cleanly separates a main-thread API layer from a worker-thread execution layer, with environment adapters that swap browser vs. Node.js implementations behind identical interfaces.

┌──────────────────────────── MAIN THREAD ────────────────────────────┐
│  src/index.js  (public exports)                                      │
│    ├── createWorker  ── src/createWorker.js                          │
│    │      └── builds worker handle, manages promises, paths, OEM     │
│    ├── createScheduler ─ src/createScheduler.js                      │
│    │      └── worker pool + job queue (round-robin to idle workers)  │
│    ├── Tesseract.recognize/detect (deprecated one-shots)            │
│    ├── OEM / PSM / languages constants                               │
│    └── createJob, utils/{getId,log,resolvePaths,getEnvironment}     │
│                                                                      │
│  src/worker/{browser,node}/  ── environment ADAPTER (main side)      │
│    spawnWorker · terminateWorker · send · onMessage · loadImage      │
└───────────────────────────────┬──────────────────────────────────┘
                                 │  postMessage(packet) / onmessage
                                 ▼
┌────────────────────────── WORKER THREAD ────────────────────────────┐
│  src/worker-script/index.js  (action dispatcher)                     │
│    handlers: load · FS · loadLanguage · initialize · setParameters  │
│              · recognize · detect · terminate                        │
│    setAdapter({ getCore, gunzip, fetch, readCache/writeCache/... })  │
│                                                                      │
│  src/worker-script/{browser,node}/  ── environment ADAPTER (worker)  │
│    getCore (SIMD-aware WASM loader) · gunzip · cache (IndexedDB|fs)  │
│  src/worker-script/utils/  setImage · dump · arrayBufferToBase64     │
│                                 │                                    │
│                                 ▼                                    │
│              tesseract.js-core  (WebAssembly Tesseract engine)        │
└──────────────────────────────────────────────────────────────────┘

Design patterns

Adapter pattern (the central idea). The same worker-script core (src/worker-script/index.js) runs in both browser and Node.js. It calls setAdapter(...) to receive environment-specific implementations of getCore, gunzip, fetch, and the cache methods. The browser adapter (src/worker-script/browser/) uses importScripts, zlibjs, and idb-keyval; the Node adapter (src/worker-script/node/) uses require, zlib, and fs. The recognition logic is written once.

Symmetric main-side adapter. Likewise, src/worker/browser/ and src/worker/node/ expose identical spawnWorker, terminateWorker, send, onMessage, loadImage modules. createWorker imports from ./worker/node and relies on the browser field in package.json to swap to the browser build at bundle time.

Job/promise correlation. Each call from the main thread creates a job (createJob) with an id and an action. createWorker stores {resolve, reject} keyed by `${action}-${jobId}` and resolves it when a matching message returns. This lets multiple in-flight jobs coexist over a single message channel.

Command dispatch. The worker script maps each packet.action to a handler function in a lookup object ({ load, FS, loadLanguage, initialize, setParameters, recognize, detect, terminate }[packet.action]).

Note

The browser field in package.json ("./src/worker/node/index.js": "./src/worker/browser/index.js") is what makes a single require('./worker/node') resolve to the browser implementation when bundled for the web. This is the seam that makes the dual-environment design work without conditional requires in the hot path.


Repository Structure

tesseract.js/
├── src/                          Core library (the documented surface)
│   ├── index.js                  Public exports
│   ├── index.d.ts                TypeScript definitions (full type surface)
│   ├── Tesseract.js              Deprecated one-shot recognize()/detect()
│   ├── createWorker.js           Main-thread worker factory & job manager
│   ├── createScheduler.js        Worker pool + job queue
│   ├── createJob.js              Job object factory
│   ├── constants/                OEM, PSM, imageType, defaultOptions, languages
│   ├── utils/                    getEnvironment, getId, log, resolvePaths
│   ├── worker/                   Main-side adapters
│   │   ├── browser/              spawnWorker (Blob URL), loadImage (DOM/Blob), ...
│   │   └── node/                 spawnWorker (worker_threads), loadImage (fs/fetch)
│   └── worker-script/            Worker-side execution
│       ├── index.js              Action dispatcher + all OCR handlers
│       ├── constants/            defaultOutput
│       ├── utils/                setImage, dump, arrayBufferToBase64
│       ├── browser/              getCore (importScripts), gunzip (zlibjs), cache (idb-keyval)
│       └── node/                 getCore (require), gunzip (zlib), cache (fs)
├── examples/                     Official runnable examples (browser + node)
├── benchmarks/                   Speed & memory benchmarks (browser + node)
├── docs/                         Authoritative user docs (api, faq, performance, ...)
├── scripts/                      Dev server, webpack & rollup build configs
├── tests/                        Mocha + Karma test suites + assets
├── package.json                  Metadata, scripts, deps, browser field
├── babel.config.json             @babel/preset-env, "last 2 versions"
└── karma.conf.js                 Browser test runner (Chrome + Firefox headless)

System Requirements

Layer Requirement
Node.js (v7) v16+ (v6 needs v14+)
Browser JavaScript and WebAssembly support
CPU features SIMD / relaxed-SIMD are auto-detected and used when present; a non-SIMD core is the fallback
Network (default) Outbound HTTPS to jsDelivr for the core and to the tessdata CDN for language files
Memory High — each worker holds a full WASM engine instance; budget hundreds of MB per active worker for large images
Storage Cache for .traineddata (IndexedDB in browser, filesystem in Node.js)

Important

React Native is not supported because it lacks WebAssembly. Other constrained runtimes (some serverless "edge worker" platforms, certain browser-extension manifest setups) may require explicit workerPath/corePath configuration or may not work at all.


Configuration Reference

All configuration is supplied programmatically — there are no config files, environment variables, or .env settings in Tesseract.js. Configuration flows through three channels.

1. createWorker arguments

createWorker(langs = 'eng', oem = OEM.LSTM_ONLY, options = {}, config = {})

2. WorkerOptions (third argument)

Option Type Default Notes
corePath string jsDelivr tesseract.js-core@v<coreVersion> Must be a directory holding all four core builds
langPath string jsDelivr @tesseract.js-data/<lang>/4.0.0 (or …/4.0.0_best_int for LSTM-only) No trailing /
workerPath string jsDelivr tesseract.js@v<version>/dist/worker.min.js (browser); local path (node) Override for bundler/framework issues
dataPath string . Path inside the WASM MEMFS for traineddata; rarely changed
cachePath string . Node: cache folder; browser: IndexedDB key prefix
cacheMethod string write write / readOnly / refresh / none
legacyCore boolean false Download code supporting the Legacy model
legacyLang boolean false Download data supporting the Legacy model
workerBlobURL boolean true Wrap the worker script in a Blob URL (browser)
gzip boolean true Whether remote traineddata is gzipped
logger function () => {} Progress callback
errorHandler function — Worker error callback

Note

defaultOptions (src/constants/defaultOptions.js) sets only workerBlobURL: true and a no-op logger. Environment-specific defaultOptions add the appropriate workerPath (node: a filesystem path under worker-script/node/index.js; browser: the CDN worker.min.js for the current version).

cacheMethod semantics (from source):

Value Reads cache Writes cache Cache-delete on init failure
write (default) Yes Yes Yes
readOnly Yes No No
refresh No Yes Yes
none No No No

The read path is skipped when cacheMethod ∈ {refresh, none}; the write path runs when cacheMethod ∈ {write, refresh, undefined}. On initialization failure, cached data is deleted only when cacheMethod ∈ {write, refresh, undefined} (i.e. when we have write permission).

3. Tesseract engine parameters

Two sub-channels:

  • Runtime parameters — via worker.setParameters({...}) or per-call recognize options. Applied with api.SetVariable(key, value). Keys starting with tessjs_ are filtered out (reserved for Tesseract.js internals).
  • Init-only parameters — via the config argument of createWorker/reinitialize. These map to Tesseract's [type]_INIT_MEMBER settings and cannot be changed after Init. Examples: load_system_dawg, load_freq_dawg, load_unambig_dawg, load_punc_dawg, load_number_dawg, load_bigram_dawg, tessedit_ocr_engine_mode, tessedit_init_config_only, language_model_ngram_on, language_model_use_sigmoidal_certainty.

Warning

Calling setParameters with an init-only key has no effect and logs Attempted to set parameters that can only be set during initialization: <names>. The engine silently ignores them — there is no exception, so watch the console during integration.

The config argument may be either a config-file string or an object. If an object, it is serialized to Tesseract config-file text (JSON.stringify then ,→newline, :→space, quotes/braces stripped) and written to /config in the WASM FS before Init.


Module Reference

Main thread

Module Exports Responsibility
src/index.js createWorker, createScheduler, recognize, detect, setLogging, OEM, PSM, languages Public API surface
src/createWorker.js factory (langs, oem, options, config) => Promise<Worker> Spawn worker, resolve paths, manage per-job promises, drive the load → loadLanguage → initialize boot sequence, expose worker methods
src/createScheduler.js factory () => Scheduler Maintain workers, runningWorkers, jobQueue; dequeue assigns the next queued job to an idle worker
src/createJob.js factory ({id, action, payload}) => job Generate job ids (getId('Job', n)) and normalize job objects
src/Tesseract.js recognize, detect Deprecated one-shots: create a worker, run one job, terminate in a finally

Constants

File Contents
src/constants/OEM.js TESSERACT_ONLY:0, LSTM_ONLY:1, TESSERACT_LSTM_COMBINED:2, DEFAULT:3
src/constants/PSM.js 14 page-segmentation modes ('0'–'13')
src/constants/imageType.js COLOR:0, GREY:1, BINARY:2
src/constants/defaultOptions.js workerBlobURL:true, no-op logger
src/constants/languages.js ~90 symbolic language-code constants
src/worker-script/constants/defaultOutput.js Default output flags — only text:true; all others false

Utilities

File Purpose
src/utils/getEnvironment.js Detect runtime: webworker / browser / node (by globals WorkerGlobalScope, document, process)
src/utils/getId.js `${prefix}-${cnt}-${random}` id generator
src/utils/log.js Global logging flag, setLogging, gated log()
src/utils/resolvePaths.js Resolve corePath/workerPath/langPath to absolute URLs in the browser (new URL(s, location.href)); identity in Node.js

Main-side adapters (src/worker/{browser,node}/)

Module Browser Node.js
spawnWorker new Worker(URL.createObjectURL(blob)) wrapping importScripts, or direct new Worker(workerPath) new Worker(workerPath) from worker_threads
terminateWorker worker.terminate() worker.terminate()
send worker.postMessage(packet) worker.postMessage(packet)
onMessage worker.onmessage = … worker.on('message', …)
loadImage DOM-aware: URL/base64, File/Blob, img/video/canvas, OffscreenCanvas → Uint8Array fs/fetch/base64 → Uint8Array
defaultOptions adds CDN workerPath for current version adds filesystem workerPath

Worker-side execution

Module Purpose
src/worker-script/index.js The engine driver. Implements load, FS, loadLanguage, initialize, setParameters, recognize, detect, terminate; dispatchHandlers routes packets; setAdapter injects env code
src/worker-script/utils/setImage.js Writes the image into the WASM FS; converts BMP via bmp-js (Leptonica BMP gaps); reads EXIF orientation; api.SetImageFile(exif, angle) (throws Error attempting to read image. on failure)
src/worker-script/utils/dump.js Builds the result JSON: pulls each requested output (GetUTF8Text, GetHOCRText, GetTSVText, …), renders PDF via TessPDFRenderer, extracts pre-processed images, maps PSM/OEM enums back to strings
src/worker-script/utils/arrayBufferToBase64.js Pure-JS base64 encoder for image output
src/worker-script/{browser,node}/getCore.js SIMD-aware loader that selects the correct tesseract.js-core build
src/worker-script/{browser,node}/gunzip.js zlibjs.gunzipSync (browser) / zlib.gunzipSync (node)
src/worker-script/{browser,node}/cache.js idb-keyval (browser) / fs (node) cache CRUD

Recognition Pipeline Internals

What actually happens during worker.recognize(image, options, output):

  1. Image load (main thread). loadImage normalizes the input (URL, base64, Buffer, DOM element, File/Blob, local path) into a Uint8Array, then sends a recognize job to the worker.
  2. Option splitting. In the worker, keys that are neither Tesseract.js options (rectangle, pdfTitle, pdfTextOnly, rotateAuto, rotateRadians) nor tessjs_* are treated as engine parameters and applied with SetVariable (after SaveParameters, restored with RestoreParameters afterward).
  3. Output planning. processOutput merges requested formats over defaultOutput and counts how many require OCR. Outputs imageColor, imageGrey, imageBinary, layoutBlocks, debug do not require recognition; if only those are requested, skipRecognition is true and full OCR is bypassed.
  4. Image placement & rotation. With rotateAuto, the engine temporarily switches to an auto PSM, finds lines, computes the skew angle (GetGradient or legacy GetAngle), and re-sets the image at that angle (angles < 0.005 rad are ignored). Otherwise rotateRadians (default 0) is used.
  5. Region selection. If rectangle is given, api.SetRectangle(left, top, width, height).
  6. Recognition. api.Recognize(null) runs unless skipped; for layout-only requests, api.AnalyseLayout() runs instead.
  7. Result dump. dump reads every requested format into a JSON object, appends confidence, psm, oem, version, and rotateRadians, then the result is posted back and the matching promise resolves.

Note

Progress is reported throughout via res.progress(...). The engine's internal TesseractProgress(percent) callback is rescaled to (percent - 30) / 70 so the user-visible "recognizing text" progress runs 0→1 over the OCR phase.


Caching Subsystem

Language data is the largest runtime download (typically ~2 MB; worst case, e.g. Legacy + Chinese Simplified, ~20 MB), so caching is central to performance.

Flow (loadLanguage in src/worker-script/index.js):

  1. For each language, attempt a cache read of <cachePath>/<lang>.traineddata (skipped if cacheMethod ∈ {refresh, none}).
  2. On a miss, fetch from langPath (or the default CDN). In Node.js, langPath may be a URL or a local path — is-url plus scheme checks (file://, moz-extension://, chrome-extension://) decide which. URLs are fetched (.gz appended when gzip); local paths are read from disk.
  3. Detect gzip via magic bytes (0x1F 0x8B) and gunzip if needed.
  4. Write the data into the WASM MEMFS (<dataPath>/<lang>.traineddata).
  5. Write back to the cache when cacheMethod ∈ {write, refresh, undefined}.

Self-healing on init failure. If api.Init returns -1 and data came from cache, the worker assumes the cached data may be bad or lack the requested OEM. It deletes the cache entry; if the failure mentions "components are not present", it re-runs loadLanguage (forcing a fresh download) and retries Init once.

Environment Cache backend
Browser IndexedDB via idb-keyval (cachePath becomes a key prefix)
Node.js Filesystem via fs (cachePath is a folder; default is the current working directory)

Tip

In Node.js, set cachePath to a stable, writable directory (e.g. a mounted volume) so cached traineddata survives container restarts and isn't written to the process CWD unpredictably.


Security Analysis

Tesseract.js is a client/embedded library, so its threat surface differs from a server application. Findings below are based on the source.

Network & supply chain

  • Default remote loading. By default, the WASM core and language data are loaded from the jsDelivr CDN over HTTPS at runtime. This is a runtime dependency on a third-party CDN and an implicit trust of its contents.

Important

For high-assurance or air-gapped deployments, self-host the core (corePath), worker script (workerPath), and language data (langPath) from infrastructure you control, and pin versions. This removes the runtime CDN dependency and lets you verify artifacts. See Operations.

  • importScripts of a remote URL (browser). getCore calls global.importScripts(corePathImportFile) against the (possibly CDN) core URL — remote code execution by design. Whoever controls corePath controls executed code. Treat corePath/workerPath as security-sensitive configuration; never set them from untrusted user input.
  • Blob-URL worker (browser). spawnWorker builds a worker from new Blob(['importScripts("${workerPath}");']). Sites with a strict Content-Security-Policy must allow worker-src blob: (and the CDN in script-src) or set workerBlobURL: false and serve a same-origin worker.min.js.

Input handling

  • Image bytes flow into a native (WASM) engine. Untrusted images are parsed by Leptonica/Tesseract inside WebAssembly. WASM provides memory-sandboxing (no host memory access), which substantially contains parser bugs — but denial-of-service via pathological images (memory growth, long runtimes) is still possible. Bound input sizes and apply timeouts in server contexts.
  • BMP special-case. setImage runs BMP files through bmp-js before handing them to Leptonica (which supports only some BMPs). This is extra attack surface in pure JS, though again sandbox-contained.
  • No code-injection from parameters. Engine parameters go through api.SetVariable; the config object is serialized to a Tesseract config file, not evaluated. Keys are filtered (tessjs_* removed). There is no eval of user data.

Filesystem (Node.js)

  • The Node cache adapter reads/writes paths derived from cachePath/langPath. If those are ever set from untrusted input, path traversal is conceivable — keep them operator-controlled. deleteCache swallows errors by design (cache cleanup is best-effort).

Secrets

  • The library handles no credentials, tokens, or secrets. There is no authentication, no outbound telemetry, and no secret material in the codebase.

Caution

The postinstall script runs opencollective-postinstall || true (a funding banner). It is harmless and non-fatal, but some environments dislike postinstall scripts. Issue #1030 tracks a request to remove it for Deno compatibility. Install with --ignore-scripts if your policy forbids postinstall hooks.


Operations

Running in long-lived Node.js services

Warning

Recycle workers periodically. WebAssembly memory only ever grows, so a single large image permanently raises a worker's footprint. Additionally, workers "learn" by adding encountered words to an internal dictionary; over thousands of unrelated documents this bloats with typos. Terminate and recreate workers/schedulers on a cadence — e.g. every ~500 jobs.

const { createScheduler, createWorker } = require('tesseract.js');

const POOL_SIZE = 4;
const MAX_JOBS = 500;
let scheduler;
let jobsSinceReset = 0;

async function buildScheduler() {
  const s = createScheduler();
  await Promise.all(Array.from({ length: POOL_SIZE }, async () => {
    const w = await createWorker('eng', 1, { cachePath: '/var/cache/tessdata' });
    s.addWorker(w);
  }));
  return s;
}

async function recognize(image) {
  if (!scheduler || jobsSinceReset >= MAX_JOBS) {
    const old = scheduler;
    scheduler = await buildScheduler();
    jobsSinceReset = 0;
    if (old) await old.terminate(); // drain/replace
  }
  jobsSinceReset += 1;
  const { data: { text } } = await scheduler.addJob('recognize', image);
  return text;
}

Tip

Size POOL_SIZE to available memory, not CPU count — each worker is memory-heavy. Start small (2–4) and load-test.

Operational checklist

  • [ ] Pin tesseract.js and tesseract.js-core to exact versions.
  • [ ] Self-host corePath, workerPath, langPath (or accept the CDN dependency knowingly).
  • [ ] Set a stable, writable cachePath (Node.js) on a persistent volume.
  • [ ] Bound image size and add per-job timeouts.
  • [ ] Recycle workers/schedulers on a job-count or time cadence.
  • [ ] Set oem/psm explicitly if you need parity with the Tesseract CLI.
  • [ ] For browsers, configure CSP for blob: workers and the CDN, or serve a same-origin worker.

Observability

Enable progress/diagnostic logging with a logger callback or globally with setLogging(true):

const { setLogging } = require('tesseract.js');
setLogging(true); // verbose internal logs to console

There is no built-in metrics export; instrument around addJob/recognize (latency, success/empty/error counts) yourself.


Build & Release Pipeline

The dist/ artifacts are not committed; they are produced by the build. Source is authored as CommonJS and transpiled with Babel (@babel/preset-env, target "last 2 versions").

npm scripts (package.json)

Script Command Purpose
start node scripts/server.js Dev server on port 3000 (webpack-dev-middleware, writes to disk)
build rimraf dist && webpack --config scripts/webpack.config.prod.js && rollup -c scripts/rollup.esm.mjs Build UMD (webpack) + ESM (rollup) bundles into dist/
profile:tesseract webpack-bundle-analyzer dist/tesseract-stats.json Inspect main bundle size
profile:worker webpack-bundle-analyzer dist/worker-stats.json Inspect worker bundle size
prepublishOnly npm run build Ensure a fresh build before publish
wait rimraf dist && wait-on http://localhost:3000/dist/tesseract.min.js Wait for dev build before tests
test npm-run-all -p -r start test:all Start server and run the full suite
test:all npm-run-all wait test:browser test:node:all Browser + Node tests
test:browser karma start karma.conf.js Headless Chrome + Firefox
test:node / test:node:all nyc mocha … Node tests with coverage
lint / lint:fix eslint src Airbnb-base lint

Dev server (scripts/server.js)

An Express server that serves the webpack-compiled bundles and the repo statically on port 3000. It sets Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp — these COOP/COEP headers enable performance.measureUserAgentSpecificMemory() used by the benchmarks (and are required for SharedArrayBuffer-style isolation generally). The default dev page is examples/browser/basic-efficient.html.

Note

The COOP/COEP headers are needed only for the benchmark memory measurements; the docs note they can be removed if problematic in other contexts.

Contributor workflow

git clone https://github.com/naptha/tesseract.js.git
cd tesseract.js
npm install
npm start   # dev server with live rebuild of tesseract.min.js / worker.min.js

Before opening a PR:

npm run lint
npm run test

Important

Always confirm the automated tests pass before submitting a pull request. The dev server rebuilds tesseract.min.js and worker.min.js automatically when files in src/ change.


Testing Strategy

Layer Tooling Location
Browser Karma + Mocha, headless Chrome & Firefox karma.conf.js, tests/*.test.mjs
Node.js Mocha + nyc (coverage), expect.js tests/*.test.mjs
Assertions expect.js —

Test suites cover the FS interface, OSD detection, recognition, and the scheduler (tests/FS.test.mjs, detect.test.mjs, recognize.test.mjs, scheduler.test.mjs). Test assets include sample images in many formats and .traineddata for eng, chi_tra, and osd. The Karma config serves the four SIMD/relaxed-SIMD core builds and proxies them so browser tests exercise the real WASM core.

Tip

Because browser tests need the built dist/ bundles, the test script orchestrates the dev server (start), waits for the bundle (wait), then runs browser and node suites in parallel via npm-run-all.


Code Quality & Conventions

  • Style: ESLint with eslint-config-airbnb-base. Lint runs on src only.
  • Module format: CommonJS throughout src/ ('use strict' headers, module.exports). Migration to ESM is a tracked aspiration (issue #890) but not done.
  • Environment abstraction: Strong, consistent adapter boundaries (worker/ and worker-script/ each have parallel browser//node/ trees with identical interfaces). This is the codebase's defining quality and makes it easy to reason about.
  • Small, single-purpose modules: Most files are tiny (the largest single source file is the worker-script dispatcher at ~19 KB). Utilities are one function each.
  • Type coverage: A complete hand-written index.d.ts describes the full public surface, including the deep Page/Block/Paragraph/Line/Word/Symbol result tree.
  • Backward-compatibility care: The code carries explicit compatibility shims (e.g. GetGradient vs. legacy GetAngle; TesseractCore vs. older TesseractCoreWASM; tolerating corePath pointing at a single .js file). Comments explain why, which aids maintenance.

Note

A reader auditing this codebase should start at src/worker-script/index.js (all OCR behavior) and src/createWorker.js (all main-thread orchestration). Everything else is adapters, constants, and small utilities.


Version Migration

Highlights of recent breaking changes (full lists are in the linked upstream issues).

v6 (from v5)

  • All output formats except text are disabled by default. Re-enable explicitly, e.g. worker.recognize(image, {}, { hocr: true }).
  • Minor changes to the structure of the blocks output.
  • Memory leak fixed; overall runtime and memory reductions.
  • Tracking: issue #993.

v5 (from v2–v4)

  • createWorker arguments changed — language and OEM are now set in createWorker (e.g. createWorker('chi_sim', 1)).
  • worker.initialize and worker.loadLanguage were removed — delete them from your code (workers come pre-loaded).
  • ~50%+ smaller default downloads and lower memory use.
  • Tracking: issue #820; v2→v5 guide: issue #771.

v4

  • Added rotation preprocessing (including auto-rotate); processed images retrievable; better scheduler/parallel support.
  • createWorker became async; getPDF replaced by the pdf recognize option.

Important

The single most common migration break is the v6 output-format change. If a previously-working integration suddenly returns null for hocr/tsv/blocks, enable those formats in the third argument of recognize.


Potential Improvements

Observations from the source and the issue tracker, framed as actionable opportunities for maintainers/integrators.

  • ESM migration (issue #890). The library is CommonJS with a separate rollup ESM bundle step. A native-ESM source tree would simplify the build and improve tree-shaking and modern-runtime support (Deno, Bun).
  • Remove the opencollective-postinstall hook (issue #1030). It complicates Deno installs and triggers postinstall-policy warnings; the funding banner could move elsewhere.
  • Non-standard runtime support. Tracked gaps include Bun + Next.js API routes (#1064), Cloudflare Workers (#950), Electron node-mode (#1043), and browser-extension MV3 (#961). Most stem from build systems relocating the worker script — a more robust worker-path resolution strategy would reduce the recurring "Cannot find module" class of issues (#868-style).
  • worker.spawnWorker error propagation (issue #712). Errors thrown during worker spawn are hard to catch from the caller; surfacing them through the returned promise would improve DX.
  • Custom traineddata via non-URL paths / blob URLs (issues #998, #965). Loading custom language data from arbitrary locations has rough edges worth smoothing.
  • languages export on the default export (issue #887). A small consistency fix to the public surface.
  • Documented "fast" language data tradeoffs. The performance docs invite users to try 4.0.0_fast data but note it is unbenchmarked; publishing quality/speed numbers would help operators make the call.

Note

None of these are correctness bugs in the wrapper's core path — recognition itself is delegated to the upstream engine. They are integration-surface and build-system refinements.


Quick Reference

Public API

createWorker(langs='eng', oem=1, options={}, config={}) -> Promise<Worker>
createScheduler() -> Scheduler
recognize(image, langs?, options?)  // deprecated
detect(image, options?)             // deprecated
setLogging(boolean)
OEM, PSM, languages

Worker

recognize(image, options?, output?, jobId?)
detect(image, jobId?)            // needs legacyCore+legacyLang
setParameters(params, jobId?)
reinitialize(langs?, oem?, config?, jobId?)
writeText / readText / removeFile / FS
terminate(jobId?)

Scheduler

addWorker(worker) -> id
addJob('recognize'|'detect', ...payload) -> Promise
getQueueLen() -> number
getNumWorkers() -> number
terminate() -> Promise

Defaults

Setting Default
langs 'eng'
oem 1 (LSTM_ONLY)
psm 6 (SINGLE_BLOCK)
output text only
cacheMethod write
gzip true
workerBlobURL true

Runtime dependencies

Package Role
tesseract.js-core The WebAssembly Tesseract engine
bmp-js Decode BMP images Leptonica can't
idb-keyval Browser IndexedDB cache
is-url Distinguish URL vs. local path (Node langPath)
node-fetch fetch fallback on older Node
wasm-feature-detect Detect SIMD / relaxed-SIMD for core selection
zlibjs Browser gunzip of traineddata
opencollective-postinstall Funding banner (postinstall)

Does NOT exist / NOT supported

  • ❌ PDF input (output PDF is supported)
  • ❌ Handwriting recognition
  • ❌ React Native (no WebAssembly)
  • ❌ Config files / environment variables (config is programmatic only)
  • ❌ Modifying the recognition model or engine accuracy (delegated to upstream Tesseract)
  • ❌ Authentication, secrets, telemetry