You send a prompt to the Gemini API expecting clean text, and instead get back something littered with **bold**, ## headers, and - bullet points. If you are building anything beyond a standard chat UI — a text-to-speech pipeline, a PDF exporter, a database ingestion system — those Markdown symbols cause real problems downstream.
I ran into this firsthand while building a mobile app that passed Gemini responses directly into a TTS engine. The synthesizer dutifully read out "asterisk asterisk" at every bold phrase. Not ideal. Here is what actually works.
Why Gemini API Returns Markdown by Default
Gemini 2.x models are trained to produce readable, well-structured responses. Without explicit instructions, the model uses Markdown to organize information — particularly for explanatory, list-based, or comparative prompts.
Prompt types that reliably trigger Markdown output:
- "Tell me about X" or "Summarize X" — high chance of headers and bullet lists
- "List the steps / advantages / differences" — almost always returns formatted lists
- "Compare X and Y" — typically uses bold labels and structured sections
Prompt types where Markdown is rare:
- "Translate this text to French"
- "Fix the grammar in this paragraph"
- "What is 142 times 37?"
The model decides whether to use Markdown based on what kind of response seems most appropriate for the task. That means you cannot rely on prompt phrasing alone to prevent formatting — you need to set explicit constraints.
Method 1: Set response_mime_type to "text/plain"
This is the simplest and most reliable fix. Setting response_mime_type in GenerationConfig instructs the model to produce plain text output rather than Markdown-formatted content.
Python
import google.generativeai as genai
genai.configure(api_key="YOUR_GEMINI_API_KEY")
model = genai.GenerativeModel(
model_name="gemini-2.5-pro",
generation_config=genai.GenerationConfig(
response_mime_type="text/plain", # The key setting
)
)
response = model.generate_content(
"What are the three main advantages of cloud computing?"
)
print(response.text)
# Output: "1. Cost reduction: No upfront infrastructure investment required...
# 2. Scalability: Resources scale on demand..."
# No asterisks, no hash symbols.Node.js / TypeScript
import { GoogleGenerativeAI } from "@google/generative-ai";
const genAI = new GoogleGenerativeAI("YOUR_GEMINI_API_KEY");
const model = genAI.getGenerativeModel({
model: "gemini-2.5-pro",
generationConfig: {
responseMimeType: "text/plain", // camelCase in the TypeScript SDK
},
});
const result = await model.generateContent(
"What are the three main advantages of cloud computing?"
);
console.log(result.response.text());
// Clean plain text, no Markdown formattingOne important clarification: setting response_mime_type: "text/plain" does not degrade response quality. The content remains equally detailed. Bullet points become numbered sentences; bold emphasis disappears into the surrounding text. You lose formatting, not information.
This setting also does not change your token usage. You are paying for the same content either way.
Method 2: Prohibit Markdown via System Instructions
For longer conversations or finer control over output style, combine response_mime_type with a System Instruction that explicitly bans Markdown. The System Instruction defines constraints before the conversation starts, making them more persistent than in-prompt requests.
import google.generativeai as genai
genai.configure(api_key="YOUR_GEMINI_API_KEY")
system_prompt = """You are a helpful assistant.
Follow these output rules at all times:
- Never use Markdown formatting. No asterisks, hash symbols, or backticks.
- For lists, use numbered format: 1. 2. 3.
- For code samples, use indentation only, no code fence syntax.
- Separate paragraphs with blank lines, not headers.
"""
model = genai.GenerativeModel(
model_name="gemini-2.5-pro",
system_instruction=system_prompt,
generation_config=genai.GenerationConfig(
response_mime_type="text/plain",
)
)
response = model.generate_content("How do I reverse a list in Python?")
print(response.text)
# Output:
# There are three common ways to reverse a list in Python.
#
# 1. Using the reversed() function, which returns an iterator:
# original = [1, 2, 3, 4, 5]
# reversed_list = list(reversed(original))
#
# 2. Using slice notation...System Instructions are highly effective for this purpose. However, they are guidance, not a hard constraint baked into the model. On complex prompts or in very long conversations, Markdown can occasionally reappear. That makes Method 3 worth keeping in your stack as a fallback.
Method 3: Strip Markdown in Post-Processing
The most defensible approach is to treat plain text output as something you actively enforce, not merely request. A Markdown stripping function applied to every response catches whatever the model settings miss.
import re
def strip_markdown(text: str) -> str:
"""Convert Gemini API Markdown output to plain text."""
# Remove headings (# ## ###)
text = re.sub(r'^\#{1,6}\s+', '', text, flags=re.MULTILINE)
# Remove **bold** and *italic* (preserving the text)
text = re.sub(r'\*{1,2}([^*]+)\*{1,2}', r'\1', text)
# Remove __bold__ and _italic_
text = re.sub(r'_{1,2}([^_]+)_{1,2}', r'\1', text)
# Remove code block delimiters
text = re.sub(r'```[a-z]*\n?', '', text)
text = re.sub(r'```', '', text)
# Remove inline code delimiters (preserving the code text)
text = re.sub(r'`([^`]+)`', r'\1', text)
# Convert bullet points to a neutral symbol
text = re.sub(r'^[\-\*]\s+', '• ', text, flags=re.MULTILINE)
# Remove horizontal rules
text = re.sub(r'^-{3,}$', '', text, flags=re.MULTILINE)
# Compress multiple consecutive blank lines
text = re.sub(r'\n{3,}', '\n\n', text)
return text.strip()
# Example usage
response = model.generate_content("Explain Python list comprehensions")
clean_text = strip_markdown(response.text)
print(clean_text)TypeScript Version
function stripMarkdown(text: string): string {
return text
.replace(/^\#{1,6}\s+/gm, "") // Remove headings
.replace(/\*{1,2}([^*]+)\*{1,2}/g, "$1") // Remove bold/italic markers
.replace(/_{1,2}([^_]+)_{1,2}/g, "$1") // Remove underscore bold/italic
.replace(/```[\w]*
?/g, "") // Remove code block open markers
.replace(/```/g, "") // Remove code block close markers
.replace(/`([^`]+)`/g, "$1") // Remove inline code markers
.replace(/^[\-\*]\s+/gm, "• ") // Convert bullets to symbols
.replace(/^-{3,}$/gm, "") // Remove horizontal rules
.replace(/
{3,}/g, "
") // Compress consecutive blank lines
.trim();
}
const response = await model.generateContent("Explain Python list comprehensions");
const cleanText = stripMarkdown(response.response.text());
console.log(cleanText);One limitation to be aware of: these regular expressions also remove Markdown delimiters from within code blocks. If your use case requires preserving actual code while stripping surrounding Markdown, you need a more sophisticated parser. For standard plain-text output scenarios — TTS, PDF generation, database storage — this simple approach works reliably.
Handling Multi-Turn Chat Sessions
Markdown re-emergence is most common in multi-turn conversations. As conversation history grows, the model tends to drift back toward formatted output even when response_mime_type is set. This is one of the subtler failure modes that catches developers off guard.
import google.generativeai as genai
genai.configure(api_key="YOUR_GEMINI_API_KEY")
model = genai.GenerativeModel(
model_name="gemini-2.5-pro",
system_instruction="Respond in plain text only. No Markdown formatting of any kind.",
generation_config=genai.GenerationConfig(
response_mime_type="text/plain",
)
)
chat = model.start_chat(history=[])
# Turn 1 — plain text, as expected
response1 = chat.send_message("What is Python?")
clean1 = strip_markdown(response1.text)
print(clean1)
# Turn 5 — Markdown may creep back in; post-processing catches it
response5 = chat.send_message("Can you give me a detailed breakdown of its key features?")
clean5 = strip_markdown(response5.text)
print(clean5)The most reliable configuration for chat sessions: set both response_mime_type and system_instruction on the model, then apply strip_markdown() to every response individually.
When You Actually Want Markdown
Before stripping Markdown everywhere, it is worth asking whether you actually need to. If your frontend renders Markdown — a React component using react-markdown, a mobile app with a Markdown renderer, a documentation site — then the default output is exactly what you want, and stripping it would be counterproductive.
Strip Markdown when the output is going to:
- A TTS / speech synthesis engine
- A database field that stores raw text
- A PDF or Word document via a template
- An email body (most email clients do not render Markdown)
- A native mobile UI label that displays literal text
Keep Markdown when the output is going to:
- A web chat UI with a Markdown rendering library
- A developer-facing response (Markdown displays nicely in most terminals and IDEs)
- A rich text editor that accepts Markdown input
Which Approach to Use When
For single-turn API calls, response_mime_type: "text/plain" is almost always sufficient. I skip the System Instruction to save tokens.
For TTS pipelines, I use all three layers: response_mime_type, System Instruction, and strip_markdown(). A TTS engine reading "asterisk asterisk important" aloud is a user experience failure, so the extra overhead is worth it.
For multi-turn chat sessions, I set both response_mime_type and System Instruction up front, then apply strip_markdown() to each response. The combination handles even long sessions reliably.
Wrapping Up
Add response_mime_type: "text/plain" to your GenerationConfig first. That single line resolves the problem for the majority of cases. For TTS, PDF export, or long chat sessions, layer in a System Instruction and the strip_markdown() utility to cover edge cases.
Once you have plain text output working reliably, the next useful experiment is the opposite direction: getting consistently structured JSON using response_mime_type: "application/json" paired with response_schema. That combination handles data extraction and transformation tasks far better than trying to parse structured information out of free-form text.