md2linkedin is a Python library and CLI tool that converts Markdown text to
LinkedIn-compatible Unicode plain text. Bold and italic formatting is preserved
using Unicode Mathematical Sans-Serif characters so styling survives when pasting
into platforms that do not support Markdown.
## Home documentation
### index
# md2linkedin
[](https://pypi.org/project/md2linkedin/)

[](https://pypistats.org/packages/md2linkedin)
`md2linkedin` converts Markdown text to LinkedIn-compatible plain text
by replacing bold, italic, and bold-italic markers with Unicode
Mathematical Sans-Serif characters. This preserves visual formatting
when pasting into platforms like LinkedIn that do not support Markdown
natively.
## Installation
| Package Manager | Installation Command |
|-----------------|---------------------------|
| pip | `pip install md2linkedin` |
| uv | `uv add md2linkedin` |
> **Tip:** Run the CLI without installing it with
> `uvx md2linkedin post.md`.
## Usage
### Python API
``` python
# @pyodide
from md2linkedin import convert
md = """
# Exciting News
I'm thrilled to share that **we just launched** a new product!
Key highlights:
- **Performance**: *3x faster* than the previous version
- **Reliability**: ***zero downtime*** deployments
- **Developer UX**: clean, intuitive API
Check it out and let me know what you think.
"""
print(convert(md))
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
𝗘𝗫𝗖𝗜𝗧𝗜𝗡𝗚 𝗡𝗘𝗪𝗦
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
I'm thrilled to share that 𝘄𝗲 𝗷𝘂𝘀𝘁 𝗹𝗮𝘂𝗻𝗰𝗵𝗲𝗱 a new product!
Key highlights:
• 𝗣𝗲𝗿𝗳𝗼𝗿𝗺𝗮𝗻𝗰𝗲: 3𝘹 𝘧𝘢𝘴𝘵𝘦𝘳 than the previous version
• 𝗥𝗲𝗹𝗶𝗮𝗯𝗶𝗹𝗶𝘁𝘆: 𝙯𝙚𝙧𝙤 𝙙𝙤𝙬𝙣𝙩𝙞𝙢𝙚 deployments
• 𝗗𝗲𝘃𝗲𝗹𝗼𝗽𝗲𝗿 𝗨𝗫: clean, intuitive API
Check it out and let me know what you think.
### CLI
``` bash
# Convert a Markdown file (output: post.linkedin.txt)
md2linkedin post.md
# Specify output path
md2linkedin post.md -o linkedin_post.txt
# Pipe from stdin
echo "**Hello**, *world*!" | md2linkedin
# Keep link URLs in the output
md2linkedin post.md --preserve-links
# Disable monospace code rendering
md2linkedin post.md --no-monospace-code
```
## Key Features
- **Bold**: `**text**` or `__text__` → Unicode Sans-Serif Bold (𝗯𝗼𝗹𝗱)
- **Italic**: `*text*` or `_text_` → Unicode Sans-Serif Italic (𝘪𝘵𝘢𝘭𝘪𝘤)
- **Bold-italic**: `***text***` or `___text___` → Unicode Sans-Serif
Bold Italic (𝙗𝙤𝙡𝙙-𝙞𝙩𝙖𝙡𝙞𝙘)
- **Headers**: `#`/`##`/etc. styled with bold Unicode; H1 gets a `━`
border
- **Code spans**: backticks stripped, content rendered in Unicode
Monospace (𝚌𝚘𝚍𝚎) by default; `--no-monospace-code` keeps plain text
- **Fenced code blocks**: fences stripped and content rendered in
Unicode Monospace by default; `--no-monospace-code` preserves verbatim
- **Links**: stripped to display text by default; `--preserve-links`
retains URLs
- **Images**: replaced by alt text
- **Bullet lists**: `-`/`*`/`+` → `•`; nested items → `‣`
- **Blockquotes**: leading `>` stripped
- **HTML spans**: unwrapped, inner text preserved
- **HTML entities**: decoded (`&` → `&`, etc.)
- **Backslash escapes**: resolved (`\*` → `*`)
- **Windows line endings**: normalised automatically
- **Emojis & non-ASCII**: pass through unchanged — no accidental
corruption
## Limitations
Converting Markdown to LinkedIn-friendly text relies on Unicode
Mathematical Alphanumeric Symbols to simulate styling. This approach has
notable limitations:
- **Code Blocks**: Monospace Unicode characters do not enforce true
fixed-width alignment on proportional fonts (like LinkedIn’s default
font). As a result, indentation and column alignment in code blocks
will often break visually.
- **Tables**: Markdown tables are not converted — they pass through as
raw pipe syntax (`| col | col |`), which LinkedIn does not render,
producing unreadable output.
- **Accessibility**: Screen readers often read Unicode mathematical
characters aloud individually (e.g., “mathematical sans-serif bold b”)
instead of as complete words, making the content difficult for
visually impaired users to understand.
- **Searchability**: Text styled with these Unicode characters may not
be indexed properly by LinkedIn’s search algorithm, meaning people
searching for your keywords might not find your post.
For more examples, check out the package documentation at:
## License
This project is licensed under the MIT License.
## Code of Conduct
Please note that the md2linkedin project is released with a [Contributor
Code of
Conduct](https://www.contributor-covenant.org/version/3/0/code_of_conduct/).
By contributing to this project, you agree to abide by its terms.
## Acknowledgements
Hex sticker font is `Rubik`. Icons are sourced from
[Flaticon](https://www.flaticon.com/):
- Markdown icon by [Freepik](https://www.flaticon.com/authors/freepik)
- LinkedIn icon by [Freepik](https://www.flaticon.com/authors/freepik)
- Arrow/conversion icon by
[Freepik](https://www.flaticon.com/authors/freepik)
## Advanced usage
### advanced
# Advanced Usage
## Programmatic Use
### Converting Strings in Memory
The simplest entry point is `convert()`, which accepts a Markdown string and
returns a plain-text string ready for LinkedIn:
```python
from md2linkedin import convert
result = convert("**Hello**, *world*!")
print(result) # 𝗛𝗲𝗹𝗹𝗼, 𝘸𝘰𝘳𝘭𝘥!
```
### Converting Files
Use `convert_file()` to read a `.md` file and write the output to a `.txt`
file. The function returns the `Path` of the written file so you can chain
further operations:
```python
from md2linkedin import convert_file
out = convert_file("post.md")
print(f"Written to: {out}") # post.linkedin.txt
# Override the output path
out = convert_file("post.md", "my_post.txt")
```
### Stripping and Preserving Links
By default, Markdown link syntax is stripped to display text only
(`[GitHub](https://github.com)` → `GitHub`). This is useful for removing clickable URLs from your output, keeping only the descriptive text.
Pass `preserve_links=True` to retain the full link syntax:
```python
result = convert("[GitHub](https://github.com)", preserve_links=True)
# [GitHub](https://github.com)
result = convert_file("post.md", preserve_links=True)
```
### Direct Unicode Mapping
The Unicode mapping functions are public and useful for applying a specific
style to a plain string programmatically:
```python
from md2linkedin import (
to_sans_bold,
to_sans_italic,
to_sans_bold_italic,
to_monospace,
apply_style,
)
to_sans_bold("Open to Work") # 𝗢𝗽𝗲𝗻 𝘁𝗼 𝗪𝗼𝗿𝗸
to_sans_italic("3 years of exp") # 3 𝘺𝘦𝘢𝘳𝘴 𝘰𝘧 𝘦𝘹𝘱
to_sans_bold_italic("Key insight") # 𝙆𝙚𝙮 𝙞𝙣𝙨𝙞𝙜𝙝𝙩
to_monospace("print('hi')") # 𝚙𝚛𝚒𝚗𝚝('𝚑𝚒')
# Dynamic dispatch
apply_style("Hiring!", "bold")
```
---
## What Gets Transformed (and What Doesn't)
| Markdown construct | Output |
|--------------------|--------|
| `**bold**` / `__bold__` | Unicode 𝗯𝗼𝗹𝗱 |
| `*italic*` / `_italic_` | Unicode 𝘪𝘵𝘢𝘭𝘪𝘤 |
| `***bold-italic***` / `___bold-italic___` | Unicode 𝙗𝙤𝙡𝙙-𝙞𝙩𝙖𝙡𝙞𝙘 |
| `` `inline code` `` | Unicode 𝚖𝚘𝚗𝚘𝚜𝚙𝚊𝚌𝚎 (backticks stripped) |
| ` ```fenced block``` ` | Unicode 𝚖𝚘𝚗𝚘𝚜𝚙𝚊𝚌𝚎 (fences stripped) |
| `# H1` | Bold Unicode + `━` border |
| `## H2`–`###### H6` | Bold Unicode, no border |
| `[text](url)` | `text` (URL discarded) |
| `` | `alt` text (URL discarded) |
| `- item` | `• item` |
| ` - nested` | ` ‣ nested` |
| `> blockquote` | `> ` prefix removed |
| `...` | Tags removed, text kept |
| `&` / `>` etc. | Decoded to `&` / `>` |
| `\*` backslash escapes | Resolved to literal `*` |
| Emojis, accented chars | Passed through unchanged |
| Digits inside bold | Also converted (`**123**` → `𝟭𝟮𝟯`) |
| `_snake_case_` in middle of word | **Not** italicised |
---
## Handling Edge Cases
### Nested Formatting
`md2linkedin` handles nesting by processing bold-italic (`***`) first,
ensuring it is not accidentally consumed piecemeal:
```python
convert("***very important***") # → bold-italic Unicode
convert("**bold and *italic* inside**") # → bold wrapping italic
```
### Code Is Rendered in Monospace
By default, inline code and fenced code blocks are converted to Unicode
Mathematical Monospace characters. Only ASCII letters and digits are mapped;
all other characters (including Markdown syntax like `**`) pass through
unchanged — no nested processing is performed:
```python
convert("Use `**bold**` in Markdown")
# → Use **𝚋𝚘𝚕𝚍** in Markdown (backticks stripped, letters monospaced, ** kept)
convert("```\nprint('hi')\n```")
# → 𝚙𝚛𝚒𝚗𝚝('𝚑𝚒') (fences stripped, content monospaced)
```
To disable monospace rendering and restore the previous plain-text behavior:
```python
convert("Use `**bold**` in Markdown", monospace_code=False)
# → Use **bold** in Markdown (backticks stripped, content as plain text)
convert("```\n**not bold**\n```", monospace_code=False)
# → ```\n**not bold**\n``` (fenced block fully preserved)
```
### Underscore Italic vs. Snake Case
Single underscores are italicised only when they sit at word boundaries:
```python
convert("_italic_") # → italic Unicode
convert("snake_case_variable") # → unchanged
```
### Unmatched / Orphaned Markers
Unmatched asterisks or underscores are left untouched — `md2linkedin` never
crashes on malformed input:
```python
convert("price: $10 * 3") # → price: $10 * 3 (no crash)
convert("under_score alone") # → unchanged
```
### Large Documents
`convert()` is a pure-Python, single-pass function with no I/O. It scales
linearly with input length and is safe to call in hot paths or on large files:
```python
big_md = Path("quarterly_report.md").read_text()
result = convert(big_md)
```
### Emojis and Non-ASCII Characters
Emojis and accented characters pass through unchanged — only ASCII letters
and digits are remapped to Unicode Mathematical variants:
```python
from md2linkedin import convert
result = convert("**Excited to share** 🎉 *check this out*!")
print(result) # 𝗘𝘅𝗰𝗶𝘁𝗲𝗱 𝘁𝗼 𝘀𝗵𝗮𝗿𝗲 🎉 𝘤𝘩𝘦𝘤𝘬 𝘵𝘩𝘪𝘴 𝘰𝘂𝘵!
```
---
## Running Without Installing (uvx)
If you only need `md2linkedin` occasionally, you can run it directly with
[`uvx`](https://docs.astral.sh/uv/guides/tools/) without a permanent
installation:
```bash
# Convert a Markdown file (output: post.linkedin.txt)
uvx md2linkedin post.md
# Pipe from stdin
echo "**bold** and *italic*" | uvx md2linkedin
# Preserve link syntax
uvx md2linkedin --preserve-links post.md
# Explicit output path
uvx md2linkedin post.md -o linkedin_post.txt
# Disable monospace code rendering
uvx md2linkedin --no-monospace-code post.md
```
`uvx` downloads and caches the package in an isolated environment, so
subsequent runs are fast.
---
## Integration with Pandoc (LaTeX → LinkedIn)
If you write in LaTeX (e.g. a résumé), you can pipe through `pandoc` first:
```bash
pandoc --from=latex --to=markdown_strict --wrap=none resume.tex | md2linkedin
```
Or in Python:
```python
import subprocess
from md2linkedin import convert
result = subprocess.run(
["pandoc", "--from=latex", "--to=markdown_strict", "--wrap=none"],
input=Path("resume.tex").read_text(),
capture_output=True,
text=True,
check=True,
)
linkedin_text = convert(result.stdout)
```
---
## CLI Reference
```
Usage: md2linkedin [OPTIONS] [INPUT_FILE]
Convert Markdown to LinkedIn-friendly Unicode text.
Options:
-o, --output PATH Output file path.
--preserve-links Keep link syntax in output.
--no-monospace-code Disable monospace Unicode rendering for code.
-V, --version Show the version and exit.
-h, --help Show this message and exit.
```
## API documentation
### converter
# API Reference
## High-level API
::: md2linkedin.convert
::: md2linkedin.convert_file
## Unicode Mapping
::: md2linkedin.apply_style
::: md2linkedin.to_monospace
::: md2linkedin.to_sans_bold
::: md2linkedin.to_sans_italic
::: md2linkedin.to_sans_bold_italic
## Changelog
### changelog
--8<-- "CHANGELOG.md"