Typo.js: a browser-side spellchecker built on Hunspell dictionaries
A client-side JavaScript spellchecker that uses Hunspell-style dictionaries.
At a glance
- What is it?
- Typo.js is a JavaScript and TypeScript spellchecker that reads Hunspell-style affix and dictionary files and answers whether a word is spelled correctly and what the alternatives are, with support for the affix flags that matter for real languages. It suits a developer who wants spellchecking in a browser extension or an editor without a server call, and the constraint to know about is that it is the Hunspell dictionary format rather than a custom one, so the corpus is the asset you have to source.
- Who is it for?
- Adopt Typo.js if you need spellchecking that runs entirely in the browser, since the whole dictionary and suggestion engine is client side and there is no service to depend on. Do not adopt it expecting a modern dictionary format, because it reads the Hunspell aff and dic pair, so check first that the language you need exists as Hunspell dictionaries and that you can ship them, since the browser has to download them and the size of a full dictionary is not trivial.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 3 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 October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The dictionary format is the design decision
Typo.js is a JavaScript and TypeScript spellchecker that uses Hunspell-style dictionaries, and that single clause determines almost everything else about the library.
Hunspell dictionaries come in pairs. The `.aff` file is the affix file, and it describes the rules of the language: how prefixes attach, how suffixes attach, which flags to use, how compounds are allowed, what character encoding the file is in. The `.dic` file is the word list, and it maps each word to the flags that say which affix rules apply to it. A Hunspell implementation therefore is mostly the job of interpreting the `.aff` grammar, and the word list is data rather than code.
That is why a client-side spellchecker built this way is a realistic thing to ship. The alternative would be a proprietary word list format, and there is a great deal of value in the fact that the same dictionaries work with the command line Hunspell, with LibreOffice, with Firefox's built-in spellchecker and with this library. The corpus already exists and is maintained by the communities that speak those languages.
The cost of that choice is that the library is tied to a format it does not control, and to the flag set that Hunspell defines. The list of supported flags in the documentation is long and specific, which is a good sign about the depth of the implementation: `PFX`, `SFX`, `REP`, `FLAG`, `COMPOUNDMIN`, `COMPOUNDRULE`, `ONLYINCOMPOUND`, `KEEPCASE`, `NOSUGGEST`, `NEEDAFFIX`, `ICONV` and `OCONV`.
Two of those are worth pausing on. `NOSUGGEST` marks a word as valid but never offered as a correction, which is how dictionaries exclude profanity from the suggestion list without excluding it from the spellchecker. `KEEPCASE` marks a word whose capitalisation matters, so that a product name typed in the wrong case is flagged rather than accepted. Both are behaviours you would otherwise have to implement yourself on top of a simpler word list.
`ICONV` and `OCONV` are about encoding, and they connect to the charset handling described later. A dictionary can declare a different input and output encoding, which matters for older dictionaries that are not UTF-8.
Three ways to load a dictionary, and why they differ
The library can be loaded in several contexts, and the constructor changes shape in each one, which is the part of the API most likely to trip you up.
In a Chrome extension, you include the typo.js file in the extension's background page and initialise the dictionary directly:
var dictionary = new Typo("en_US");There is no path argument, because in an extension the dictionary files sit next to the script and the library finds them on its own.
In a standard web application you have to say where the dictionary folder is:
var dictionary = new Typo("en_US", false, false, { dictionaryPath: "typo/dictionaries" }),The settings object is the mechanism, and the other two constructor arguments are the two booleans that come before it, so the call reads as locale, then two flags, then settings. The documentation does not spell out what the two booleans are, which is a genuine gap for anyone reading only the README, but the shape of the call is consistent across all three examples and that is what you need in order to write the call correctly.
In Node.js, the module is required and the dictionary path is absolute:
var Typo = require("typo-js");
var dictionary = new Typo([...]);or with an ES module import:
import Typo from "typo-js"
var dictionary = new Typo("en_US", false, false, { dictionaryPath: "/node_modules/typo-js/dictionaries" })The documentation warns that your path to `node_modules` may vary, which is the honest caveat for anyone copying that path. In a browser build, pointing the library at a folder inside `node_modules` works in development and breaks the moment the bundle is deployed, so a production application will want the dictionary copied into its own static assets and the path pointed at wherever those are served from.
The Node example also shows a different form for the first argument, an array rather than a locale string, which suggests the constructor accepts either a locale or a list of dictionaries when you need to combine them.
Checking a word and asking for corrections
The two operations you will use most are both one-liners, and they are worth looking at together because the shape of the suggestion list tells you something about how the library works.
To check a word:
var is_spelled_correctly = dictionary.check("mispelled");And to ask for corrections:
var array_of_suggestions = dictionary.suggest("mispeling");
// array_of_suggestions == ["misspelling", "dispelling", "misdealing", "misfiling", "misruling"]The example result is instructive in a way that a documentation example usually is not. The suggestions are not all corrections of the same kind. `misspelling` is the obvious one, adding a letter. `dispelling` and `misfiling` are different edit distances. And `misdealing` and `misruling` share a prefix with the input rather than a suffix, which means the suggestion engine is generating candidates from the affix rules in the dictionary rather than doing a nearest-neighbour lookup over a word list.
That is what the affix support buys you. The engine knows that `mis-` is a valid prefix for some stems and `-ing` is a valid suffix for others, so it can propose a word that a simple edit-distance search would never find, and it can order the results in a way that reflects how the language actually forms words.
The comment in the example is also a note about determinism. The array is given as an exact expected value rather than as a set, which means the order is stable across runs. That matters if you are rendering a suggestion list in a user interface, and it matters more if you have a test asserting on the output.
For a spellchecker that runs in a browser, this is all done locally, which is the property that makes it usable at all in an extension. There is no request, no latency and no data leaving the page.
Encoding, and the one flag the library adds itself
Two details separate this implementation from a naive word list lookup, and both are about correctness rather than features.
The first is character encoding. When Typo.js loads the dictionary files itself, it honors the charset declared on the `SET` line of the `.aff` file, for example `SET ISO8859-1`. If you read the files yourself and pass their contents to the constructor, you have to decode them with that charset yourself, and the `_readFile` helper accepts the charset as its second argument:
var affData = dictionary._readFile("path/to/de_DE.aff", "ISO8859-1");That is a small but genuinely annoying detail to get wrong. A German dictionary encoded in ISO-8859-1 read as UTF-8 produces mojibake, and the failure shows up as every word looking misspelled rather than as an error. The library handles it for the common case where it does the reading, and pushes the responsibility onto you for the case where it does not.
The second is a flag the library adds on top of the Hunspell set. `PRIORITYSUGGEST` allows you to specify that certain words should be given priority in the suggestions list when correcting a misspelled word. You add it to the `.aff` file, ideally on the line after NOSUGGEST:
PRIORITYSUGGEST @and then add the `@` flag to the words you want prioritised in the `.dic` file:
skibidi/@
rizz/@Words carrying that flag are then prioritised above other suggestions if they already appear in the suggestion list.
The examples make the use case obvious, and the two sample words are the kind of vocabulary that a standard Hunspell dictionary from an earlier decade might not contain, which is the underlying problem. If your users type a term that is not in the dictionary, the spellchecker will call it wrong and, without help, suggest something unrelated. With `PRIORITYSUGGEST` and a custom `.dic` addition, the correct term is still suggested but now sits at the top of the list.
The wording is careful here: the words are prioritised if they already appear in the suggestion list. The flag does not inject a word that the suggestion engine would not have produced. It reorders, it does not add.
The `REP` flag, also in the supported list, covers the other end of the problem, since it is how a dictionary says that one word should be replaced by another, which is how the dictionary can teach the checker about a preferred spelling.
How the project is built and what ships with it
The repository layout is small and explains the whole development model.
There is a `typo/` directory holding the generated JavaScript, `typo/typo.js` as the single built file, and a `ts/` directory with the full TypeScript source. The README states that to modify Typo.js you make your changes in `ts/typo.ts` and then run `build.sh` to generate the JavaScript file. So the TypeScript is the source of truth and the JavaScript is generated output that is committed, which is the arrangement a project uses when it wants consumers to drop in a single file.
That single-file approach is what makes the Chrome extension instructions work. You include the typo.js file in your extension's background page, one file, no build step and no module loader. The same file works in a browser page through a script tag and in Node through the package entry point.
The `tests/` directory holds the unit test suites, and the `examples/` directory has a node example, with the complete Node.js example file at `examples/node/index.js`. There is also a `manifest.json` in the root, and the documentation explains exactly what it is for: it simplifies testing, because it allows you to load all of the files in the Typo project as a Chrome extension, and it has no purpose if you are using Typo.js in your own project.
That manifest is a small piece of evidence about how the library is tested. Testing a spellchecker means running it against real dictionary files, and the natural way to do that in a browser is to load the whole project as an extension with one manifest rather than assembling a test harness.
There is a `SECURITY.md` in the root, a `license.txt`, and a `build.sh` for the build step. The README is the only top-level documentation file, and the full documentation site is separate, at the project page linked from the README.
The project is free software under the Modified BSD License, though the licence classification GitHub shows is one it cannot identify. The last push was on 2026-10-20, there are no GitHub releases, and the README points out that Typo.js has been used in real-world projects, citing the Parola mobile word puzzle game and a number of npm packages that depend on it.
Editorial conclusion
Adopt Typo.js if you need spellchecking that runs entirely in the browser, since the whole dictionary and suggestion engine is client side and there is no service to depend on. Do not adopt it expecting a modern dictionary format, because it reads the Hunspell aff and dic pair, so check first that the language you need exists as Hunspell dictionaries and that you can ship them, since the browser has to download them and the size of a full dictionary is not trivial.
Frequently asked questions
What is Typo.js?
It is a JavaScript and TypeScript spellchecker that uses Hunspell-style dictionaries, running entirely in the browser with no server call. It is free software under the Modified BSD License, and the last push to the repository was on 2026-10-20.
How do I use Typo.js in a web application or a Node.js project?
In a browser you pass a settings object with a dictionaryPath pointing at the folder holding the dictionary files, for example `new Typo("en_US", false, false, { dictionaryPath: "typo/dictionaries" })`. In Node you require or import the module and give an absolute path such as `/node_modules/typo-js/dictionaries`, noting that your path to node_modules may vary.
Which Hunspell affix flags does Typo.js support?
It has full support for PFX, SFX, REP, FLAG, COMPOUNDMIN, COMPOUNDRULE, ONLYINCOMPOUND, KEEPCASE, NOSUGGEST, NEEDAFFIX, ICONV and OCONV, plus the Typo-specific PRIORITYSUGGEST flag which lets you mark words that should be prioritised in the suggestion list.
How do I check a word and get suggestions with Typo.js?
Call `dictionary.check("mispelled")` to get a boolean, and `dictionary.suggest("mispeling")` to get an array of corrections such as misspelling, dispelling, misdealing, misfiling and misruling. The suggestions come from applying the affix rules in the dictionary, so they include forms a plain edit-distance search would not reach.
What does the PRIORITYSUGGEST flag do in Typo.js?
You add `PRIORITYSUGGEST @` to the .aff file and then mark words with the @ flag in the .dic file, for example `skibidi/@`. Words carrying that flag are then prioritised above other suggestions when correcting a misspelled word, but only if they already appear in the suggestion list.
How do I add a custom word to Typo.js?
Adding a word to the .dic file is not enough on its own, because a standard dictionary may not produce it as a suggestion at all. The supported route is the PRIORITYSUGGEST flag, which reorders a word that the suggestion engine already produces so that it sits at the top of the list. Typo.js also honours the charset declared on the SET line of the .aff file, and the _readFile helper accepts that charset as its second argument if you read the files yourself.
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/cfinke-typo-js)Was this useful?