---
title: "Browser-Based Retrosynthesis with RENKIN WebAssembly"
description: "Run RENKIN's retrosynthesis search entirely in the browser via WebAssembly -- no server, no installation. API reference, bundler support, and examples."
---
# WASM / JavaScript API
## Installation
```bash
npm install renkin
```
## Browser (ES Module)
```html
<script type="module">
import init, { find_routes, version } from './node_modules/renkin/renkin.js';
await init();
console.log('RENKIN version:', version());
const raw = find_routes(
"CC(=O)Oc1ccccc1C(=O)O", 5, 3, 0 );
const result = JSON.parse(raw);
console.log('Routes found:', result.routes_found);
</script>
```
## Browser and bundler usage
The npm package is currently built with `wasm-pack build --target web`.
**Supported:**
- Native browser ES modules
- Vite
- Webpack
- Rollup and compatible bundlers
**Not currently supported:**
- Plain Node.js `require()`
- Direct Node.js execution without a bundler
To exercise the WASM API from a plain Node.js script (not through a
bundler), build a `--target nodejs` package from source instead — see
[Minimal Node.js Example](#minimal-nodejs-example-ci-verified) below,
which is verified this way, not against the published npm package.
## `find_routes`
```typescript
function find_routes(
target: string, // Target molecule SMILES
depth: number, // Maximum retrosynthetic depth
max_routes: number, // Maximum routes to return
beam_width: number // A* beam width (0 = unlimited)
): string // JSON-encoded result
```
WASM always uses the compiled-in default rule set (28 hand-crafted rules) and
building blocks — there is no way to load an external templates file or
custom building blocks list from the WASM entry point (unlike the CLI/Python
bindings). See [Rust API](rust.md) or [Python API](python.md) for
`--templates`/`templates_path` support.
**Return value (JSON):**
```typescript
interface Result {
routes_found: number;
routes: Route[];
}
interface Route {
depth: number;
score: number;
confidence: number;
success_probability: number;
convergency: number;
route_cost: number;
building_blocks: string[];
steps: Step[];
}
interface Step {
target: string; // SMILES of target at this step
rule: string; // reaction rule name
template_id: string; // stable template identity (rule:<name> / smirks-sha256:<hex>)
precursors: string[]; // SMILES of precursor molecules
step_confidence: number;
atom_economy_status: string; // "normal" / "above_expected_range" / "not_evaluable" (always present)
// conditions / atom_economy / atom_economy_raw_percent / procedure_hint /
// reaction_family / metadata_source / metadata_scope / evidence are present
// when applicable and simply absent from the JSON otherwise
}
```
## `audit_route_v2`
```typescript
function audit_route_v2(
content: string, // Route export JSON text (RENKIN or AiZynthFinder)
policy: string // "informational" | "standard" | "strict"
): string // JSON-encoded AuditRouteReport, or {"error": "..."}
```
The browser counterpart to `renkin audit-route` (see
[Audit Reproducibility and Compatibility Contract](../guides/audit-reproducibility-contract.md)
for the full `AuditRouteReport`/`audit_manifest` shape, and what each
`policy` value means) — calls the identical
`bridge::build_audit_route_report_with_policy` pipeline the CLI uses, so a
route audited in the browser gets exactly the same verdict the CLI would
produce for the same input and policy. Unlike the CLI, `content` must
already be plain JSON text — there is no gzip support in the browser (a
paste or file upload never needs it). `policy` controls only how each
route's `status` is derived from findings already collected — never which
findings are detected or reported.
```js
import init, { audit_route_v2 } from './node_modules/renkin/renkin.js';
await init();
const routeJson = JSON.stringify({
target: "CCOC(=O)c1ccccc1",
routes: [{
steps: [{ target: "CCOC(=O)c1ccccc1", precursors: ["CCO", "O=C(O)c1ccccc1"], template_id: "co_aliphatic_cleavage" }],
building_blocks: ["CCO", "O=C(O)c1ccccc1"],
}],
});
const report = JSON.parse(audit_route_v2(routeJson, "auto", "", "strict"));
Also available from the [Live Playground](../playground/){ target="_blank" }'s
`[ Audit a Route ]` tab — paste or upload a route (and optionally a stock
list) with a policy selector, entirely client-side.
## `audit_route`
```typescript
function audit_route(content: string, format: string, stockText: string): string
```
The original (v0.28.0) 3-argument export, kept unchanged for backward
compatibility — a thin `policy: "standard"` wrapper around
[`audit_route_v2`](#audit_route_v2). New code should call `audit_route_v2`
directly; `audit_route` exists only so a build predating v0.29.0's policy
parameter keeps working exactly as before.
## `version`
```typescript
function version(): string
```
Returns the RENKIN version string (e.g., `"0.29.0"`).
## Minimal Node.js Example (CI-verified)
This example runs against a package built locally with
`wasm-pack build --target nodejs` — a different build target from the
published npm package (`--target web`, browser/bundler only; see
[Browser and bundler usage](#browser-and-bundler-usage) above). It's the
from-source path for using RENKIN's WASM bindings in a plain Node.js
script; `npm install renkin` alone does not give you this.
`examples/quickstart.mjs` is run against a `wasm-pack build --target nodejs`
output as part of CI, so this call shape can't silently drift from the real API:
```javascript
--8<-- "examples/quickstart.mjs"
```
## Live Playground
An interactive playground is available at [/playground/](../playground/){ target="_blank" }.
The playground runs entirely in WebAssembly in your browser — no network calls, no server.
## Example: React Integration
```jsx
import { useEffect, useState } from 'react';
function RetrosynthesisWidget({ smiles }) {
const [routes, setRoutes] = useState(null);
const [wasmReady, setWasmReady] = useState(false);
useEffect(() => {
import('renkin').then(async (mod) => {
await mod.default();
setWasmReady(true);
});
}, []);
useEffect(() => {
if (!wasmReady || !smiles) return;
import('renkin').then((mod) => {
const raw = mod.find_routes(smiles, 5, 3, 0);
setRoutes(JSON.parse(raw));
});
}, [wasmReady, smiles]);
if (!routes) return <div>Loading...</div>;
return <div>Found {routes.routes_found} routes</div>;
}
```