Proposal: raw Markdown output for published notes (/shares/:id?format=md)
Summary
Add an optional format=md query parameter to the existing public share route, so that a published note can be fetched as raw Markdown in addition to the current server-rendered HTML page.
GET /shares/:id -> rendered HTML page (unchanged, default)
GET /shares/:id?format=md -> text/markdown, note body only
Motivation
Today the only public representation of a published note is the fully rendered HTML page. That is perfect for the built-in viewer, but it makes life hard for anything that wants to build on top of Joplin Server publishing:
- Alternative reading frontends (custom typography, themes, TOC, print styles) have to scrape the rendered page and extract the body from the Mustache template's DOM. This couples them to the template structure and quietly breaks when the server is upgraded.
- Static site generators / archival tools want the source Markdown, not pre-rendered HTML.
- The only current way to get raw content is the authenticated
/api/items/:id/contentroute, which requires full account credentials — a wildly over-privileged token for reading content that is already public. Encouraging third-party tools to hold owner credentials seems worse for the ecosystem than exposing a read-only format switch on already-public data.
A format=md switch keeps Joplin Server the single source of truth for what is published, while letting the ecosystem decide how it is presented.
Behavior
GET /shares/:id?format=mdreturns the note body withContent-Type: text/markdown; charset=utf-8.- Safe metadata is exposed via headers (or could be a
format=jsonvariant if preferred):X-Joplin-Note-TitleX-Joplin-Note-Updated-Time
- Works the same for linked notes in recursive shares:
GET /shares/:id?format=md¬e_id=...(subject to the existingisInTreecheck). - Resources are unaffected —
?resource_id=already serves them publicly. - Unpublishing, disabled-account checks, and share lookup behave exactly as they do for the HTML route (same code path, only the final rendering step differs).
Privacy note (why "body only")
The serialized item stored on the server carries the full metadata footer, including fields the rendered HTML page has never exposed: latitude/longitude, author, source_url, parent_id, etc. Returning the serialized item verbatim would therefore be a new information disclosure. This proposal deliberately returns the body plus a whitelist (title, updated time) and strips everything else, so format=md exposes strictly less than or equal to what the HTML page already shows.
Implementation sketch
Small and local:
packages/server/src/routes/index/shares.ts: pass the format through (the query is already forwarded torenderItem).packages/server/src/utils/joplinUtils.ts: inrenderItem, whenformat === 'md'and the item to render is a note, return{ body: note.body, mime: 'text/markdown' }instead of callingrenderNote(). The note entity is already unserialized at that point; no new data access is introduced.- Tests alongside the existing share route tests.
I'm happy to submit a PR with tests if this direction is acceptable.
Alternatives considered
- Scraping the rendered page — works today, fragile across server upgrades, couples third parties to the internal template.
- Authenticated items API — requires full-library credentials for public content; over-privileged.
- A separate JSON API for shares — heavier to design and maintain; the format switch on the existing route reuses all existing checks and stays trivially small.