Purifai

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.

Zero runtime dependencies23,689 B minified ESMMIT

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.

LimitDefaultBounds
input1,000,000Input code units consumed
output250,000Output code units emitted
depth64Live structural nesting
token65,536Aggregate 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 a result promise. Chunk-invariant output.
escapeHtmlText(text) → string
Losslessly encodes &, <, >, " and ' for an HTML text node.
PurifaiLimitError
Extends RangeError; exposes kind, limit and observed.

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.