Model or dataset
supermemoryai/markdowner avatar
supermemoryai/markdowner

Markdowner: one GET request turns a web page into LLM markdown

A fast tool to convert any website into LLM-ready markdown data. Built by https://supermemory.ai

2,025 stars149 forksTypeScriptMIT

At a glance

What is it?
Markdowner converts a single web page into markdown for language models, built on Cloudflare Browser Rendering, Durable Objects and Turndown. Self-hosting costs a paid Workers plan and a hand-edited wrangler.toml.
Who is it for?
Adopt Markdowner when your pipeline needs page text shaped for a model and you already run on Cloudflare: the parameters are few, the conversion step is one named library, and the whole Worker fits in a single repository you can read in an afternoon. Do not adopt it when you need storage, indexing or a query layer, since it stops at markdown, nor when the budget forbids a paid Workers plan or you need a crawl deeper than 10 subpages.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 20 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Markdown as the input format behind a retrieval backend

Markdowner was written while its author was building Supermemory, an app where people store website content and then query it with a model. The stated reason for converting anything at all is that data arriving in a structured, predictable form gives the model better answers than the same page handed over as raw markup. That framing narrows the tool sharply, and the boundary is worth stating plainly. The unit of work is one URL per request, not a site: nothing here takes a list of seeds, and nothing keeps a corpus. The audience is an engineer wiring the ingestion step in front of a language model who wants that step to run on their own Cloudflare account rather than handing every target URL to a service they do not control. A public instance answers at `https://md.dhr.wtf`, and the implementation behind it is a single Worker.

One GET request, one required parameter and three toggles

The hosted endpoint takes a single GET request:

bash
$ curl 'https://md.dhr.wtf/?url=https://example.com'

The url parameter is the only required input. Everything else is one of 3 boolean toggles, and every one of them defaults to false: `enableDetailedResponse`, `crawlSubpages` and `llmFilter`. The response type is picked with a header rather than a query string: `Content-Type: text/plain` asks for a plain text body and `Content-Type: application/json` asks for JSON. Two of those toggles deserve a second look. `enableDetailedResponse` is described as returning the full HTML content, so a caller who switches it on stops receiving the markdown the tool is named for and gets page source instead. `crawlSubpages` walks outward from the starting URL but stops at 10 subpages, a ceiling that appears only in the parameter description and not in the feature list.

Browser Rendering opens the page, Turndown strips it

Fetching is not an HTTP GET against the target site. Markdowner uses Cloudflare's Browser Rendering together with Durable Objects to spin up browser instances, then converts what those instances rendered into markdown with Turndown. That choice matters most for pages that assemble their content in the browser, where a plain fetch returns the shell instead of the text a model needs. Your deployment also needs a KV namespace created by hand, named `md_cache`, with the generated IDs pasted into `wrangler.toml`. The documentation does not say how long a body stays in that store. It does not say whether a caller can force a fresh render of a page that has changed, either. For a page you fetch often, that is the first gap to close before trusting the output.

Self hosting costs a paid plan and a hand-edited wrangler.toml

The setup is short, and the first two commands are ordinary:

bash
git clone https://github.com/dhravya/markdowner
npm i

The third one creates the KV namespace:

bash
npx wrangler kv:namespace create md_cache

After that you open `wrangler.toml`, put the new IDs in, and ship the Worker:

bash
npm run deploy

Two things in those 5 steps deserve a hard look. The feature list calls the project easy to self-host and free, yet browser rendering and Durable Objects both sit behind the Workers paid plan. So the hosted instance costs nothing while your own copy is billed. And the clone line points at the `dhravya` account path while this repository sits under `supermemoryai`. Following that command literally gets you a different URL than the project you were reading about.

The manifest is called browser-rendering and declares no runtime dependency

