← Blog

Claude API Markdown Output Formatting: Full Guide

2026-09-29 · 5 min read · SubToAPI Team

Claude naturally writes in Markdown when you ask it to, but getting consistent, parseable Markdown output from the API requires more than just hoping for the best. If you're rendering Claude's responses in a chat UI, a documentation tool, or a CMS, you need predictable formatting — proper heading levels, clean code fences, correctly escaped characters, and no stray commentary wrapped around the content you actually want.

This guide covers how to prompt Claude for reliable Markdown, how to handle edge cases like nested code blocks and tables, and how to parse the output safely on the client side.

Why Claude's Markdown output isn't always consistent

Claude is trained to produce well-formatted Markdown by default, but a few things can throw off consistency:

None of these are bugs in the model; they're a result of underspecified prompts or a rendering pipeline that assumes more structure than it's asking for.

Prompting for clean Markdown

The most reliable fix is being explicit in your system prompt. A few patterns that work well:

You are a technical writer. Always respond in valid Markdown.
Use ## for section headings, never # (reserved for the page title).
Wrap all code in fenced code blocks with a language identifier.
Do not include any text before or after the Markdown content —
no greetings, no "here is your answer" preambles.

For structured content like documentation or changelogs, go further and specify the exact schema:

Respond only with Markdown following this structure:
## Summary
A one-paragraph overview.

## Details
A bulleted list of key points.

## Example
A fenced code block showing usage.

This reduces variance dramatically because you're not asking Claude to infer structure — you're handing it the structure directly.

Requesting specific Markdown elements

If you need particular elements, name them explicitly rather than describing them in prose:

Claude follows literal formatting instructions more reliably than implied ones, especially in longer responses where drift can creep in.

Handling code blocks safely

Code blocks are the most common source of broken Markdown, particularly when the code itself contains triple backticks (for example, when Claude is asked to output a Markdown example inside a response). Two practical mitigations:

  1. Ask for four-backtick fences when the content might contain three-backtick code blocks:

`` If your example code itself contains Markdown code fences, wrap the outer block in four backticks instead of three. ``

  1. Request a language identifier on every fence ( `python , `bash , `json ) so your syntax highlighter and any downstream parsing logic can key off it reliably.

Parsing Markdown from API responses

On the client side, treat the API response as untrusted text until it's parsed. A typical pipeline:

import { marked } from "marked";
import DOMPurify from "dompurify";

const response = await fetch("https://api.subtoapi.app/v1/messages", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "claude-sonnet-4",
    max_tokens: 1024,
    system: "Respond only in valid Markdown, no preamble.",
    messages: [{ role: "user", content: "Explain event loops in Node.js." }],
  }),
});

const data = await response.json();
const markdown = data.content[0].text;

const html = DOMPurify.sanitize(marked.parse(markdown));

Always sanitize before rendering as HTML — Markdown parsers can pass through raw HTML tags, and if Claude's output ever includes user-supplied content, that's an injection risk. DOMPurify or an equivalent sanitizer is not optional in production.

Streaming Markdown correctly

If you're streaming responses, don't run a Markdown parser on every partial chunk — it will produce flickering, malformed HTML as tags open and close mid-stream. Instead, buffer tokens and re-parse the accumulated text on each update, or use a streaming-aware Markdown renderer that tracks open block state (unclosed code fences, open list items) and only finalizes elements once they're complete. SubToAPI passes through Claude's native streaming events unchanged, so you can apply the same buffering strategy you'd use directly against Anthropic's API — see /docs/streaming for event shapes.

Enforcing format with JSON + Markdown fields

For applications where Markdown is one field among several (like a title, tags, and a body), it's often cleaner to ask Claude to return a JSON object with a Markdown string inside one field, rather than trying to parse mixed structure out of free text:

{
  "title": "Understanding Event Loops",
  "tags": ["nodejs", "concurrency"],
  "body_markdown": "## Overview\n\nThe event loop is..."
}
</br>

This way, your Markdown renderer only ever touches the body_markdown field, and everything else is handled with normal JSON parsing — no risk of stray headings or lists leaking into fields that shouldn't have them.

If you're already calling Claude through SubToAPI, the request and response shapes match the standard Messages API, so any Markdown prompting strategy that works with Anthropic's endpoint works unchanged here — see /docs/messages for the full request reference, or /docs/quickstart to get a key running in a few minutes.

Common formatting bugs and fixes

| Problem | Likely cause | Fix | |---|---|---| | Extra preamble text before content | No explicit "no preamble" instruction | Add it to the system prompt | | Broken nested code fences | Three-backtick fences inside three-backtick fences | Request four-backtick outer fences | | Inconsistent heading levels | No heading schema specified | Define exact heading levels in the prompt | | Raw HTML rendering unexpectedly | Markdown parser allows HTML passthrough | Sanitize output before rendering | | Flickering tables/lists while streaming | Parsing partial chunks directly | Buffer and re-parse accumulated text |

questions

Does Claude always return Markdown by default? No. Claude adapts formatting to context. For consistent Markdown, specify it explicitly in your system prompt, including which heading levels and elements to use.

How do I stop Claude from adding conversational text before Markdown content? Add an explicit instruction like "respond only with Markdown, no preamble or explanation" to your system prompt. This is the single most effective fix for stray wrapper text.

Is it safe to render Claude's Markdown output directly as HTML? Only after sanitizing it with a library like DOMPurify. Markdown parsers can pass through raw HTML tags, which is a risk if any part of the input is user-supplied.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →