A self-contained TypeScript wrapper around the official IUPAC InChI C library compiled to WebAssembly. Convert MDL Molfiles to InChI/InChIKey and back, in Node and in the browser, without any external file or fetch — the WASM binary is gzip-compressed and base64-embedded inside the package.
Try it in the browser on inchi.cheminfo.org — the playground, its file-conversion API and their sources live in cheminfo/inchi.cheminfo.org.
npm install inchi-jsimport {
inchiFromMolfile,
inchikeyFromInchi,
molfileFromInchi,
} from 'inchi-js';
const ethanol = `
Mrv2014 01010100002D
3 2 0 0 0 0 999 V2000
0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
1.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
2.0000 0.0000 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0 0 0 0
2 3 1 0 0 0 0
M END
`;
const { inchi } = await inchiFromMolfile(ethanol);
// → 'InChI=1S/C2H6O/c1-2-3/h3H,2H2,1H3'
const { inchikey } = await inchikeyFromInchi(inchi);
// → 'LFQSCWFLJHTTHZ-UHFFFAOYSA-N'
const { molfile } = await molfileFromInchi(inchi);
// → reconstructed MolfileEvery conversion is async because the WASM module is initialised
lazily on first call (and cached forever after).
inchiFromMolfile(molfile: string, options?: {
/** Raw InChI option string, e.g. '-AuxNone -DoNotAddH'. Default: '' */
options?: string;
}): Promise<{
returnCode: -1 | 0 | 1;
inchi: string;
auxinfo: string;
message: string;
log: string;
}>;Wraps MakeINCHIFromMolfileText. returnCode === 0 means success;
1 is a warning (the result is still usable, see message/log);
-1 is an error.
The InChI option string is documented in the IUPAC InChI
Technical Manual.
Common options include -AuxNone, -DoNotAddH, -FixedH, -RecMet,
-SUU, -SLUUD.
inchikeyFromInchi(inchi: string): Promise<{
returnCode: -1 | 0 | 1;
inchikey: string;
message: string;
}>;Wraps GetINCHIKeyFromINCHI. Returns the 27-character InChIKey for a
given InChI string.
molfileFromInchi(inchi: string, options?: {
options?: string;
}): Promise<{
returnCode: -1 | 0 | 1;
molfile: string;
message: string;
log: string;
}>;Wraps GetStructFromINCHIEx + GetINCHIEx with -OutputSDF.
Reconstructs an MDL Molfile from an InChI string.
molfileFromAuxinfo(auxinfo: string, options?: {
/** Do not add explicit hydrogens. Default: false */
doNotAddH?: boolean;
/** Differentiate "unknown" from "undefined" stereo. Default: false */
diffUnkUndfStereo?: boolean;
}): Promise<{
returnCode: -1 | 0 | 1;
molfile: string;
message: string;
log: string;
}>;Wraps Get_inchi_Input_FromAuxInfo. The MDL chiral flag stored in the
AuxInfo is preserved when emitting the Molfile.
structureFromInchi(inchi: string, options?: {
/** Raw option string passed verbatim to `GetStructFromINCHIEx`. Default: '' */
options?: string;
}): Promise<{
returnCode: -1 | 0 | 1;
atoms: StructureAtom[];
stereo: StructureStereo[];
message: string;
log: string;
}>;Wraps GetStructFromINCHIEx and returns the raw connection table —
atoms with their adjacency lists and the 0D stereo descriptors —
without going through a Molfile.
oclMoleculeFromInchi(inchi: string, OCL: typeof import('openchemlib'), options?: {
options?: string;
}): Promise<{
molecule: Molecule | null;
returnCode: -1 | 0 | 1;
message: string;
log: string;
}>;Builds an openchemlib
Molecule (with 2D coordinates and wedge bonds) from an InChI.
openchemlib is an optional peer dependency: the module is passed
in by the caller, so a project that never calls this function does not
have to install it.
import * as OCL from 'openchemlib';
import { oclMoleculeFromInchi } from 'inchi-js';
const { molecule } = await oclMoleculeFromInchi('InChI=1S/H2O/h1H2', OCL);oclMoleculeFromStructure(structure, OCL) does the same conversion
from an already-parsed structureFromInchi result.
Eagerly preloads the WASM module so the first conversion isn't slowed
by the (one-time, ~100ms) instantiation. Returns the underlying
Emscripten Module object.
import { loadInchiWasm } from 'inchi-js';
await loadInchiWasm();The library is intentionally fetch-free. At build time (build/build-wasm.sh):
- The IUPAC InChI C source from
vendor/inchi/INCHI-1-SRC/{INCHI_BASE,INCHI_API/libinchi}/src/*.cis linked withbuild/inchi_web.c(a JSON-emitting wrapper adapted fromIUPAC-InChI/InChI-Web-Demo) via Emscripten + CMake. - The resulting
inchi.wasmis gzip-compressed (level: 9) and base64-encoded intosrc/wasm/data.ts. - The Emscripten JS glue is rewritten as an ES module in
src/wasm/glue.tsand the factory function is given the decoded bytes via thewasmBinaryoption — bypassingfetchentirely. The runtime bridge that decodes the bytes and instantiates the module lives insrc/wasm/loadWasm.ts.
The generated data.ts and glue.ts files are committed, so
consumers never need a C toolchain.
npm test runs the full upstream IUPAC test corpus against the
WebAssembly build, in addition to the small canonical tests under
src/__tests__/:
| Folder | Mirrors | Coverage |
|---|---|---|
__tests__/regression/inchiSdf.test.ts |
INCHI-1-TEST/tests/test_library/data/ci/inchi.sdf.gz |
2,190 structures — every InChI must equal the reference SQLite snapshot, plus InChIKey parity on the first 100. Honors the upstream expected_failures list (4 known regressions). |
__tests__/regression/mculeSdf.test.ts |
INCHI-1-TEST/tests/test_library/data/ci/mcule.sdf.gz |
2,000 mcule.com structures vs. the reference SQLite snapshot. |
__tests__/executable/github52.test.ts |
test_executable/test_github_52.py |
V3000 empty bond block parsing. |
__tests__/executable/testIo.test.ts |
test_executable/test_io.py |
V3000 I/O edge cases: SCSR rejection, >999-atom rejection, -LargeMolecules switch, 999-atom acceptance. |
__tests__/executable/organometallicsPubchem.test.ts |
test_executable/test_organometallics_pubchem.py |
Every structure in the PubChem organometallics fixture must yield an InChI under -RecMet. |
__tests__/executable/aromaticIons.test.ts |
test_executable/test_aromatic_ions.py |
Three aromatic-bond cation/anion cases — xfail in upstream and here (test.fails). |
Out of scope: the upstream
INCHI-1-TEST/tests/test_unit/
C++ unit tests (test_strutil.cpp, test_ichican2.cpp, …) exercise
internal C functions that are not part of the public InChI API
exposed via WebAssembly, and would require a separate native build.
The upstream test_executable cases that depend on InChI CLI stderr
parsing (test_alex_clark_structures, test_organometallics_ccdc,
test_github_67, test_github_40, test_pubchem_107) are all marked
xfail upstream and are skipped here as well — they document broken
behavior rather than asserted invariants.
benchmark/bench-inchi.ts runs the WASM
build against the official IUPAC inchi-1 executable on the same
2,190-structure corpus used by the regression tests
(vendor/inchi/INCHI-1-TEST/tests/test_library/data/ci/inchi.sdf.gz,
avg ~3.7 KB / molfile). It reports three modes side by side:
- WASM, per molfile — the realistic JS use case: one
inchiFromMolfile()call per structure inside a single Node process. - Native, batch — the realistic CLI use case: one
inchi-1process that walks the whole SDF. - Native, per call — for a 50-structure sample, spawn
inchi-1once per molfile. This isolates the per-structure C kernel cost from process-startup overhead.
Results on an Apple M1 with inchi-1 1.07.5 and Node 26:
| Mode | ms / structure | structures / s |
|---|---|---|
WASM inchi-js, per molfile |
0.69 | ~1,450 |
Native inchi-1, batch (1 process for the whole SDF) |
0.43 | ~2,325 |
Native inchi-1, per molfile (separate process per call) |
~3.1 | ~320 |
- WASM is ~1.6× slower than the native binary running in batch mode.
- Spawning
inchi-1per molfile is ~7× slower than batching — the per-call mode exists to make that anti-pattern visible. - 2,188 / 2,190 InChIs match the native binary byte-for-byte; the two differences correspond to the known upstream regressions documented in inchiSdf.test.ts.
- WASM cold start (module instantiation + gzip decompression) is ~60 ms and only paid once per process.
Run it with:
npm run benchmark # full 2,190-structure corpus
node benchmark/bench-inchi.ts <sdf-path> [limit] # custom corpus / limit
INCHI_BIN=/path/to/inchi-1 npm run benchmark # use a specific native binaryThe IUPAC InChI C source is a git submodule under
vendor/inchi/, pinned to a known commit of
IUPAC-InChI/InChI. It carries
both the C sources the WASM is compiled from and the reference corpora
the regression tests run against, so a checkout needs it:
git clone --recurse-submodules https://github.com/cheminfo/inchi-js.git
# or, in an existing clone:
git submodule update --init --recursive| Command | Effect |
|---|---|
npm run test-only |
The vitest suite, including the IUPAC regression corpora. |
npm test |
Tests, the same suite against the bundle, type-check, eslint and prettier. |
npm run tsc |
Bundle src/ into lib/ (ESM + minified + .d.ts). |
npm run test-bundle |
Run the suite against lib/inchi-js.js instead of the source. |
npm run build-wasm |
Rebuild the embedded WASM from vendor/inchi/ (needs emscripten + cmake). |
npm run benchmark |
Compare the WASM build with the native inchi-1 binary. |
The suite needs Node 22 or later: the regression references are read
with node:sqlite.
You only need this if you bump the InChI C version or change
build/inchi_web.c. Requirements:
- Emscripten ≥ 3 (
emcc,emcmake) - CMake ≥ 3.15
git submodule update --init --recursive
npm run build-wasm # rebuilds src/wasm/data.ts + src/wasm/glue.ts
npm run tsc # recompiles the lib/ output
npm test # runs the test suite to verifyMIT — Copyright (c) cheminfo. See LICENSE for the full text and the acknowledgement of the bundled IUPAC InChI software (also MIT, Copyright (c) 2024 InChI Project).