The package manifest does not describe a library anyone installs from a registry. Its name field reads `browser-rendering`, the version is `0.0.0`, and `private` is `true`, which means `npm i` after the clone installs a local toolchain and nothing a downstream project could depend on. The file carries 4 scripts, each a thin wrapper around wrangler: `deploy` runs `wrangler deploy`, while dev and start both run `wrangler dev`, and `cf-typegen` runs `wrangler types`. The 3 devDependencies are `@cloudflare/workers-types` at `^4.20240502.0`, `typescript` at `^5.0.4` and a wrangler range that starts at `^3.53.1`. What is missing is louder than what is present. No runtime dependency is declared at all, so the Turndown conversion named in the tech notes has nothing to trace in this manifest, and `src/` is the only directory in the listing where that answer would live. The repository carries an MIT licence, and the last push landed on 2026-10-02.

The named alternatives are rejected without attribution

The project positions itself against 2 services it links by address, `r.jina.ai` and `firecrawl.dev`. Then come 3 complaints: too expensive or proprietary, too limited, and very difficult to deploy. No complaint is attached to a name, so a reader cannot tell which of the two is the expensive one and which is the stubborn one to run. The difference that can be pinned down is structural. Those two are reached as hosted endpoints. Markdowner is a Worker whose whole implementation sits in one repository, with the conversion step named as a library instead of hidden behind an API. Hand that control back, though, and you lose everything around the conversion. There is no storage layer here, no index, no query interface and no scheduling. Supermemory, the app that prompted the project, is what supplies those pieces.

What the documentation leaves unanswered about the public endpoint

Four questions come up before you point real traffic at this, and none of them have answers in the documentation. Access control is the first: the usage example is a bare GET, no authentication parameter is described anywhere, and nothing says what the rate limits are. `llmFilter` is the second. It is a boolean with no model named and no prompt shown, so you cannot audit what it strips out of a page and you cannot reproduce its output. Crawling behaviour is the third. Nothing addresses robots.txt or excluded paths, and the crawler is only claimed to work without a sitemap, with no rule given for how it chooses which links to follow. Versioning is the last. There are no GitHub releases, so nothing in the repository marks a version boundary, and no changelog file appears in the listing either.

Editorial conclusion

Adopt Markdowner when your pipeline needs page text shaped for a model and you already run on Cloudflare: the parameters are few, the conversion step is one named library, and the whole Worker fits in a single repository you can read in an afternoon. Do not adopt it when you need storage, indexing or a query layer, since it stops at markdown, nor when the budget forbids a paid Workers plan or you need a crawl deeper than 10 subpages. Verify first what `enableDetailedResponse` returns, then pin down how long `md_cache` keeps a body.

Frequently asked questions

How do I convert a website to markdown with Markdowner?

Send a GET request that carries the url parameter: `curl 'https://md.dhr.wtf/?url=https://example.com'`. Add `Content-Type: application/json` to the headers when you want a JSON body instead of markdown text.

What does the crawlSubpages option do in Markdowner?

It crawls outward from the given URL and returns markdown for up to 10 subpages. The toggle is off by default, as are `enableDetailedResponse` and `llmFilter`.

Can I self-host Markdowner on the free Cloudflare Workers tier?

No. Browser rendering and Durable Objects need the Workers paid plan, so your own copy is billed even though the hosted instance at `https://md.dhr.wtf` is free to call.

Which library turns the rendered page into markdown in Markdowner?

Turndown does the conversion. It is not listed in `package.json`, which declares 3 devDependencies and no runtime dependency at all, so the import has to be traced inside `src/`.

Does Markdowner require an API key or a sitemap to crawl a site?

No key is documented; the usage example is a bare GET carrying only the url parameter. The crawler is described as working without a sitemap, but no rule is given for how it picks which links to follow beyond the 10 subpage ceiling.

Official sources

  1. Issues
  2. License: MIT
  3. Project website
  4. README
  5. supermemoryai/markdowner on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/supermemoryai-markdowner.svg)](https://hysenlabs.com/projects/supermemoryai-markdowner)