Joplin raw share proposal

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/content route, 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=md returns the note body with Content-Type: text/markdown; charset=utf-8.
  • Safe metadata is exposed via headers (or could be a format=json variant if preferred):
    • X-Joplin-Note-Title
    • X-Joplin-Note-Updated-Time
  • Works the same for linked notes in recursive shares: GET /shares/:id?format=md&note_id=... (subject to the existing isInTree check).
  • 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 to renderItem).
  • packages/server/src/utils/joplinUtils.ts: in renderItem, when format === 'md' and the item to render is a note, return { body: note.body, mime: 'text/markdown' } instead of calling renderNote(). 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.

It reads like you're developing SharePoint-like solution on top of Joplin Server. Can you share the use case you're pursuing with "an alternative reading front end"?

Not SharePoint-like at all — no collaboration, no permissions, no portal. It's strictly a read-only presentation layer for notes you have already published with Joplin's own Publish feature.

The concrete use case: I'm building Joplin Portal, a small self-hosted frontend (single Docker container) that takes an existing share link and renders it with better reading ergonomics — proper typography, a table of contents, dark mode, syntax highlighting, print/PDF styles, themes. Think "what the published page looks like" rather than "what publishing does". Joplin Server stays the single source of truth: what's published, stays published there; unpublish and the Portal page dies with it. Zero server modification.

To re-render a note consistently I need the source Markdown, and today there are only two ways to get it: scrape the rendered HTML out of the Mustache template (breaks silently on server upgrades), or hold full account credentials for content that is already public (over-privileged, and bad practice to encourage). A format=md switch on the existing public route is the smallest possible fix: same share lookup, same access checks, same lifecycle — only the final rendering step differs.

Same need was raised back in 2022 issue #6611, "server/shares: raw markdown" and closed for lack of someone to implement it — I'm offering the implementation with tests.

Closing bot generated thread