HTML to Markdown Converter: Webpage to Markdown & JSON
Pricing
from $1.50 / 1,000 page converteds
HTML to Markdown Converter: Webpage to Markdown & JSON
Convert any webpage URL or raw HTML to clean, LLM-ready Markdown and JSON for RAG and AI. Removes boilerplate, keeps headings, tables and links.
Pricing
from $1.50 / 1,000 page converteds
Rating
0.0
(0)
Developer
Grace M. Hartwell
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
20 hours ago
Last modified
Categories
Share
HTML to Markdown Converter turns any public webpage URL or raw HTML into clean, LLM-ready Markdown plus structured JSON. It strips menus, cookie banners, footers and sidebars, keeps the main content (headings, lists, code blocks, tables, links) and returns the page title, meta description, language, heading outline, word count and page metadata. Use it as a website to Markdown tool for RAG pipelines, AI agents, content migration and SEO audits. No browser is used, so runs stay light; you pay $1.50 per 1,000 converted pages, and failed pages are free.
What does HTML to Markdown Converter do?
- Converts webpages to Markdown: fetches each URL in your list once (it does not crawl further links) and converts the HTML to GitHub-flavored Markdown.
- Converts raw HTML to Markdown: paste one HTML document (
rawHtml) or send many (htmlDocuments). Nothing is fetched in this case. - Extracts the main content using Mozilla Readability (the library behind Firefox Reader View) plus extra boilerplate cleanup. If no clear article is found, it falls back to the cleaned
<body>. - Returns structured JSON (HTML to JSON) for each page:
title,description,language,markdown,wordCount,headings, optionallinks, andmetadata(canonical URL, site name, Open Graph type and image, published and modified time, meta keywords, HTTP status, content type). - Reports errors clearly and for free: 404s, timeouts, login or paywall pages (401/402/403), robots.txt-blocked URLs and invalid URLs each get an error item with an
errorCode.
Key features
- 📝 Clean Markdown output: ATX headings, fenced code blocks,
-bullet lists, absolute link URLs, optional images (). - 📊 HTML tables to Markdown tables (GFM). Single-column layout tables become plain paragraphs.
- 🧹 Boilerplate removal:
nav,header,footer,aside, forms, dialogs, buttons, hidden elements, and elements whose class or ID points to cookie, consent, banner, newsletter, sidebar, share, ads, popup, comments and similar blocks. - 🔗 Optional links list: up to 500 unique links per page, each with
url,textand aninternalflag. - 🔒 Privacy by default:
mailto:andtel:links are reduced to plain text and never listed. E-mail addresses in the text are replaced with[email removed](you can turn this off). - 🤝 Polite crawling: robots.txt is respected. By default the Actor makes at most 5 parallel requests and 60 requests per minute, with a 1-second delay between requests to the same domain.
- 💸 Budget control: set Max total charge and the Actor plans its work around it and stops cleanly when the budget is reached.
Use cases
- RAG and AI agents: feed clean Markdown from documentation, blogs or knowledge bases into a vector database or an LLM prompt.
- LLM training and fine-tuning datasets: collect normalized page text with
languageandwordCount, so you can filter by language or length. - Content migration: move HTML pages into a Markdown-based CMS, docs site, static site generator or Git repository.
- SEO content audits: get titles, meta descriptions, H1–H6 heading outlines, canonical URLs and word counts for a list of URLs.
- Archiving and change tracking: store readable Markdown snapshots of policy, pricing or documentation pages on a schedule.
- HTML clean-up: paste messy exported HTML and get Markdown back without fetching anything.
How much does it cost to convert HTML to Markdown?
The Actor uses pay-per-event pricing. You pay only for the events below; there is no separate charge for platform compute.
| Event | Price | When it is charged |
|---|---|---|
page-converted | $0.0015 per page ($1.50 per 1,000) | After a converted page or HTML document is saved to your dataset |
apify-actor-start | $0.00005 per run | Automatically at run start (Apify default price) |
Not charged: 404s, timeouts, access-restricted pages, robots.txt skips, invalid URLs and conversion errors.
Example calculation (1,000 pages):
1,000 pages × $0.0015 = $1.50+ 1 Actor start × $0.00005 = $0.00005 (default 512 MB memory → 1 start event)-------------------------------------------Total ≈ $1.50005 → about $1.50 per 1,000 pages
Smaller run: 10 pages × $0.0015 = $0.015 (+ $0.00005 start).
How to use HTML to Markdown Converter
- Open HTML to Markdown Converter in Apify Store and click Try for free (or Start in Apify Console).
- Add one or more public URLs under URLs, and/or paste HTML into Raw HTML (optionally set Base URL for raw HTML so relative links become absolute).
- Choose the conversion options: main content only, keep links, keep images, convert tables, return a links list, redact e-mail addresses.
- Optional: set Max pages and a Max total charge to cap the cost of the run.
- Click Start and wait for the run to finish.
- Open the Overview or Markdown view of the dataset, or export the results as JSON, CSV, Excel and other formats, or fetch them through the API.
Input example
All fields are optional. Add at least one valid startUrls entry or some HTML in rawHtml / htmlDocuments. If all three are empty (for example an input of {}), the Actor converts the demo page https://example.com/ (1 page, charged like any other page).
{"startUrls": [{ "url": "https://example.com" },{ "url": "https://docs.apify.com/platform/actors" }],"rawHtml": "<h1>Hello</h1><p>World <a href=\"/x\">link</a></p>","rawHtmlBaseUrl": "https://example.org/","htmlDocuments": [{ "html": "<html lang=\"en\"><body><h2>Doc 2</h2><p>Text</p></body></html>", "url": "https://example.org/doc-2" }],"extractMainContent": true,"keepLinks": true,"keepImages": false,"keepTables": true,"includeLinks": true,"redactEmails": true,"maxPages": 50,"requestTimeoutSecs": 30,"maxConcurrency": 5,"maxRequestsPerMinute": 60,"proxyConfiguration": { "useApifyProxy": false }}
| Input field | Type | Default | Description |
|---|---|---|---|
startUrls | array | – (demo: https://example.com/ if no source is given) | Public http(s) URLs to convert. Each one is fetched once. |
rawHtml | string | – | One HTML document to convert without fetching. |
rawHtmlBaseUrl | string | – | Base URL used to make relative links and images in rawHtml absolute. |
htmlDocuments | array | – | Many HTML documents: [{ "html": "...", "url": "optional base URL" }]. |
extractMainContent | boolean | true | Remove boilerplate (Readability + cleanup). false converts the whole <body>. |
keepLinks | boolean | true | Render links as [text](url); off = plain text. |
keepImages | boolean | false | Render images as . |
keepTables | boolean | true | Convert tables to Markdown tables; off = drop tables. |
includeLinks | boolean | false | Add a links array to each result. |
redactEmails | boolean | true | Replace e-mail addresses in the Markdown and description with [email removed]. |
maxPages | integer | 100 | Max URLs + HTML documents converted per run (1–10,000). Also capped by Max total charge. |
requestTimeoutSecs | integer | 30 | Per-page download timeout (5–120). |
maxConcurrency | integer | 5 | Max parallel downloads (1–20). |
maxRequestsPerMinute | integer | 60 | Global rate limit (1–600), plus a 1 s delay per domain. |
proxyConfiguration | object | { "useApifyProxy": false } | Optional Apify Proxy. Not used to bypass logins or paywalls. |
Output example
One dataset item per converted URL or HTML document (this example was produced by a local test run of the Actor):
{"url": "https://example.com/","requestedUrl": "https://example.com/","source": "url","status": "ok","title": "Example Domain","description": null,"language": "en","markdown": "This domain is for use in documentation examples without needing permission. This is not a service; avoid relying on it for testing and monitoring purposes.","wordCount": 25,"headings": [],"links": [],"metadata": {"canonicalUrl": null,"siteName": null,"ogType": null,"ogImage": null,"publishedTime": null,"modifiedTime": null,"keywords": [],"extractionMode": "readability","markdownLength": 156,"statusCode": 200,"contentType": "text/html; charset=utf-8","fetchedAt": "2026-10-25T23:01:56.939Z"},"error": null,"errorCode": null}
Raw HTML input (source: "html") produces the same shape, without requestedUrl, statusCode, contentType and fetchedAt:
{"source": "html","status": "ok","url": "https://example.org/","title": "Rohes HTML","description": "Test ohne Abruf","language": "de","markdown": "# Hallo Welt\n\nEin [relativer Link](https://example.org/pfad). Kontakt: [email removed]\n\n| A | B |\n| --- | --- |\n| 1 | 2 |","wordCount": 12,"headings": [{ "level": 1, "text": "Hallo Welt" }],"links": [{ "url": "https://example.org/pfad", "text": "relativer Link", "internal": true }],"metadata": { "canonicalUrl": null, "siteName": null, "ogType": null, "ogImage": null, "publishedTime": null, "modifiedTime": null, "keywords": [], "extractionMode": "body", "markdownLength": 121 },"error": null,"errorCode": null}
Error item (free):
{ "url": "https://en.wikipedia.org/wiki/This_page_does_not_exist", "source": "url", "status": "error", "errorCode": "NOT_FOUND", "error": "HTTP 404: page not found.", "metadata": { "statusCode": 404 } }
Output fields
| Field | Type | Description |
|---|---|---|
url | string | null | Final URL after redirects (or the base URL of an HTML document). |
requestedUrl | string | URL as requested (URL sources only). |
source | url | html | Where the content came from. |
status | ok | error | Result status. |
title | string | null | <title>, else og:title / twitter:title, else first <h1>. |
description | string | null | Meta description, else og:description / twitter:description. |
language | string | null | <html lang>, content-language or og:locale. |
markdown | string | The converted Markdown. |
wordCount | integer | Words in the Markdown (code blocks and URLs excluded). |
headings | array | { level, text } for H1–H6. |
links | array | Only with includeLinks: { url, text, internal }, max 500. |
metadata | object | canonicalUrl, siteName, ogType, ogImage, publishedTime, modifiedTime, keywords, extractionMode (readability / body), markdownLength; for URLs also statusCode, contentType, fetchedAt. |
error, errorCode | string | null | Error details; null on success. |
Error codes: NOT_FOUND (404/410), ACCESS_RESTRICTED (401/402/403), UNAVAILABLE_FOR_LEGAL_REASONS (451), HTTP_ERROR, ROBOTS_TXT_DISALLOWED, TIMEOUT, NOT_HTML, FETCH_FAILED, INVALID_URL, CONVERSION_FAILED.
Integrations and API
Results are stored in a standard Apify dataset, so you can use everything the Apify platform offers: the Apify API, API clients, schedules, webhooks and the Apify integrations (for example Make, Zapier or n8n).
Run the Actor and get the converted pages in one HTTP call (replace [YOUR_APIFY_TOKEN]):
curl -X POST "https://api.apify.com/v2/acts/grace_hartwell~html-to-markdown-json/run-sync-get-dataset-items?token=[YOUR_APIFY_TOKEN]" \-H "Content-Type: application/json" \-d '{"startUrls":[{"url":"https://example.com"}],"maxPages":10}'
With the JavaScript API client:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('grace_hartwell/html-to-markdown-json').call({startUrls: [{ url: 'https://example.com' }],includeLinks: true,});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items[0].markdown);
FAQ
Does the HTML to Markdown Converter render JavaScript?
No. It parses the HTML the server returns, without a browser. Pages that build their content only in the browser (client-side rendering) may come back short or empty. Check wordCount to spot them.
Can it crawl a whole website to Markdown?
No. Each URL you provide is converted once, and links are not followed. To convert a whole site, pass in a list of URLs (for example from the site's sitemap).
How does main content extraction work?
It uses Mozilla Readability with extra boilerplate cleanup. If Readability finds less than about 120 characters of article text, the cleaned <body> is converted instead. You can see which method was used in metadata.extractionMode (readability or body). Set extractMainContent: false to always convert the full body.
Can I convert raw HTML to Markdown without a URL?
Yes. Use rawHtml for one document or htmlDocuments for many. Nothing is fetched, and the optional base URL is used only to make relative links absolute.
Which pages are free?
Only successfully converted pages are charged. Errors (404, 401/402/403, robots.txt-disallowed, timeouts, non-HTML responses, invalid URLs, conversion failures) are returned as free error items.
Can it get past logins, paywalls or bot protection?
No. It never logs in, never sends cookies and never rotates sessions to get around 401/402/403 responses. Those pages are reported as ACCESS_RESTRICTED.
Why do some table cells look flattened?
Markdown tables can't hold nested HTML, so each cell is converted to plain text (with pipes escaped). Turn off keepTables to drop tables completely.
Does it extract e-mail addresses or phone numbers?
No. mailto: and tel: links are never listed, and e-mail addresses in the text are redacted by default. If a page you choose contains personal data in its body text, you are responsible for having a legal basis (for example under GDPR) to process it.
Limitations: what this Actor does not do
- No JavaScript rendering: it uses a plain HTTP client (Cheerio), not a headless browser.
- No crawling: links on a page are not followed. Duplicate URLs in the input are converted once.
- No logins, cookies, paywall or anti-bot bypass. Proxies are optional and not meant for circumvention.
- Only HTML pages are converted: PDFs, images and other non-HTML files are not converted and come back as free error items (
NOT_HTMLorFETCH_FAILED). - Images are not downloaded: with
keepImageson, onlyreferences are kept.<figcaption>text,<iframe>,<svg>,<canvas>, scripts and styles are dropped. - Tables are simplified: nested markup inside cells becomes plain text, and single-column tables become paragraphs.
- Links list limit: at most 500 links per page; icon-only links (no text),
mailto:/tel:links and non-http(s) links are skipped. - Heuristic boilerplate removal: elements with class or ID names like
related,comments,shareorsidebarare removed, which can occasionally remove wanted content. Turn offextractMainContentif that happens. - No author names, e-mails or phone numbers are extracted into fields.
- Each run is capped by
maxPages(max 10,000) and by your Max total charge.
Author: Grace M. Hartwell