md2rich
· Markdown Tooling

Markdown to Notion: The Complete 2026 Guide (with md2rich)

Notion has become the default workspace for millions of writers, project managers, and engineering teams. Its block-based editor is flexible, but its Markdown support has a strange inconsistency: it accepts some Markdown patterns on paste, silently drops others, and produces different results depending on whether you are pasting into a page body, a code block, or a database description.

If you write in a dedicated Markdown editor (Obsidian, Typora, VS Code, iA Writer) and want to bring that content into Notion, the paste workflow matters. This guide tests three approaches — direct paste, Notion's native import, and a client-side converter pipeline — against a real-world Markdown document with headings, tables, code blocks, nested lists, and blockquotes.

The Problem: Notion's Markdown Paste Is Inconsistent

Let's start with a typical Markdown document a technical writer might produce:

# Monthly Engineering Review — June 2026

## Key Metrics

| Service | P99 Latency | Error Rate | QPS |
|---------|-------------|------------|-----|
| Auth API | 45ms | 0.02% | 8,200 |
| Search API | 210ms | 0.15% | 3,100 |
| Payment API | 180ms | 0.08% | 1,400 |

## What Changed

We shipped three significant changes this sprint:

1. **Redis cluster migration** — moved from a single 16GB instance
   to a 3-node cluster in us-east-1
2. **New rate limiter** — per-user token bucket with configurable
   refill rate (details below)
3. **Log pipeline rewrite** — Kafka topic partitioning doubled

### Rate Limiter Code

```python
import time
from collections import defaultdict

class TokenBucket:
    def __init__(self, rate, burst):
        self.rate = rate
        self.burst = burst
        self.tokens = defaultdict(lambda: burst)
        self.last_refill = defaultdict(time.time)

    def allow(self, user_id):
        now = time.time()
        elapsed = now - self.last_refill[user_id]
        self.tokens[user_id] = min(
            self.burst,
            self.tokens[user_id] + elapsed * self.rate
        )
        self.last_refill[user_id] = now
        if self.tokens[user_id] >= 1:
            self.tokens[user_id] -= 1
            return True
        return False
```

> **Note**: The token bucket approach was chosen over a fixed
> window counter because it handles burst traffic better under
> our current load profile. See the A/B test results in the
> appendix.

Now paste this Markdown directly into a Notion page and observe what breaks:

Element Direct Paste Result Broken?
# H1 heading Becomes a Notion H1 block ✅ Works
## H2 heading Becomes a Notion H2 block ✅ Works
### H3 heading Becomes a Notion H3 block ✅ Works
Table Flattened into plain text — pipes and dashes become literal characters ❌ Broken
Code block (triple backtick) Pasted as plain paragraph with backticks visible ❌ Broken
Ordered list Indentation lost — sub-items become separate top-level items ⚠️ Partial
Blockquote Quote block not created — pasted as plain paragraph ❌ Broken
Bold Preserved as bold text ✅ Works
Inline code Preserved as inline code ✅ Works

The pattern is clear: Notion handles inline formatting (bold, italic, inline code) and headings well. It falls apart on block-level elements that require structural conversion — tables, code blocks, blockquotes, and nested lists. For internal wiki-style content, direct paste might be acceptable. For published articles or detailed technical documentation, it is not.

Notion's Native File Import: Better but Limited

Notion offers a built-in Markdown importer: File → Import → Markdown & CSV. Select your .md file and Notion creates a new page from it. How does it handle the same document?

Element File Import Result Broken?
Headings (H1-H3) All converted to proper Notion heading blocks ✅ Works
Table Rendered as plain text — pipes and formatting characters visible ❌ Broken
Code block Not created as code block — pasted as text paragraph ❌ Broken
Ordered list with nesting Import preserves list items but flattens sub-items ⚠️ Partial
Blockquote Not converted — pasted as regular text ❌ Broken
Image references Raw Markdown image syntax preserved as text — not rendered ❌ Broken

The file import is slightly better than raw paste for headings (all work reliably) but still fails on the same block-level elements. The table and code block shortcomings are the most painful for technical writers — these are the structures that distinguish a well-formatted Notion page from a wall of text.

The md2rich Workflow: Three Steps, Full Fidelity

Here is the workflow I have been using for the past 6 months. It adds one step between your Markdown editor and Notion, and it preserves everything:

  1. Write in your Markdown editor — Obsidian, Typora, VS Code, iA Writer, whatever you prefer. The Markdown file stays on your machine.
  2. Copy the Markdown → open md2rich.com → paste in the left editor — the right panel shows a live rich text preview. Everything renders in your browser: headings, tables, code blocks with syntax highlighting, nested lists, blockquotes, horizontal rules.
  3. Click "Copy as Rich Text" → paste into Notion — the formatted content appears in Notion with all structure intact. The table becomes a real Notion table. The code block renders with a gray background and monospace font. Nested lists keep their indentation.

That is it. The entire conversion happens client-side — your Markdown never reaches a server. Here is the same technical document pasted through md2rich:

Element md2rich + Notion Paste
Headings (H1-H3) ✅ Proper Notion heading blocks
Table with pipes ✅ Real Notion table — columns aligned, headers styled
Code block (Python, 20 lines) ✅ Code block with gray background, monospace, preserved indentation
Nested ordered list ✅ Full indentation preserved at all levels
Blockquote ✅ Rendered as quote block with left border
Inline code + bold ✅ Both inline styles preserved
Horizontal rule ✅ Divider line created in Notion

