mrkeyoor.com_
Sat 08 Aug 21:01 UTC
npmSecurityupdated 08 Aug 2026

isomorphic-dompurify

isomorphic-dompurify packages DOMPurify behind one import that works in browsers and Node.js. Browsers use the real window and DOM; Node uses jsdom to create the DOM tree DOMPurify needs. The exported sanitizer, configuration methods, hooks, types, and factory follow DOMPurify, while the wrapper adds server lifecycle handling through clearWindow. Its job is to remove unsafe HTML, SVG, and MathML markup at an SSR or universal JavaScript boundary, not to encode plain text or validate URLs by itself.

Verdict

A practical wrapper when one import must work in SSR and the browser, with the same well-studied DOMPurify policy. Browser-only projects should install DOMPurify itself, and server users must accept jsdom's weight, runtime floor, lifecycle cleanup, and module-compatibility testing.

API stability3/5The wrapper mirrors DOMPurify's familiar sanitize, hook, configuration, inspection, factory, and type APIs, and version 3.22.0 adds a clear server cleanup method without changing that shape. However, the README explicitly says DOMPurify does not follow semantic versioning, so this wrapper releases every upstream change as a minor and cannot promise that minor updates are behavior-only.
Docs4/5The README explains why jsdom is needed, maps wrapper versions to Node requirements, shows default and named imports, configuration, a custom window factory, TypeScript hook types, clearWindow behavior, web-worker limits, playgrounds, a current CommonJS issue, and both licenses. Full sanitizer policy details live in DOMPurify's separate README, which readers must study for security-sensitive options.
Maintenance5/5npm shows version 3.22.0 published in August 2026, the GitHub repository was pushed the same week, and it is not archived. The package tracks current DOMPurify and jsdom versions quickly and has only two open items in GitHub's combined issues-and-pull-requests count. Frequent changes are appropriate for security dependencies, though they make update testing mandatory.
Ecosystem5/5The wrapper recorded 5,316,483 downloads in the latest measured week and sits directly on DOMPurify, whose repository has more than seventeen thousand stars and extensive browser security testing. Conditional browser and Node exports, CommonJS and ESM entries, TypeScript re-exports, framework playgrounds, and Trusted Types support cover the common SSR ecosystem despite jsdom's substantial footprint.

Use it if

  • The same module must sanitize user-authored markup during server rendering and again in the browser
  • You want DOMPurify's allowlist, profiles, hooks, Trusted Types support, and TypeScript types without hand-wiring jsdom in every Node entry
  • Your SSR framework respects conditional exports so Node receives the jsdom build and browser bundles receive the browser build
  • You can stay on the package's narrow current Node versions and actively update sanitizer dependencies when security fixes ship
Skip it if

Setup reality

`npm install isomorphic-dompurify` installs DOMPurify 3 and jsdom 30. The current package engine is unusually strict: Node `^22.22.2 || ^24.15.0 || >=26.0.0`, so many otherwise supported Node 22 and 24 deployments cannot install 3.22.0 without upgrading. The package publishes conditional Node and browser entries in both import and require forms, but its README still documents a CommonJS `ERR_REQUIRE_ESM` failure caused by jsdom dependencies and suggests a package-manager override to jsdom 25.0.1 for affected deployments. Test the exact framework, bundler, and module mode you ship. Server sanitization owns a long-lived jsdom window; the README says DOM state can accumulate and cause progressive slowdown and memory growth. Call `clearWindow()` at a measured boundary such as after a batch, but remember it closes the window and discards hooks and persistent config, so reapply them afterward. DOMPurify does not follow semantic versioning according to this project's README, so wrapper changes are released as minor versions even when the upstream patch might alter behavior. Pin and test sanitizer updates with malicious regression fixtures. Use the default configuration unless requirements force a narrower allowlist. Treat `removed` as debugging information only, never as proof that input was safe. Sanitize at the final markup boundary, do not mutate the result with libraries that can reintroduce unsafe nodes, and still apply normal URL, CSP, template, and output-context controls.

Patterns

Sanitize untrusted HTMLsanitize-html

import DOMPurify from 'isomorphic-dompurify';

const dirty = '<img src=x onerror=alert(1)><p>Hello</p>';
const clean = DOMPurify.sanitize(dirty);
renderHtml(clean);

Sanitize at the last markup boundary and do not pass the result through code that can add unsafe attributes or nodes afterward.

Restrict output to the HTML profileuse-html-profile

import {sanitize} from 'isomorphic-dompurify';

