Purifai
Readable text from hostile HTML — without a DOM.
A fixed-policy HTML-to-text converter for servers, browsers and edge runtimes. It keeps useful document structure, drops non-reader bodies, and enforces input, output, nesting and retained-token limits while scanning.
What a flat tag remover gets wrong
Stripping angle brackets leaves the script body behind and collapses
the text that mattered. Purifai drops the body and formats the reader content.
Input
<script>alert(1)</script>
<h2>Release</h2>
<ul><li>Fast</li></ul>
toText(input)
Release
- Fast
A flat tag remover can leak alert(1) into your output as visible text.
Install
npm install purifai
Ships ESM and CommonJS. Requires Node.js 22 or newer when used in Node; also runs on Bun, Deno, Cloudflare Workers and browsers.
Quick start
import { toText } from 'purifai';
const html = '<h1>Guide</h1><p>Start <strong>here</strong>.</p>';
const text = toText(html, {
layout: 'readable',
links: 'label',
images: 'alt',
});
// Guide
//
// Start here.
Read this before you render the output
toText returns a JavaScript string. It does not return safe
HTML. Prefer a text sink:
element.textContent = toText(untrustedHtml);
If the only sink available is an HTML text node, escape explicitly:
import { escapeHtmlText, toText } from 'purifai';
element.innerHTML = escapeHtmlText(toText(untrustedHtml));
escapeHtmlText is for an HTML text context only. It does not make a
value safe for an attribute, URL, JavaScript, CSS or template source. A displayed URL is
still text — moving it into href is a separate URL-policy decision.
Bounded conversion
Purifai enforces all four limits before unbounded caller-controlled state can accumulate.
toText throws PurifaiLimitError on a breach;
convert is the only API that can return a deliberately truncated prefix.
| Limit | Default | Bounds |
|---|---|---|
input | 1,000,000 | Input code units consumed |
output | 250,000 | Output code units emitted |
depth | 64 | Live structural nesting |
token | 65,536 | Aggregate retained token and attribute code units |
Values measure UTF-16 code units, not encoded bytes. Truncation is explicit and deterministic, and never emits half of a surrogate pair.
API
toText(html, options?) → string- Converts one HTML string to readable text. Throws on invalid input or a breached limit.
convert(html, options?) → ConversionResult- Text plus a frozen report: completion, truncation, consumed input, output length, dropped-container counts.
createTextTransform(options?) → TextTransform- A native
TransformStream<string, string>with aresultpromise. Chunk-invariant output. escapeHtmlText(text) → string- Losslessly encodes
&,<,>,"and'for an HTML text node. PurifaiLimitError- Extends
RangeError; exposeskind,limitandobserved.
What it deliberately does not do
The fixed scope is the reason to choose it. Purifai does not preserve markup, reconstruct
CSS layout, expose custom formatters, or classify a user's intent. It is a bounded
extraction grammar, not browser tree construction — so no
rowspan/colspan tables, no SVG or MathML semantics, no selector
rules, and no browser-equivalent malformed-markup recovery.
If you need selector-driven formatting, complex table layout, or allow-listed safe HTML, Purifai is the wrong tool and its README says so.