Skip to main content

md2mrkdwn

CI pypi downloads versions license

Pure Python library for converting Markdown to Slack's mrkdwn format. Zero dependencies, comprehensive formatting support, and proper handling of edge cases.

Features

  • Zero dependencies - Pure Python implementation with no external packages required
  • Comprehensive formatting - Supports bold, italic, strikethrough, links, images, lists, and more
  • Configurable - Customize symbols, formats, and enable/disable specific conversions
  • Code block handling - Preserves content inside code blocks without conversion
  • Table support - Wraps markdown tables in code blocks for Slack display
  • Task lists - Converts checkbox syntax to Unicode symbols (☐/☑)
  • Edge case handling - Properly handles nested formatting and special characters

Quick Start

from md2mrkdwn import convert

markdown = "**Hello** *World*! Check out [Slack](https://slack.com)"
mrkdwn = convert(markdown)
print(mrkdwn)
# Output: *Hello* _World_! Check out <https://slack.com|Slack>

Installation

# Install with pip
pip install md2mrkdwn

# Or install with uv
uv add md2mrkdwn

# Or install with pipx (for CLI tools that use this library)
pipx install md2mrkdwn

Usage

Simple Function

The convert() function provides a simple interface for one-off conversions:

from md2mrkdwn import convert

markdown = """
# Hello World

This is **bold** and *italic* text.

- Item 1
- Item 2

Check out [this link](https://example.com)!
"""

mrkdwn = convert(markdown)
print(mrkdwn)

Output:

*Hello World*

This is *bold* and _italic_ text.

• Item 1
• Item 2

Check out <https://example.com|this link>!

Class-based Usage

For multiple conversions, use the MrkdwnConverter class:

from md2mrkdwn import MrkdwnConverter

converter = MrkdwnConverter()

# Convert multiple texts
text1 = converter.convert("**bold** and *italic*")
text2 = converter.convert("# Header\n\n- List item")

print(text1)  # *bold* and _italic_
print(text2)  # *Header*\n\n• List item

Custom Configuration

Use MrkdwnConfig to customize conversion behavior:

from md2mrkdwn import convert, MrkdwnConfig, MrkdwnConverter

# Custom bullet character
config = MrkdwnConfig(bullet_char="-")
print(convert("- Item 1\n- Item 2", config=config))
# Output: - Item 1
#         - Item 2

# Custom checkbox symbols
config = MrkdwnConfig(checkbox_checked="✓", checkbox_unchecked="○")
print(convert("- [x] Done\n- [ ] Todo", config=config))
# Output: • ✓ Done
#         • ○ Todo

# Plain headers (no bold)
config = MrkdwnConfig(header_style="plain")
print(convert("# Title", config=config))
# Output: Title

# URL-only links (no link text)
config = MrkdwnConfig(link_format="url_only")
print(convert("[Click here](https://example.com)", config=config))
# Output: <https://example.com>

# Disable specific conversions
config = MrkdwnConfig(convert_bold=False, convert_italic=False)
print(convert("**bold** and *italic*", config=config))
# Output: **bold** and *italic*

# Reusable converter with config
converter = MrkdwnConverter(MrkdwnConfig(
    bullet_char="→",
    horizontal_rule_char="=",
    horizontal_rule_length=20
))
print(converter.convert("- Item\n\n---"))
# Output: → Item
#
#         ====================

Configuration Options