No element is lost. The same document that produces a broken mess when pasted directly into Notion renders perfectly through the md2rich pipeline.

Why Client-Side Matters for Notion Users

Notion users tend to put everything in Notion — meeting notes, product specs, customer feedback, hiring rubrics, financial models. When you stage content through a cloud-based Markdown converter before pushing it into Notion, that converter's server gets a copy of whatever you just pasted. For an engineering team writing a post-mortem of a production incident, that is a problem.

md2rich eliminates the server completely. The JavaScript runs in your browser tab. The Markdown you paste is read, rendered into HTML, written to the clipboard as rich text, and then the tab forgets everything. Refresh the page — the editor is empty. Close it — nothing happened outside your browser. This is the right security model for:

Code Example: A Reader-Facing Document

Here is a sample that a developer might write in Markdown for a public Notion page shared with their team or community:

# API Migration Guide: v2 to v3

## Key Changes

The v3 API introduces **breaking changes** in three areas:

| Area | v2 | v3 |
|------|----|-----|
| Auth | Bearer token in header | OAuth 2.0 + refresh tokens |
| Pagination | `page` / `per_page` | Cursor-based `after` / `before` |
| Rate limit | 100 req/min | 1,000 req/min per token |

## Migration Steps

1. **Generate a new API key** in the developer dashboard
2. **Update the authorization header** (see code below)
3. **Test against the v3 sandbox endpoint** before switching

### Python Example

```python
import requests

def migrate_to_v3(old_token):
    """Exchange v2 token for v3 OAuth credentials."""
    response = requests.post(
        "https://api.example.com/v3/auth/token",
        json={"grant_type": "token_exchange",
              "old_token": old_token}
    )
    return response.json().get("access_token")
```

> **Important**: The v2 auth header format (`Authorization: Token xxx`)
> will stop working on August 1, 2026.

Write this in Markdown, paste through md2rich, and paste into Notion. The table becomes a proper Notion table, the code block renders as a code block, the warning blockquote appears as a styled quote, and the nested list keeps every level of indentation. The reader sees a clean, well-structured Notion page — exactly as you intended.

Comparison: Three Paths to Notion

Dimension Direct Paste File Import md2rich Pipeline
Setup time 0 seconds File menu + path selection 10 seconds
Login required No No No
Content leaves your device No No (local file) No (client-side)
Tables preserved
Code blocks preserved
Nested lists preserved ⚠️ Partial ⚠️ Partial
Blockquotes preserved
Batch / multiple documents One at a time One file at a time One at a time
Works offline N/A (Notion is online) N/A After initial page load

FAQ

Does Notion support pasting Markdown directly?

Partially. Notion preserves bold, italic, inline code, and basic lists when you paste Markdown. But tables are flattened into plain text, code blocks lose their formatting, nested lists sometimes collapse, and headers may or may not carry over depending on the source. The paste behavior has improved since 2024 but remains unreliable for anything more complex than a paragraph with bold text.

Can I import a Markdown file directly into Notion?

Yes — Notion supports importing .md files via File → Import → Markdown & CSV. But the import has significant limitations: tables are not supported and appear as plain text, image references are resolved as raw URLs, and callouts or blockquotes lose their styling. For most content, the paste workflow through a client-side converter like md2rich produces better results than direct import.

Is md2rich safe to use with confidential Notion drafts?

Yes. md2rich runs 100% client-side in your browser. Your Markdown never leaves your device. This is especially important for Notion users who write drafts containing unreleased product details, customer data, or internal planning notes — the content goes from your Markdown editor to your Notion workspace with no third-party server in the middle.

Does md2rich preserve Notion-specific features like toggle lists?

md2rich preserves standard Markdown: headings, bold, italic, links, ordered and unordered lists, code blocks, blockquotes, horizontal rules, and tables. Notion-specific extensions like toggle lists, callout blocks, and database properties require Notion's own editor. For standard long-form content and technical documentation, md2rich covers everything.

Which Markdown editor works best with Notion + md2rich?

Any Markdown editor works because the pipeline is simply plain text copied between tools. Obsidian is a popular pairing because its graph view helps organize content across many Notion-related documents. VS Code with Markdown All in One is the developer favorite for technical documentation. iA Writer excels for long-form prose. Write in any editor, copy the Markdown, paste into md2rich.com, and push into Notion — the workflow is identical regardless of your editor choice.

Can I automate this for batch publishing to Notion?

md2rich is designed for one-off conversion — open, paste, copy, close. For batch automation, you would pair a Markdown-to-HTML library (like marked.js that md2rich uses internally) with Notion's API. The client-side privacy advantage is lost in an automated pipeline, but the rendering logic is the same. For most users, the per-document approach takes under 10 seconds per page.

Conclusion

Notion's Markdown handling has a well-defined gap: it handles inline formatting reliably but fails on block-level structures that matter most for technical and long-form content. The fix is a single extra step — a client-side Markdown-to-rich-text converter that runs in your browser and touches no server.

The workflow is:

Try it with the sample Markdown above. Paste directly into Notion first, then paste through md2rich.com. The difference is visible in under 30 seconds.

Try md2rich Free

Paste Markdown → copy rich text → paste into Notion, LinkedIn, X Articles, or Medium. md2rich.com — zero tracking, zero upload, free.