Documentation
Defuddle extracts the main content from web page and returns clean Markdown or HTML.
Installation
1npm install defuddleFor Node.js use, install a DOM implementation:
1npm install defuddle linkedomOr JSDOM:
1npm install defuddle jsdomTo use the CLI globally, install with -g, or use npx to run without installing globally:
1# Install globally2npm install -g defuddle34# Or use npx5npx defuddle parse https://example.com/articleBrowser use
In the browser, create a Defuddle instance with a Document object and call parse().
1import Defuddle from 'defuddle';23const result = new Defuddle(document).parse();45console.log(result.content); // cleaned HTML string6console.log(result.title); // page title7console.log(result.author); // author nameYou can also parse HTML strings using DOMParser:
1const parser = new DOMParser();2const doc = parser.parseFromString(htmlString, 'text/html');3const result = new Defuddle(doc).parse();Pass options as the second argument:
1const result = new Defuddle(document, {2 url: 'https://example.com/article',3 debug: true4}).parse();Node.js use
The Node.js API accepts a DOM Document from any implementation (linkedom, JSDOM, happy-dom, etc.) and returns a promise.
1import { parseHTML } from 'linkedom';2import { Defuddle } from 'defuddle/node';34const { document } = parseHTML(htmlString);5const result = await Defuddle(document, 'https://example.com/article', {6 markdown: true7});Or with JSDOM:
1import { JSDOM } from 'jsdom';2import { Defuddle } from 'defuddle/node';34const dom = new JSDOM(htmlString, { url: 'https://example.com/article' });5const result = await Defuddle(dom.window.document, 'https://example.com/article');Note: For defuddle/node to import properly, your package.json must have "type": "module".
CLI use
Defuddle includes a CLI for parsing web pages from the terminal. You can run it with npx or install it globally with npm install -g defuddle.
1# Parse a local HTML file2npx defuddle parse page.html34# Parse a URL5npx defuddle parse https://example.com/article67# Output as markdown8npx defuddle parse page.html --markdown910# Output as JSON with metadata11npx defuddle parse page.html --json1213# Extract a specific property14npx defuddle parse page.html --property title1516# Save output to a file17npx defuddle parse page.html --output result.htmlCLI options
| Option | Alias | Description |
|---|---|---|
--output <file> | -o | Write output to a file instead of stdout |
--markdown | -m | Convert content to markdown |
--md | Alias for --markdown | |
--json | -j | Output as JSON with metadata and content |
--property <name> | -p | Extract a specific property |
--debug | Enable debug mode |
Using templates
Use Knap to format extracted content and metadata with a Markdown template. Pipe Defuddle's JSON output into Knap:
1npx defuddle parse https://example.com/article --markdown --json \2 | npx knap render template.md --data - --output note.mdYour template.md can reference Defuddle properties such as {{ title }} and {{ content }}. See the Knap CLI docs for a complete example.
API use
Use the hosted API to extract a web page without installing Defuddle. Append the page URL to https://defuddle.md/:
1curl https://defuddle.md/stephango.comThe response is Markdown with YAML frontmatter containing page metadata. You can include the target URL's https:// prefix and path, for example https://defuddle.md/https://stephango.com/saw.
To return clean HTML instead, add /html/ before the target URL:
1curl https://defuddle.md/html/stephango.comFor additional requests, buy a request block and pass your API key in the Authorization header. See pricing for request blocks, usage checks, and top-ups.
1curl -H "Authorization: Bearer YOUR_KEY" https://defuddle.md/stephango.comOptions
Options can be passed when creating a Defuddle instance (browser) or as the third argument (Node.js).
| Option | Type | Default | Description |
|---|---|---|---|
url | string | URL of the page being parsed | |
markdown | boolean | false | Convert content to Markdown |
separateMarkdown | boolean | false | Keep content as HTML and return Markdown in contentMarkdown |
removeExactSelectors | boolean | true | Remove elements matching exact selectors (ads, social buttons, etc.) |
removePartialSelectors | boolean | true | Remove elements matching partial selectors |
removeHiddenElements | boolean | true | Remove elements hidden via CSS (display:none, visibility:hidden, etc.) |
removeLowScoring | boolean | true | Remove non-content blocks by scoring (navigation, link lists, etc.) |
removeSmallImages | boolean | true | Remove small images (icons, tracking pixels, etc.) |
removeImages | boolean | false | Remove images from the output |
useAsync | boolean | true | Allow async extractors to fetch from third-party APIs when no local content is available. |
standardize | boolean | true | Standardize HTML (footnotes, headings, code blocks, etc.) |
contentSelector | string | CSS selector to use as the main content element, bypassing auto-detection | |
language | string | Preferred language (BCP 47 tag, e.g. en, fr). Sets Accept-Language header and selects transcript language. | |
includeReplies | boolean | 'extractors' | 'extractors' | Include replies: 'extractors' for site-specific extractors only, true for all, false for none |
debug | boolean | false | Enable debug logging and return debug info in the response |
Response
The parse() method returns an object with the following properties:
| Property | Type | Description |
|---|---|---|
content | string | Cleaned HTML string of the extracted content |
contentMarkdown | string | Markdown version (when separateMarkdown is true) |
title | string | Title of the article |
description | string | Description or summary |
author | string | Author of the article |
site | string | Name of the website |
domain | string | Domain name of the website |
favicon | string | URL of the website's favicon |
image | string | URL of the article's main image |
language | string | Language of the page in BCP 47 format (e.g. en, en-US) |
published | string | Publication date |
wordCount | number | Number of words in the extracted content |
parseTime | number | Time taken to parse in milliseconds |
metaTags | object[] | Meta tags from the page |
schemaOrgData | object | Schema.org data extracted from the page |
extractorType | string | Type of site-specific extractor used, if any |
debug | object | Debug info including content selector and removals (when debug: true) |
Bundles
Defuddle is available in three bundles:
| Bundle | Import | Description |
|---|---|---|
| Core | defuddle | Browser usage. No dependencies. Handles math content but without MathML/LaTeX conversion fallbacks. |
| Full | defuddle/full | Includes math equation parsing (MathML ↔ LaTeX) and Markdown conversion via Turndown. |
| Node.js | defuddle/node | For Node.js. Accepts any DOM Document (linkedom, JSDOM, happy-dom, etc.). Includes full capabilities for math and Markdown conversion. |
The core bundle is recommended for most use cases.
HTML standardization
Defuddle standardizes HTML elements to provide a consistent input for downstream tools like Markdown converters.
Headings
- The first H1 or H2 is removed if it matches the title.
- H1s are converted to H2s.
- Anchor links in headings are removed.
Code blocks
Code blocks are standardized. Line numbers and syntax highlighting are removed, but the language is retained.
1<pre>2 <code data-lang="js" class="language-js">3 // code4 </code>5</pre>Footnotes
Inline references and footnotes are converted to a standard format using sup, a, and an ordered list with class="footnote".
Math
Math elements, including MathJax and KaTeX, are converted to standard MathML with a data-latex attribute containing the original LaTeX source.
Callouts
Callout and alert elements from various sources are standardized to the Obsidian callout format. When converting to Markdown, these become Obsidian-style callouts.
Supported sources:
- GitHub markdown alerts (
div.markdown-alert) - Obsidian callouts (
div.callout[data-callout]) - Callout asides (
aside.callout-*) - Bootstrap alerts (
div.alert.alert-*)
1<div data-callout="info" class="callout">2 <div class="callout-title">3 <div class="callout-title-inner">Info</div>4 </div>5 <div class="callout-content">6 <p>This is an informational callout.</p>7 </div>8</div>Debugging
Debug mode
When debug mode is enabled:
- Returns a
debugfield in the response with detailed information about content extraction - More verbose console logging about the parsing process
- Preserves HTML class and id attributes that are normally stripped
- Retains all
data-*attributes - Skips div flattening to preserve document structure
1const result = new Defuddle(document, { debug: true }).parse();23// CSS selector path of chosen main content element4console.log(result.debug.contentSelector);56// Array of removed elements with step, reason, selector, and text preview7console.log(result.debug.removals);The debug field contains:
| Property | Type | Description |
|---|---|---|
contentSelector | string | CSS selector path of the chosen main content element |
removals | array | List of elements removed during processing |
Each removal entry contains:
| Property | Type | Description |
|---|---|---|
step | string | Pipeline step (e.g. removeLowScoring, removeBySelector, removeHiddenElements) |
selector | string | CSS selector or pattern that matched |
reason | string | Why the element was removed (e.g. score: -20, display:none) |
text | string | First 200 characters of removed element's text content |
Pipeline toggles
Disable individual pipeline steps to diagnose content extraction issues:
1// Skip content scoring2const result = new Defuddle(document, { removeLowScoring: false }).parse();34// Skip hidden element removal5const result = new Defuddle(document, { removeHiddenElements: false }).parse();67// Skip small image removal8const result = new Defuddle(document, { removeSmallImages: false }).parse();910// Skip HTML standardization11const result = new Defuddle(document, { standardize: false }).parse();Content selector
Use contentSelector to bypass auto-detection and specify the main content element directly. Falls back to auto-detection if the selector doesn't match.
1const result = new Defuddle(document, {2 contentSelector: 'article.post-content'3}).parse();