Option Type Default Description
bullet_char str Character for unordered list items
checkbox_checked str Symbol for checked task items
checkbox_unchecked str Symbol for unchecked task items
horizontal_rule_char str Character for horizontal rules
horizontal_rule_length int 10 Length of horizontal rules
header_style HeaderStyle HeaderStyle.BOLD BOLD, PLAIN, or PREFIX
link_format LinkFormat LinkFormat.SLACK SLACK, URL_ONLY, or TEXT_ONLY
table_mode TableMode TableMode.CODE_BLOCK CODE_BLOCK or PRESERVE
table_link_format LinkFormat LinkFormat.URL_ONLY Link format inside tables
strip_table_emoji bool True Strip emoji shortcodes from tables
convert_table_links bool True Enable/disable link conversion in tables
convert_bold bool True Enable/disable bold conversion
convert_italic bool True Enable/disable italic conversion
convert_strikethrough bool True Enable/disable strikethrough conversion
convert_links bool True Enable/disable link conversion
convert_images bool True Enable/disable image conversion
convert_lists bool True Enable/disable list conversion
convert_task_lists bool True Enable/disable task list conversion
convert_headers bool True Enable/disable header conversion
convert_horizontal_rules bool True Enable/disable horizontal rule conversion
convert_tables bool True Enable/disable table wrapping

Handling Tables

Markdown tables are automatically wrapped in code blocks since Slack doesn't support native table rendering:

from md2mrkdwn import convert

markdown = """
| Name | Age |
|------|-----|
| Alice | 30 |
| Bob | 25 |
"""

print(convert(markdown))

Output:

Name Age
Alice 30
Bob 25

Links in Tables

Links inside tables are converted to URL-only format by default (different from the global link_format setting):

from md2mrkdwn import convert, MrkdwnConfig, LinkFormat

markdown = """
| App | Link |
|-----|------|
| Example | [Visit](https://example.com) |
"""

# Default: URL only
print(convert(markdown))
# | App | Link |
# | Example | https://example.com |

# Slack format
config = MrkdwnConfig(table_link_format=LinkFormat.SLACK)
print(convert(markdown, config))
# | Example | <https://example.com|Visit> |

# Text only (link text, no URL)
config = MrkdwnConfig(table_link_format=LinkFormat.TEXT_ONLY)
print(convert(markdown, config))
# | Example | Visit |

Conversion Reference

Markdown mrkdwn Notes
**bold** or __bold__ *bold* Slack uses single asterisk
*italic* or _italic_ _italic_ Slack uses underscores
***bold+italic*** *_text_* Combined formatting
~~strikethrough~~ ~text~ Single tilde
[text](url) <url|text> Slack link format
![alt](url) <url> Images become plain URLs
# Header (all levels) *Header* Bold (Slack has no headers)
- item / * item • item Bullet character (U+2022)
1. item 1. item Preserved as-is
- [ ] task • ☐ task Unchecked checkbox (U+2610)
- [x] task • ☑ task Checked checkbox (U+2611)
> quote > quote Same syntax
`code` `code` Same syntax
code block code block Same syntax
--- / *** ────────── Horizontal rule (U+2500)
Tables Wrapped in ``` Slack has no native tables

How It Works

Conversion Pipeline

md2mrkdwn processes text through a multi-stage pipeline:

  1. Table extraction - Tables are detected, validated, and replaced with placeholders
  2. Code block tracking - Lines inside code blocks are skipped during conversion
  3. Pattern application - Regex patterns convert formatting using placeholder protection
  4. Placeholder restoration - Tables and temporary markers are replaced with final output

Pattern Interference Prevention

A key challenge in markdown conversion is preventing patterns from interfering with each other. For example, converting **bold** to *bold* could then be matched by the italic pattern.

md2mrkdwn solves this using placeholder substitution:

  1. Bold text is temporarily marked with null-byte placeholders
  2. Italic patterns run without matching the placeholders
  3. Placeholders are replaced with final mrkdwn characters

Table Handling

Tables are detected using these criteria:

  • Lines matching |...| pattern
  • Second row contains separator cells (dashes with optional alignment colons)
  • Header and separator have matching column counts

Valid tables are wrapped in triple-backtick code blocks for monospace display in Slack.

Column alignment accounts for Unicode character display width. Emoji like ⭐ render as 2 columns wide in monospace fonts but have a character length of 1. The converter pads columns based on display width to maintain proper alignment.

Code Block Protection

Content inside code blocks (both fenced and inline) is protected from conversion:

  • Fenced blocks: State machine tracks opening/closing ``` markers
  • Inline code: Segments are extracted before conversion and restored after

