Claude API Markdown Response Formatting Explained
When you call the Claude API, responses often come back with markdown syntax baked in: bold text, ## headings, numbered lists, fenced code blocks with triple backticks. This is expected behavior, not a bug. Claude is trained to produce well-structured text, and markdown is the most common way to express structure in plain text output. If you're seeing literal asterisks or hash symbols in your UI instead of rendered formatting, the fix lives on your side, not Anthropic's.
This article covers why Claude outputs markdown by default, how to instruct it to format differently (or not at all), and how to properly render or strip markdown depending on where your response ends up — a chat UI, a PDF, a Slack message, or a JSON field.
Why Claude Returns Markdown by Default
Claude's training data includes enormous amounts of technical writing, documentation, and chat transcripts where markdown is the de facto formatting standard. When you ask for "a comparison of three frameworks" or "steps to deploy this app," Claude naturally reaches for:
- Bullet and numbered lists
- Bold text for emphasis or key terms
- Headings to separate sections
- Fenced code blocks for anything resembling code
- Tables when comparing structured data
This is genuinely useful if you're rendering the output in a markdown-aware viewer. It becomes a problem if your frontend just dumps the raw text into a <p> tag or a plain-text SMS, where your users will see literal ** and # characters.
Controlling Markdown Output via Prompting
The most reliable lever you have is the system prompt. Claude follows explicit formatting instructions well. A few patterns that work consistently:
To suppress markdown entirely:
Respond in plain prose only. Do not use markdown syntax,
including asterisks, bullet points, numbered lists, or
headings. Write in full sentences and paragraphs.
To force a specific structure:
Format your response as markdown with an H2 heading for
each section and a bullet list under each heading. Do not
include any text outside this structure.
To get clean lists without bold wrappers:
Use markdown bullet lists for steps. Do not bold any text
within list items.
Claude is generally good about following these constraints, but it's not perfect — occasionally it slips in a bold phrase out of habit, especially in longer responses. If formatting consistency is critical (e.g., you're parsing the output downstream), don't rely purely on prompting. Pair it with the parsing and rendering techniques below, and if you need strictly structured output rather than formatted text, look at JSON-based approaches instead.
Rendering Markdown Safely in Your App
If you want Claude's markdown rendered properly rather than suppressed, you need a markdown renderer on your frontend. This is a one-time setup, not a per-request concern.
For web apps, popular options include marked, markdown-it, or react-markdown if you're in React:
import ReactMarkdown from "react-markdown";
function ClaudeResponse({ text }) {
return <ReactMarkdown>{text}</ReactMarkdown>;
}
Important: never render markdown output with dangerouslySetInnerHTML without sanitization. If Claude's output is ever influenced by untrusted user input (which it often is, since users write the prompts), there's a theoretical injection surface if the model echoes HTML-like content. Use a renderer that escapes raw HTML by default, or run the output through a sanitizer like DOMPurify after conversion.
For Slack, Discord, or other chat platforms, you generally don't need a separate renderer — these platforms have their own (slightly different) markdown dialects. Slack uses single asterisks for bold instead of double, for example, so you may need a translation step:
function toSlackMarkdown(text) {
return text
.replace(/\*\*(.*?)\*\*/g, "*$1*") // bold
.replace(/^#{1,6}\s+(.*)$/gm, "*$1*"); // headings → bold
}
Stripping Markdown for Plain-Text Contexts
If you're sending Claude's output into an SMS, email subject line, voice assistant, or anywhere markdown syntax has no meaning, strip it before display. A simple regex pass handles most cases:
function stripMarkdown(text) {
return text
.replace(/```[\s\S]*?```/g, "") // code blocks
.replace(/`([^`]+)`/g, "$1") // inline code
.replace(/\*\*(.*?)\*\*/g, "$1") // bold
.replace(/\*(.*?)\*/g, "$1") // italics
.replace(/^#{1,6}\s+/gm, "") // headings
.replace(/^[-*+]\s+/gm, "") // bullet markers
.replace(/^\d+\.\s+/gm, "") // numbered lists
.trim();
}
This won't be perfect for edge cases like nested lists or tables, but it handles the common output patterns cleanly enough for plain-text display.
Handling Code Blocks Specifically
Code blocks deserve separate handling because they usually need syntax highlighting rather than plain rendering. Claude typically wraps code in triple backticks with a language hint:
def greet(name): return f"Hello, {name}"
Extract the language and content with a regex, then pass to a highlighter like highlight.js or prism:
const codeBlockPattern = /```(\w+)?\n([\s\S]*?)```/g;
const matches = [...text.matchAll(codeBlockPattern)];
matches.forEach(([, lang, code]) => {
// lang: "python", code: the extracted snippet
});
Where SubToAPI Fits In
If you're already calling Claude through SubToAPI, the formatting behavior is identical to direct Anthropic API calls — SubToAPI passes requests through transparently and returns the same response structure, so whatever prompting and rendering strategy you use here applies without modification. Streaming responses (see /docs/streaming) deliver markdown incrementally in chunks, so if you're rendering live, buffer tokens until you hit a complete line or block boundary before running them through your markdown parser — partial ** sequences render incorrectly mid-stream. Check /docs/messages for the full response schema, and /docs/quickstart if you're setting up your first integration.
Questions
Does Claude always return markdown, or can I disable it completely? Claude defaults to markdown for structured content but reliably follows explicit system prompt instructions to use plain prose instead. It's not a hard API toggle — it's prompt-controlled behavior.
Why do I see literal asterisks instead of bold text in my app? Your frontend is displaying raw text without a markdown renderer. Add a library like react-markdown or marked, or strip the syntax with a regex if you want plain text instead.
Is markdown formatting different when streaming responses? The markdown syntax itself doesn't change, but partial tokens arrive mid-syntax (e.g., one asterisk before the next chunk). Buffer until a complete block boundary before parsing to avoid rendering glitches.