Scribe.js: OCR and searchable PDF generation in JavaScript
JavaScript OCR and text extraction for images and PDFs. Projects Scribe OCR: officially supported GUI front-end for Scribe.js Site at scribeocr.com, repo at github.com/scribeocr/scribeocr If you have a project or example repo that uses Scribe.js, feel free to add it to this list using a pull request.
At a glance
- What is it?
- Scribe.js is an ESM OCR library for images and PDFs that can also write an invisible text layer into an existing PDF. It is aimed at developers, not end users, and it ships under AGPL-3.0.
- Who is it for?
- Adopt Scribe.js if you are a developer who needs OCR plus a searchable PDF text layer inside a JavaScript or TypeScript codebase, and if AGPL-3.0 fits your distribution model. Do not adopt it if you need a no-build CDN import, a UMD bundle, or a finished scanning application for non-technical users; the README points those users at the separate Scribe OCR GUI.
- Can I use it commercially?
- Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The gap Scribe.js fills between OCR and a finished PDF
Most JavaScript OCR work stops at a string. You hand an image to a recognizer, you get text back, and everything after that is your problem: page geometry, line breaks, and the fact that the PDF you started with is still a picture of words rather than words. Scribe.js takes the longer path. The README lists three use cases, and the third is the one that separates it from a plain recognizer: writing .pdf files that include a high-quality invisible text layer, or inserting text into an existing .pdf file so it becomes searchable.
The audience is explicit. The README states that Scribe.js is a library intended for developers, and that end users who want to scan documents should use the GUI at scribeocr.com, whose repository lives at github.com/scribeocr/scribeocr. That is a clean split, and it is worth respecting: if you are looking for a document scanner to hand to an office, this is the wrong layer of the stack. If you are building the scanner, it is the right one.
Document objects, recognition, and export as the core data flow
The architecture visible in the README is a document-centric API. Instead of a single stateless function, you call openDocument with one or more inputs, which the README describes as an image, PDF, or existing OCR file(s). That call returns a document object. Recognition and export are methods on that object, so the document carries state between steps: what pages exist, what text has been recognized, and what format it will be written out as.
That design choice has consequences. Because the document persists, you can recognize once and export more than once, and you can transform the document between those steps. It also means you are responsible for cleanup. The README repeats the same line after both examples: call scribe.terminate() after all recognition has completed, so a Node process can exit. In a long-running server this matters less; in a script or a CLI it is the difference between a process that ends and one that hangs.
The README also notes that Scribe.js is written in JavaScript using ESM, so it can be imported directly from browser or Node.js JavaScript code without a build step. There is no UMD version, and that absence shapes the deployment story described below.
Installing scribe.js-ocr from npm and producing a searchable PDF
The package name on npm is scribe.js-ocr, not the repository name. The README gives the install command directly.
npm i scribe.js-ocrAfter that, the import path depends on your environment. In Node.js, or in a browser with a bundler, the README imports the package by name. In a browser without a bundler you import the file path instead.
// Node.js, or in the browser with a bundler
import scribe from 'scribe.js-ocr';
// In the browser without a bundler (import map or relative path):
import scribe from 'node_modules/scribe.js-ocr/scribe.js';For a first real use, the README offers a one-call helper. It takes an array of sources and returns text, and the README is candid that this function makes it easy for new users to test Scribe.js but is generally not ideal for production use.
const text = await scribe.extractText(['https://tesseract.projectnaptha.com/img/eng_bw.png']);
console.log(text);
await scribe.terminate(); // release everything after all recognition completed, so a Node process can exitWhen you want control over the output, the README's second example opens a document, recognizes with an explicit language list, and downloads a PDF. The result should be a receipt.pdf that looks like the original image but carries selectable text.
const doc = await scribe.openDocument(['receipt.png']); // image, PDF, or existing OCR file(s)
await doc.recognize({ langs: ['eng'] }); // run OCR
await doc.download('pdf', 'receipt.pdf'); // write a searchable PDF
await scribe.terminate(); // release everything after all recognition completed, so a Node process can exitThe package.json also declares a binary named scribe pointing at cli/scribe.js, and the repository has a docs/cli.md reference. The README does not reproduce CLI usage, so treat the CLI as documented elsewhere rather than something you can infer from the library examples.
Same-origin serving, no CDN, and the framework templates that exist
The constraint most likely to break a first integration is stated plainly: when using Scribe.js in the browser, all files must be served from the same origin as the file importing Scribe.js, and importing Scribe.js from a CDN will not work. Combined with the lack of a UMD build, this rules out the pattern many front-end developers reach for first, which is a script tag pointed at a hosted bundle. You need a bundler, an import map, or a server that can serve the package files yourself.
The project acknowledges this friction by maintaining template repositories. The README lists browser with ESM and no build, Next.js, Webpack 5, and Vue.js v2, all under the scribeocr organization. It also asks for contributions: if you are using Scribe.js within a framework not listed, the README suggests making a basic repo and adding it to the list with a PR, especially if non-obvious steps were required. That is a reasonable admission that the integration surface is not uniform across frameworks.
One detail worth flagging for anyone searching for TypeScript support: the repository is JavaScript, and the README's import examples are plain .js. There is a jsconfig.json at the top level and the tests run through Vitest, but the README does not advertise published type definitions. If typed APIs are a requirement, verify that before you commit.
Where Scribe.js is the wrong tool
The AGPL-3.0 licence is the first limitation, and it is not a footnote. If you modify Scribe.js and let users interact with it over a network, the licence's network clause is the part to read carefully before you build a hosted product on top of it. This is a distribution-model question, not a technical one, and it is the reason some teams will stop here.
The second limitation is the browser constraint already described. A CDN import does not work, and there is no UMD version, so any environment that expects to drop a single script tag onto a page is out of scope.
The third is scope. Scribe.js recognizes text and writes text layers. If your problem is layout analysis, table structure, or form-field extraction, the README does not claim to solve it, and the use cases it lists are recognition and PDF text extraction. And if your actual user is a person who wants to scan a stack of paper, the README itself redirects you to the separate GUI project. Using the library for that job means rebuilding the application that already exists.
Scribe.js against Tesseract.js: the difference is the output
The README points readers to a dedicated comparison document, docs/scribe_vs_tesseract.md, and the related searches show that this question is what people actually type. The honest summary from the README alone is about output shape. Tesseract.js is the recognizer people reach for when they want text out of an image in JavaScript. Scribe.js is built around a document object that can recognize text and then export a PDF with an invisible text layer, or insert that layer into a PDF that already exists.
That distinction drives the rest. A recognizer returns a string and leaves page assembly to you. Scribe.js keeps the document around so that recognition and export are separate steps on the same object, which is what makes the searchable-PDF workflow possible in one library. The README does not publish accuracy numbers for either tool, and the comparison article is the place to look for the project's own argument rather than anything stated in the README. If your requirement is a string, the lighter option is usually enough. If your requirement is a PDF that a user can search, the extra machinery is the point.
Licence, maintenance, and the cost of upgrading
Scribe.js is licensed AGPL-3.0, and the package.json confirms it. For a library that a server exposes to users, the AGPL's network clause is the practical consideration, and it is worth a conversation with whoever owns your licensing decisions rather than a guess. None of this is legal advice; it is a pointer to the file you should read.
The repository is not archived, and the last push was on 2026-05-27, which is the same date as the v0.12.0 release. The releases listed are v0.12.0, v0.11.3, and v0.11.0, with the two earlier ones in early May 2026. The package.json in the repository carries version 0.15.0, which is ahead of the newest release listed, so the published npm version and the repository's package version are not the same number at the time of writing. If you pin a version, check which one npm actually resolves.
Upgrade cost is mostly about the API surface you touch. The README's examples are small: an import, an openDocument call, a recognize call, a download call, and terminate. Code that stays on those methods has little to migrate. Code that reaches into the document object's other methods, or into the submodule-based build, has more to re-verify. Note that a local development copy clones with --recurse-submodules, so the build depends on submodules being present.
Editorial conclusion
Adopt Scribe.js if you are a developer who needs OCR plus a searchable PDF text layer inside a JavaScript or TypeScript codebase, and if AGPL-3.0 fits your distribution model. Do not adopt it if you need a no-build CDN import, a UMD bundle, or a finished scanning application for non-technical users; the README points those users at the separate Scribe OCR GUI. Before committing, verify that your build serves Scribe.js from the same origin as the importing file, and confirm how you will satisfy the AGPL-3.0 network clause if your service is hosted rather than shipped.
Frequently asked questions
Is there a free version of Scribe.js?
Scribe.js is published on npm as scribe.js-ocr and licensed AGPL-3.0, so there is no paid tier described in the README. The README does point end users who want to scan documents to the GUI at scribeocr.com, which is a separate project from the library.
How do I install Scribe.js?
Install it from npm with npm i scribe.js-ocr, then import it by package name in Node.js or in a browser with a bundler. In a browser without a bundler, the README imports the file path node_modules/scribe.js-ocr/scribe.js instead.
Can I load Scribe.js from a CDN?
No. The README states that when using Scribe.js in the browser, all files must be served from the same origin as the file importing Scribe.js, so importing it from a CDN will not work. There is also no UMD version.
What does Scribe.js do that Tesseract.js does not?
The README frames the difference around output: Scribe.js can write .pdf files that include a high-quality invisible text layer, and can insert text into an existing .pdf file to make it searchable. The project keeps a separate comparison document at docs/scribe_vs_tesseract.md for the full argument.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/scribeocr-scribe-js)