Skip to main content

Convert Markdown to Slack mrkdwn format

Project description

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.

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

md2mrkdwn-0.3.1.tar.gz (11.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

md2mrkdwn-0.3.1-py3-none-any.whl (11.9 kB view details)

Uploaded Python 3

File details

Details for the file md2mrkdwn-0.3.1.tar.gz.

File metadata

  • Download URL: md2mrkdwn-0.3.1.tar.gz
  • Upload date:
  • Size: 11.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for md2mrkdwn-0.3.1.tar.gz
Algorithm Hash digest
SHA256 cfffaf017385ce6b97c80db3f09e144f4aa6624f59d81a4d291c5b348d428d3e
MD5 1a3564aa9ffabbd765d968d321bac96c
BLAKE2b-256 02be8428854cd1e342cfd79610683f01eedcb7b76c4cd083ece0fedcd9f0c7f3

See more details on using hashes here.

Provenance

The following attestation bundles were made for md2mrkdwn-0.3.1.tar.gz:

Publisher: publish.yml on bigbag/md2mrkdwn

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file md2mrkdwn-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: md2mrkdwn-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 11.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for md2mrkdwn-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 64194bbdc44d8ce99ed00678f6002570eda2c82a90c028ef7d74910752e1d631
MD5 ca607d6580ab028d048e53b6379e8d0b
BLAKE2b-256 088ed94ceadd214c3215bc99febc2d173a07596c4c8485c1081367117ca9199c

See more details on using hashes here.

Provenance

The following attestation bundles were made for md2mrkdwn-0.3.1-py3-none-any.whl:

Publisher: publish.yml on bigbag/md2mrkdwn

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page