Development

Setup

git clone https://github.com/bigbag/md2mrkdwn.git
cd md2mrkdwn
make install

Commands

make install  # Install all dependencies
make test     # Run tests with coverage
make lint     # Run linters (ruff + mypy)
make format   # Format code with ruff
make clean    # Clean cache and build files

Running Tests

# Run all tests with coverage
uv run pytest --cov=md2mrkdwn --cov-report=term-missing

# Run specific test class
uv run pytest tests/test_converter.py::TestBasicFormatting -v

# Run with verbose output
uv run pytest -v

Project Structure

md2mrkdwn/
├── src/
│   └── md2mrkdwn/
│       ├── __init__.py      # Package exports
│       └── converter.py     # MrkdwnConverter, MrkdwnConfig classes
├── tests/
│   ├── conftest.py          # Pytest fixtures
│   ├── test_converter.py    # Converter tests
│   └── test_config.py       # Configuration tests
├── pyproject.toml           # Project configuration
├── Makefile                 # Development commands
└── README.md

API Reference

convert(markdown: str, config: MrkdwnConfig | None = None) -> str

Convert Markdown text to Slack mrkdwn format.

Parameters:

  • markdown - Input text in Markdown format
  • config - Optional configuration (uses defaults if not provided)

Returns:

  • Text converted to Slack mrkdwn format

MrkdwnConverter

Class for converting Markdown to mrkdwn.

Constructor:

  • MrkdwnConverter(config: MrkdwnConfig | None = None)

Methods:

  • convert(markdown: str) -> str - Convert Markdown text to mrkdwn

Example:

converter = MrkdwnConverter()
result = converter.convert("**Hello** *World*")

# With custom config
config = MrkdwnConfig(bullet_char="-")
converter = MrkdwnConverter(config)
result = converter.convert("- Item")

MrkdwnConfig

Immutable configuration dataclass for customizing conversion behavior.

Example:

from md2mrkdwn import MrkdwnConfig, DEFAULT_CONFIG

# Create custom config
config = MrkdwnConfig(
    bullet_char="→",
    header_style="plain",
    convert_bold=False
)

# Use the default config singleton
print(DEFAULT_CONFIG.bullet_char)  # •

See Also

License

MIT License - see LICENSE file.

Release files for md2mrkdwn 0.4.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for md2mrkdwn 0.4.3
File Size Uploaded
md2mrkdwn-0.4.3.tar.gz 12.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for md2mrkdwn 0.4.3
File Interpreter ABI Platform
md2mrkdwn-0.4.3-py3-none-any.whl Python 3 none any Details

Total release size: 25.0 kB

Release files / md2mrkdwn-0.4.3.tar.gz

Download URL md2mrkdwn-0.4.3.tar.gz
Size 12.2 kB
Tags Source
SHA-256 checksum
How to use checksums
f460de3dcdc090605e2a5e393e16a04073aff8adaf5fadbc37cb9a64ad287e9c
BLAKE2b-256 checksum
How to use checksums
1bc31e4a2c07c6371311da9ffd0bc16c444ffb9f4a5c4b2bad8acfd9dfe430f2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jan 23, 2026.

Transparency log

Release files / md2mrkdwn-0.4.3-py3-none-any.whl

Download URL md2mrkdwn-0.4.3-py3-none-any.whl
Size 12.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1799b761099090994ec16413a5c48663c642577927326b9228e54c19efd2ae5e
BLAKE2b-256 checksum
How to use checksums
650c4b18a4b12c6d80e9c4e00efb165db5b2f1ba82af636a6d2112201ab07c69
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jan 23, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.3 This release

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page