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:
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:
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, links are reduced to their display text
([GitHub](https://github.com) → GitHub). Pass preserve_links=True to
keep the full link syntax, including any title. Autolinks and bare URLs come
out in angle brackets:
convert("[GitHub](https://github.com)", preserve_links=True)
# [GitHub](https://github.com)
convert("See https://github.com", preserve_links=True)
# See <https://github.com>
out = 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:
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')") # 𝚙𝚛𝚒𝚗𝚝('𝚑𝚒')
# Choose the style at runtime: "bold", "italic" or "bold_italic"
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 𝙗𝙤𝙡𝙙-𝙞𝙩𝙖𝙡𝙞𝙘 |
~~strikethrough~~ |
strikethrough (markers dropped) |
==highlight== |
highlight (markers dropped) |
`inline code` |
Unicode 𝚖𝚘𝚗𝚘𝚜𝚙𝚊𝚌𝚎 (backticks stripped) |
```fenced block``` |
Unicode 𝚖𝚘𝚗𝚘𝚜𝚙𝚊𝚌𝚎 (fences stripped) |
| Four-space indented block | Unicode 𝚖𝚘𝚗𝚘𝚜𝚙𝚊𝚌𝚎 (indent stripped) |
# H1 |
Bold uppercase Unicode + ━ borders |
## H2–###### H6 |
Bold Unicode, no border |
Title + ===== / ----- (setext) |
Same as # H1 / ## H2 |
[text](url) |
text (URL discarded) |
[text][ref] + [ref]: url |
text (definition consumed) |
<url> and bare URLs |
The URL, unstyled |
 |
alt text (URL discarded) |
- item |
• item |
- nested |
‣ nested |
- nested twice |
◦ nested twice |
1. item / 1) item |
1. item (renumbered in sequence) |
\| a \| b \| table |
Bold header, cells joined by \| |
> blockquote |
> prefix removed, at every depth |
--- / * * * thematic break |
Dropped |
Two trailing spaces, <br> |
A line break |
<span>...</span> |
Tags removed, text kept |
<script> / <style> |
Dropped, content and all |
<!-- comment --> |
Dropped |
& / > etc. |
Decoded to & / > |
\* backslash escapes |
Resolved to literal * |
| Emojis, accented chars | Passed through unchanged |
| Digits inside bold | Also converted (**123** → 𝟭𝟮𝟯) |
| Digits inside italic / bold-italic | Unchanged: Unicode has no such digits |
_snake_case_ in middle of word |
Not italicised |
Two GitHub extensions are deliberately not enabled, because LinkedIn has
nowhere to put their output: task-list checkboxes stay as the literal text
[ ] / [x] in front of the bullet, and footnotes are read as ordinary link
syntax, so text[^1] becomes text^1 and the [^1]: … definition is
consumed. Strip both before converting if you need them gone.
How Conversion Works
convert() is a two-stage pipeline:
comrak, Python bindings to a Rust CommonMark parser, parses the Markdown and renders it to HTML, with the tables, strikethrough, autolink and highlight extensions enabled.selectolaxparses that HTML, and a small renderer walks the tree, replacing each element with its LinkedIn equivalent: Unicode styling for emphasis and code, bullets and numbers for lists,━rules around a top-level heading.
Nothing is ever rendered as HTML. The intermediate document only exists so that the renderer sees a structured tree instead of a stream of characters, so raw HTML in the input cannot inject anything into the output — its tags are read for their structure and dropped.
The practical consequence is that every construct is read the way CommonMark
defines it, in context: a four-space indent is a code block rather than prose,
a > inside a > is a second level of quoting, a [ref]: url definition is
consumed rather than printed, and a thematic break inside a list item is a
break rather than a bullet.
Handling Edge Cases
Nested Formatting
Emphasis nests structurally, so the styles combine: italic inside bold comes out bold-italic, while the rest of the outer span stays bold:
convert("***very important***") # → 𝙫𝙚𝙧𝙮 𝙞𝙢𝙥𝙤𝙧𝙩𝙖𝙣𝙩
convert("**bold and *italic* inside**") # → 𝗯𝗼𝗹𝗱 𝗮𝗻𝗱 𝙞𝙩𝙖𝙡𝙞𝙘 𝗶𝗻𝘀𝗶𝗱𝗲
Nested Bullet Lists
Each nesting level gets its own marker — •, ‣, ◦, ▪ — and two spaces
of indentation. Levels deeper than the fourth reuse ▪, with the indentation
still growing by two spaces per level.
Depth is counted from the parsed tree, not from the raw indentation, so a document indented by four spaces per level nests exactly like one indented by two. Conversely, an item indented one space past its predecessor stays its sibling, since CommonMark allows a top-level item up to three leading spaces.
Ordered items are numbered in sequence from the list's first number, so
1., 1., 1. becomes 1., 2., 3. and a list that starts at 3. keeps
counting from three. A ) delimiter becomes .. Bullets nested under an
ordered item are indented to the width of its marker, so they line up under
its text rather than under its number.
Everything else an item holds lines up the same way. A continuation paragraph, a wrapped line or a fenced code block inside an item is indented to the column the marker opened, and a loose list keeps the blank line between its items:
Code Is Rendered in Monospace
By default, inline code and code blocks are converted to Unicode
Mathematical Monospace characters. Only ASCII letters and digits are mapped;
everything else, including Markdown syntax like **, passes through
unchanged:
convert("Use `**bold**` in Markdown")
# → Use **𝚋𝚘𝚕𝚍** in Markdown (backticks stripped, letters monospaced, ** kept)
convert("```\nprint('hi')\n```")
# → 𝚙𝚛𝚒𝚗𝚝('𝚑𝚒') (fences stripped, content monospaced)
To keep code as plain text instead:
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)
# → **not bold** (fences stripped, content as plain text)
Either way the content itself is untouched: nothing inside a code span or
block is read as Markdown, so the ** above survives.
Underscore Italic vs. Snake Case
Single underscores are italicised only when they sit at word boundaries:
Unmatched / Orphaned Markers
Unmatched asterisks or underscores are left untouched — md2linkedin never
crashes on malformed input:
Large Documents
convert() does no I/O and holds only the document it is given, the HTML it
renders from it, and the output. Parsing happens in Rust and C rather than in
Python, and the cost scales linearly with input length, so it is safe to call
on large files:
Emojis and Non-ASCII Characters
Emojis and accented characters pass through unchanged — only ASCII letters and digits are remapped to Unicode Mathematical variants:
from md2linkedin import convert
result = convert("**Excited to share** 🎉 *check this out*!")
print(result) # 𝗘𝘅𝗰𝗶𝘁𝗲𝗱 𝘁𝗼 𝘀𝗵𝗮𝗿𝗲 🎉 𝘤𝘩𝘦𝘤𝘬 𝘵𝘩𝘪𝘴 𝘰𝘶𝘵!
Integration with Pandoc (LaTeX → LinkedIn)
If you write in LaTeX (e.g. a résumé), you can pipe through pandoc first:
Or in Python:
import subprocess
from pathlib import Path
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
Run md2linkedin --help for the full text.
Usage: md2linkedin [OPTIONS] [INPUT_FILE]
Options:
-V, --version Show the version and exit.
-o, --output FILE Output file path. Defaults to INPUT_FILE with a
'.linkedin.txt' extension. Ignored when reading from
stdin.
--preserve-links Keep Markdown link syntax ([text](url)) in the output
instead of stripping URLs.
--no-monospace-code Disable monospace Unicode rendering for inline code and
fenced code blocks. When set, code is kept as plain
text, with the backticks and fences stripped.
-h, --help Show this message and exit.