GEMINI LABJP
NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Articles/API / SDK
API / SDK/2026-04-17Beginner

Gemini API Returns Markdown — How to Get Plain Text Responses

Gemini API responses often contain Markdown symbols like **, ##, and -. Learn how to get clean plain text using response_mime_type, System Instructions, and post-processing with practical Python and TypeScript code examples.

gemini-api277troubleshooting82markdownplain-textresponsesystem-instructions4python104fix10

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 formatting

One 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.

Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

API / SDK2026-05-30
Why Gemini 2.5 Pro Rejects thinkingBudget: 0 (and How to Fix It)
Setting thinkingBudget to 0 on Gemini 2.5 Pro returns a 400 INVALID_ARGUMENT error. Here is why the per-model thinking budget ranges differ, how to minimize thinking on Pro the right way, and when to switch to Flash, with Python and JavaScript examples.
API / SDK2026-04-16
Gemini API System Instructions Not Working — 4 Common Causes and How to Fix Them
Set up System Instructions but the model keeps ignoring them? This guide covers the 4 most common reasons why system prompts fail in Gemini API — from wrong parameter placement to multi-turn drift — with working code examples.
API / SDK2026-04-14
Veo API Not Working? Common Errors and How to Fix Them
Troubleshoot common Veo API errors including polling implementation mistakes, safety filter rejections, quota exceeded, and video file download failures. With working Python code examples.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →