README
¶
sitemap2md
Turn a site's sitemap.xml into clean, structured Markdown for people, scripts,
and AI agents.
Install
go install github.com/markosnarinian/sitemap2md@latest
Or build from source:
go build -o sitemap2md .
Usage
sitemap2md <url> [flags]
<url> may be a sitemap URL or a plain site URL:
sitemap2md https://electronjs.org # discovers the site's sitemap
sitemap2md https://electronjs.org/sitemap.xml # hits exactly this URL
sitemap2md -o sitemap.md example.com # scheme optional, write to file
When the URL doesn't itself serve a sitemap (responses are content-sniffed, so
an HTML homepage is never mistaken for XML), the sitemap is discovered
automatically: robots.txt Sitemap: directives are consulted first, then
well-known paths (/sitemap.xml, /sitemap_index.xml, /sitemap-index.xml,
/wp-sitemap.xml, /sitemap.xml.gz).
URLs without a scheme use https://. An explicit http:// URL is honored but
warned about.
Sitemap index files (<sitemapindex>) are followed recursively by default,
their page entries merged and deduplicated. Gzipped sitemaps (.xml.gz) are
decompressed transparently.
Without -o, stdout carries only the Markdown document, so scripts can
consume or redirect it directly. Progress, warnings, completion feedback, and
errors use stderr and never get mixed into the document:
sitemap2md electronjs.org > sitemap.md
After each run, a short completion message is printed to stderr:
Created sitemap.md with 450 pages from 1 sitemap in 2.1s.
Flags
| Flag | Default | Description |
|---|---|---|
-o, --output |
stdout | Save Markdown to a file instead of printing it |
--excerpt |
off | Add a short main-content excerpt for each page |
--excerpt-chars |
300 | Limit each excerpt to this many characters |
--no-recursive |
off | List child sitemaps instead of fetching them |
--concurrency |
8 | Maximum number of requests to run at once |
--timeout |
30s | Time limit for each HTTP request |
--heading-depth |
1 | Number of URL path levels to use as headings (1–5) |
--include |
— | Keep URLs matching this regex (repeatable) |
--exclude |
— | Skip URLs matching this regex (repeatable) |
--route |
— | Keep this URL path and its children (repeatable) |
-v, --verbose |
off | Show progress; repeat for per-page and HTTP details |
--version |
— | Print the installed version |
Filter semantics: pages must match at least one --include/--route when any
are given (they form a union); --exclude then removes matches. --route /docs matches /docs and /docs/... but not /docs-old.
Use -v for friendly phase-by-phase progress. Use -vv to troubleshoot
individual sitemap and excerpt failures, or -vvv to inspect HTTP requests.
Excerpt failures are grouped into one warning instead of printing hundreds of
similar lines.
Output format
Pages are grouped by the first segment of their URL path, one ## section per
segment, sorted for deterministic output:
# Sitemap for example.com
> 42 pages · Source: https://example.com/sitemap.xml · Generated: 2026-07-17
## Top-level pages
- [/](https://example.com/) — last updated: 2026-05-01 · frequency: daily · priority: 1.0
- [/about](https://example.com/about)
## /blog
- [/blog/post-1](https://example.com/blog/post-1) — last updated: 2026-05-01
## /docs
- [/docs/intro](https://example.com/docs/intro) — last updated: 2026-06-12 · priority: 0.9
With --excerpt, each entry gains a blockquote containing the beginning of the
page's main content — extracted with a Readability port
(codeberg.org/readeck/go-readability),
so navigation, headers, and sidebars are excluded:
- [/blog/post-1](https://example.com/blog/post-1) — last updated: 2026-05-01
> Electron 36.0.0 has been released! It includes upgrades to Chromium, V8, and Node.js…
Pages that fail to fetch or yield no extractable content are rendered without
an excerpt. One summary warning is printed to stderr; use -vv to see the
affected pages.
With --no-recursive, child sitemaps of an index are not fetched and are listed
under a ## Child sitemaps section instead.
Security: prompt injection
The output of this tool is designed to be read by AI agents, and everything in
it originates from a remote site: sitemap XML, robots.txt, and (with
--excerpt) arbitrary page content. A hostile site could try to smuggle
instructions or fake document structure into an agent's context through any of
those channels. sitemap2md therefore treats all remote text as untrusted and
always sanitizes it:
- Excerpts are stripped of control/zero-width characters, collapsed to a single blockquote line, and have all Markdown syntax backslash-escaped, so they cannot form links, images, code, HTML tags, or headings.
- URLs from
<loc>and robots.txt are percent-encoded where they could break out of[text](url)link syntax. - Metadata (
lastmod,changefreq,priority) is reduced to a whitelist of expected characters and capped in length. - When excerpts are included, the document header carries a provenance note marking them as quoted untrusted content.
Residual advice for consumers: treat the whole document as data, not
instructions; agents should keep their own instruction/data separation and
never execute directives found in page-derived text; use --exclude to drop
routes dominated by user-generated content when that risk matters.