Ohm: a grammar language and a parser library sharing one package
A library and language for building parsers, interpreters, compilers, etc.
At a glance
- What is it?
- ohmjs/ohm ships two things that share a name: a domain-specific language for writing parsing expression grammars, and the JavaScript library that turns those grammars into parsers, interpreters and compilers. Left recursion, grammar inheritance and semantics kept out of the grammar are what set it apart from a parser you generate once at build time.
- Who is it for?
- Adopt Ohm when your grammar changes faster than your build pipeline, when you need left recursion, or when you want semantics in ordinary JavaScript modules instead of inside grammar rules. Skip it when a parser generated once at build time with no runtime dependency is what you need.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 149 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 November 14, 2026, and from our analysis. They are not legal advice.
Star history
- ohmjs/ohm5,556 ★ · +6
| Repository | 2026-09-23 | 2026-11-14 |
|---|---|---|
| ohmjs/ohm | 5.6k | 5.6k |
6 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
One package, two artifacts: a grammar language and a library
The Ohm language is a domain-specific language for writing grammars. The Ohm library is the JavaScript interface that turns a grammar into a parser, an interpreter or a compiler. Both live in the same package, which is why the documentation treats them as one toolkit rather than a generator plus a separate runtime. Grammars are written in a parsing expression grammar notation, a formal way of describing syntax that the README positions as similar to regular expressions and context-free grammars. In practice a grammar is a value you construct rather than a file a tool compiles into a bundle:
const myGrammar = ohm.grammar(String.raw`
MyGrammar {
greeting = "Hello" | "Hola"
}
`);Recognizing input is then one call on the object you get back, and the result tells you which of two things happened:
const userInput = 'Hello';
const m = myGrammar.match(userInput);
if (m.succeeded()) {
console.log('Greetings, human.');
} else {
console.log("That's not a greeting!");
}No build step sits between the grammar and the parse, which is why the editor at ohmjs.org/editor can hand you a working parser with nothing installed.
Left recursion works, so operators read the way you write them
Two features decide whether a grammar feels natural to write. The first is full support for left-recursive rules, which lets you define a left-associative operator the way you would type it instead of rewriting the recursion into a right-recursive shape or a separate precedence table. The second is the object-oriented grammar extension, where one grammar extends another and adds syntax to it, so a dialect becomes a small override of a base language rather than a fork of the file. Both solve an inheritance or associativity problem, and both are the reason a grammar written in Ohm can look like the syntax it describes. The notation carries a consequence worth stating plainly: PEG rules use ordered choice, so once an alternative succeeds the ones after it are not tried, and a rule set for a language with genuine ambiguity has to remove the ambiguity rather than describe it.
Semantics live in separate modules, not inside grammar rules
Semantic actions are the part of a parser that does something with what was recognized, and Ohm separates them from the grammar completely, which the README lists among the reasons to pick it over similar tools. A rule says what the text looks like; a separate JavaScript operation says what it means. Nothing in the grammar file needs dictionary literals or inline code. The price is that a grammar on its own recognizes without interpreting: a new data format gives you a parse result and nothing else until you write the operations yourself. The README points at two runnable starting points, a basic parsing example and an arithmetic example with semantics, both on JSFiddle, and the repository carries a semantics folder under examples/ next to prettyPrint.mjs for printing what came out.
Four ways in, and the script tag is the loosest of them
Installation splits by runtime. In a browser you add one script tag, which creates a global variable named `ohm`:
<!-- Minified version, for faster page loads -->
<script src="https://unpkg.com/ohm-js@17/dist/ohm.min.js"></script>For Node.js you install the package with `npm install ohm-js`, or `yarn add ohm-js`, or `pnpm add ohm-js`, and then either module system works:
const ohm = require('ohm-js');import * as ohm from 'ohm-js';Deno has its own line, which again goes through unpkg:
import * as ohm from 'https://unpkg.com/ohm-js@17';The trade is versioning and bundling. The npm route is the only one that hands a bundler a real dependency to resolve and pin. The script tag and the Deno import both name the ohm-js@17 major on unpkg and nothing finer, and the browser route puts the whole library on the page as a global, which is fine for a page that only parses one thing and awkward for a page that parses several.
The ohmjs/ohm image is how you run the CLI without Node.js
The README gives one CLI example, and its purpose is to remove the Node.js prerequisite rather than to show the whole command surface:
docker run --rm -v $(pwd):/local ohmjs/ohm:latest compile my-grammar.ohmCompile is the verb on show, the argument is a grammar file with the .ohm extension, and the current directory is mounted into the container so the grammar can be read. Two details matter. The image is tagged ohmjs/ohm:latest, so this documented command follows whatever the newest published image happens to be, and there is no way to ask for an older one in the line as written. Building the image locally and setting up a development container are covered separately in doc/docker.md, so the route from running one command to working on Ohm itself is a documented second step rather than an obvious one. If you only need the library, none of this applies.
A failed match tells you nothing, which is what trace() is for
Debugging a parsing expression grammar means finding out which rules were tried and which one gave up, and Ohm ships two tools for that. The in-process one is the `trace()` method on a grammar, which produces a text trace. The other is graphical: the visualizer makes the entire execution of the parser visible, and the README links an article on visualizing packrat parsing to explain the model underneath. The need for both is visible in the API. A match returns a MatchResult object, and its `succeeded()` and `failed()` methods say only whether the input was recognized. The README's visible examples show only those two methods, so the trace is how a mismatch becomes an edit. The visualizer sits in the web editor, which the README also names as the easiest way to get started.
A private workspace root, and examples/ as the real documentation
The repository shape surprises people who expect to clone a package. The top level is a pnpm workspace rather than a publishable package: the root package.json is marked private with version 0.0.0, and the real build is `pnpm -r build` across packages/. The published names show up in the scripts, where `pnpm --filter ohm-js --filter @ohm-js/compiler api-report` checks the API surface, and publish:wasm-beta covers three packages: ohm-js, @ohm-js/compiler and @ohm-js/to-ast-compat. A WebAssembly compiler and an AST compatibility layer ship alongside the core library. The examples directory carries the grammars people actually read: csv, ecmascript, incremental, indentation-sensitive, markdown, math, nl-datalog-syntax, operators, simple-lisp, typescript and viz, and the README calls the math example extensively commented. The last push was on 2026-06-19, and v17.5.0 and v17.4.0 both landed in February 2026.
Editorial conclusion
Adopt Ohm when your grammar changes faster than your build pipeline, when you need left recursion, or when you want semantics in ordinary JavaScript modules instead of inside grammar rules. Skip it when a parser generated once at build time with no runtime dependency is what you need. Verify first that `ohm.grammar()` accepts your existing rules: paste them into the editor at ohmjs.org/editor and read the trace of a deliberate failure before writing any semantics.
Frequently asked questions
What is Ohm used for in JavaScript?
Ohm is a parsing toolkit made of a library and a domain-specific language, used to parse custom file formats and to build parsers, interpreters and compilers for programming languages. The Ohm language is based on parsing expression grammars, and the library gives you a JavaScript interface for the grammars you write.
How do I use Ohm without installing a package?
In a browser, add a single script tag from unpkg, either ohm.js or the minified ohm.min.js under ohm-js@17, and you get a global variable named `ohm`. For the command line without a local Node.js, the documented route is the ohmjs/ohm Docker image, and the web editor at ohmjs.org/editor needs no installation at all.
Does Ohm support left recursion and grammar inheritance?
Yes to both. Left-recursive rules are fully supported, so left-associative operators can be defined the natural way, and an object-oriented grammar extension lets one grammar add syntax to another.
Which module systems does Ohm work with in JavaScript?
In Node.js both work: `const ohm = require('ohm-js')` for CommonJS and `import * as ohm from 'ohm-js'` as an ES module. Deno imports the same major version from unpkg as a remote URL.
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/ohmjs-ohm)Star history chart for your README
[](https://hysenlabs.com/projects/ohmjs-ohm)Was this useful?