const clean = sanitize(input, {
  USE_PROFILES: {html: true},
});

USE_PROFILES overrides ALLOWED_TAGS. This profile excludes SVG and MathML rather than adding to a custom tag list.

Allow only a small formatting subsetallow-minimal-markup

import {sanitize} from 'isomorphic-dompurify';

const clean = sanitize(input, {
  ALLOWED_TAGS: ['p', 'strong', 'em', 'a'],
  ALLOWED_ATTR: ['href'],
});

A narrower allowlist is easier to reason about. URL policy still deserves tests for every scheme and context your application permits.

Remove style tags and attributesforbid-style-content

import {sanitize} from 'isomorphic-dompurify';

const clean = sanitize(input, {
  FORBID_TAGS: ['style'],
  FORBID_ATTR: ['style'],
});

This adds restrictions to the default policy. It is not a complete CSS security policy for markup you later modify.

Remove target attributes with a typed hookstrip-link-targets

import {addHook, removeHook, sanitize, type NodeHook} from 'isomorphic-dompurify';

const stripTarget: NodeHook = node => {
  if ('removeAttribute' in node) node.removeAttribute('target');
};
addHook('afterSanitizeAttributes', stripTarget);
const clean = sanitize(input);
removeHook('afterSanitizeAttributes', stripTarget);

Hooks are global to the shared instance. Remove them after scoped work, and reapply needed hooks after clearWindow on the server.

Inspect what the last call removedinspect-removed-items

import DOMPurify from 'isomorphic-dompurify';

const clean = DOMPurify.sanitize(input);
for (const item of DOMPurify.removed) {
  console.debug('sanitizer removed', item);
}

DOMPurify explicitly says removed is only a curiosity and must not drive security decisions. It describes the most recent shared-instance sanitization.

Set and clear a persistent configurationset-shared-config

import {clearConfig, sanitize, setConfig} from 'isomorphic-dompurify';

setConfig({USE_PROFILES: {html: true}});
try {
  const clean = sanitize(input);
} finally {
  clearConfig();
}

While setConfig is active, extra configuration passed to sanitize is ignored. Shared mutable config is risky under concurrent request code.

Release jsdom state after a batchclear-server-window

import {clearWindow, sanitize} from 'isomorphic-dompurify';

try {
  for (const record of records) record.clean = sanitize(record.html);
} finally {
  clearWindow();
}

On Node this closes and recreates the internal jsdom window. Hooks and setConfig state must be reapplied; in browsers the call is a no-op.

Create an isolated sanitizer instancebind-explicit-window

import createDOMPurify from 'isomorphic-dompurify';
import {JSDOM} from 'jsdom';

const window = new JSDOM('').window;
const purify = createDOMPurify(window);
try {
  const clean = purify.sanitize(input);
} finally {
  window.close();
}

The factory mirrors DOMPurify and is useful for isolated tests or policies, but importing jsdom directly makes lifecycle ownership your responsibility.

Return a DOM fragmentreturn-dom-fragment

import {sanitize} from 'isomorphic-dompurify';

const fragment = sanitize(input, {RETURN_DOM_FRAGMENT: true});
for (const child of fragment.childNodes) {
  inspectNode(child);
}

The returned nodes belong to the active browser or jsdom window. Do not reuse them after clearWindow closes the server window.

Request Trusted Types output in browsersreturn-trusted-html

import {sanitize} from 'isomorphic-dompurify';

const trusted = sanitize(input, {RETURN_TRUSTED_TYPE: true});
element.innerHTML = trusted;

TrustedHTML is returned only where the Trusted Types API is available. Keep CSP and an application Trusted Types policy aligned with this path.

Regression-test sanitizer behaviortest-malicious-fixtures

import {sanitize} from 'isomorphic-dompurify';
import {expect, test} from 'vitest';

test('removes event handlers', () => {
  const clean = sanitize('<a href="/" onclick="steal()">go</a>');
  expect(clean).not.toContain('onclick');
});

Keep fixtures for your exact config and markup sinks. Upstream behavior can change in wrapper minor releases because DOMPurify does not follow semantic versioning.

Alternatives

PackageRegistryPick it when
dompurifynpmYou sanitize in a browser or are willing to create and manage the server DOM instance yourself
sanitize-htmlnpmYou need a Node-focused parser sanitizer with explicit tag, attribute, iframe, and URL-scheme policies
xssnpmYou want a smaller Node-oriented whitelist sanitizer and its parsing and policy model fits your threat cases