Skip to content

Render markdown in OpenAPI descriptions on the API pages - #22

Open
vzert wants to merge 2 commits into
markline-dev:mainfrom
vzert:feat/markdown-descriptions
Open

Render markdown in OpenAPI descriptions on the API pages#22
vzert wants to merge 2 commits into
markline-dev:mainfrom
vzert:feat/markdown-descriptions

Conversation

@vzert

@vzert vzert commented Jul 18, 2026

Copy link
Copy Markdown

Problem

Operation, tag, parameter and response descriptions are rendered as
plain text. So a spec description like:

Create a **payment**. See the [pricing guide](/pricing) and the
`amount` field below.

shows up in the docs with the literal **, […](…) and backticks
instead of formatting.

Fix

A small mdToHtml helper (lib/md-desc.ts, remark + rehype) used on the
server-rendered API pages:

  • operation description + per-response descriptions (operation-page.tsx)
  • parameter / schema attribute descriptions (schema-table.tsx)
  • tag descriptions (endpoint-list.tsx)
  • mdToText for the page <meta> / OG description, so markdown syntax
    doesn't leak into search snippets (page.tsx)

Safety

The rendered HTML is injected via dangerouslySetInnerHTML, so it is
sanitized:

  • Raw HTML in the source is dropped — no rehype-raw, so a
    <script> / <img onerror> in a description never reaches the output.
  • Unsafe URL schemes are strippedrehype-sanitize (default GitHub
    schema) turns [x](javascript:alert(1)) into inert <a>x</a> while
    keeping normal https: links, code, tables and lists.

Verified end-to-end: injected a description with bold, a javascript:
link and a GFM table into a spec, built, and confirmed the page renders
<strong>/<code>/<table> and that the javascript: href is gone.

Scope

Intentionally limited to the server-rendered operation/tag pages. The
interactive apiref view (markline-apiref) also renders descriptions,
but rendering markdown there is an HTML-injection / sanitization design
call I'd rather leave to you — glad to send a follow-up once you're happy
with the approach here.

Dependencies

Adds direct deps: unified, remark-parse, remark-rehype,
rehype-sanitize, rehype-stringify. remark-gfm was already present.

Verification

  • npm run typecheck — clean
  • npm test — 25/25 pass (adds test/md-desc.test.ts)
  • npm run build — static export succeeds

Victor Zertuche added 2 commits July 18, 2026 13:25
Operation, tag, parameter and response descriptions were rendered as
plain text, so backticks, links, bold, lists and tables in a spec
description showed up as raw markdown syntax. Add a small `mdToHtml`
helper (remark + rehype) and use it on the server-rendered API pages:

- operation description and per-response descriptions (operation page)
- parameter / schema attribute descriptions (schema table)
- tag descriptions (endpoint list)
- `mdToText` for the page `<meta>`/OG description, so markdown syntax
  doesn't leak into snippets

The output is injected via dangerouslySetInnerHTML, so it is sanitized:
remark-rehype drops raw HTML in the source (no rehype-raw), and
rehype-sanitize's default (GitHub) schema strips unsafe URL schemes such
as `javascript:` while keeping the elements GFM emits (links, code,
tables, lists). Covered by a unit test.

Scope is intentionally limited to the server-rendered operation/tag
pages. The interactive apiref view (`markline-apiref`) also renders
descriptions, but adding markdown there is an HTML-injection/sanitization
design call I'd rather leave to you — happy to follow up on it.

Adds direct deps: unified, remark-parse, remark-rehype, rehype-sanitize,
rehype-stringify (remark-gfm was already present).
Now that descriptions render markdown, give tables, lists and inline code
minimal styling (border-collapse, code chips, list indentation) so they
read correctly in the operation / tag / parameter description slots
without depending on the full docs-prose styles.

@oreofeolurin oreofeolurin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sanitization work here is solid. I checked the output myself: [x](javascript:alert(1)) comes out as an inert <a>, raw <img onerror> and <script> get dropped, and mdToText gives clean meta descriptions. Tests and typecheck pass, static export builds fine.

On the apiref view, the approach is right and there's no design call left to make. mdToHtml with rehype-sanitize and no rehype-raw is exactly what I'd want there too, so go ahead and run the same pipeline over r.lead and the operation leads in markline-apiref.tsx.

The reason I'd rather have that in this PR than a follow-up: /api-reference/<tag> is the page people actually land on, and it renders MarklineApiRef, not ApiOperationPage. The operation page only comes up on per-operation deep links. So as it stands a tag description with **bold** and a [link](…) still renders literally on the resource page, and most readers see no change at all.

Two things worth fixing while you're in there:

  • .api-desc a has no colour set, so links fall through to the browser default #0000EE. Against the dark theme background that's about 1.3:1.
  • Response descriptions go into a <span> (operation-page.tsx:180), so a description containing a list emits <p>/<ul> inside a span in a flex row. Invalid nesting, and the status pill drifts out of alignment. A div sorts it, and mdToHtml already unwraps single paragraphs so inline descriptions stay inline.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants