
Markdown To Pdf
- 4 installs
- 2 repo stars
- Updated July 18, 2026
- netresearch/markdown-to-pdf-skill
Helps with ai & agent building tasks.
About
markdown-to-pdf is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- markdown-to-pdf
- AI & Agent Building
- AI-coding skill
Markdown To Pdf by the numbers
- 4 all-time installs (skills.sh)
- Ranked #13,372 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/netresearch/markdown-to-pdf-skill --skill markdown-to-pdfAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4 |
|---|---|
| repo stars | ★ 2 |
| Last updated | July 18, 2026 |
| Repository | netresearch/markdown-to-pdf-skill ↗ |
What it does
Helps with ai & agent building tasks.
Files
Markdown to PDF
Convert one or more Markdown files into styled PDFs using WeasyPrint and the Python markdown library. The default styling is intentionally neutral — apply your own brand stylesheet via --css.
How to use
Run the conversion script via uv run:
uv run --with markdown --with weasyprint python3 "${SKILL_DIR}/scripts/convert.py" <files...> [-o output_dir] [--css custom.css]${SKILL_DIR} is the directory containing this SKILL.md. The script resolves assets/style.css relative to its own location, so it works regardless of install path.
Steps
1. Identify target .md files from the user's request. If none specified, look for .md files in the current directory and ask which to convert. 2. Run the conversion:
uv run --with markdown --with weasyprint python3 <skill-dir>/scripts/convert.py file1.md file2.md- Use
-o <dir>to place PDFs in a specific output directory. - Use
--css <path>to override the default neutral stylesheet (e.g., a brand stylesheet fromnetresearch-branding-skill/assets/markdown-pdf.css). - Glob patterns like
*.mdare supported.
3. Report which PDF files were created and their locations.
Default styling
The bundled assets/style.css provides:
- system fonts (no external font fetches)
- neutral grayscale headers
- printable code blocks with monospace font
- A4 page size with sensible margins
- page numbers in footer
For branded output, supply a --css value pointing at your organisation's stylesheet (logo, colours, fonts, headers).
Companion skills
- `netresearch-branding-skill` ships a
markdown-pdf.cssbrand asset. Internal Netresearch users: install both skills, then--css "$(echo $CLAUDE_PLUGIN_ROOT/.../netresearch-branding-skill/.../assets/markdown-pdf.css)".
Output format
Per file:
✓ converted README.md → README.pdf (12.3 KB)
✓ converted RFC-001.md → RFC-001.pdf (4.7 KB)Errors
| Error | Action |
|---|---|
No .md files matched | List directory contents and ask user |
| WeasyPrint missing | uv run should auto-resolve it; if not, suggest uv pip install weasyprint |
--css file not found | Surface the missing path; do not fall back silently |
/* Neutral default stylesheet for markdown-to-pdf. Override with --css. */
@page {
size: A4;
margin: 2cm;
@bottom-center {
content: counter(page) " / " counter(pages);
font-family: serif;
font-size: 9pt;
color: #666;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue",
Arial, sans-serif;
font-size: 11pt;
line-height: 1.5;
color: #222;
}
h1 {
font-size: 22pt;
margin: 0 0 0.6em 0;
page-break-after: avoid;
border-bottom: 1px solid #ccc;
padding-bottom: 0.2em;
}
h2 {
font-size: 16pt;
margin: 1.2em 0 0.4em 0;
page-break-after: avoid;
}
h3 {
font-size: 13pt;
margin: 1em 0 0.3em 0;
page-break-after: avoid;
}
h4,
h5,
h6 {
margin: 0.8em 0 0.3em 0;
page-break-after: avoid;
}
p {
margin: 0 0 0.7em 0;
}
a {
color: #1d4ed8;
text-decoration: underline;
}
code {
font-family: "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
font-size: 0.92em;
background: #f3f4f6;
padding: 0.1em 0.3em;
border-radius: 2px;
}
pre {
background: #f3f4f6;
border: 1px solid #e5e7eb;
border-radius: 3px;
padding: 0.7em;
overflow-x: auto;
font-size: 9.5pt;
line-height: 1.4;
page-break-inside: avoid;
}
pre code {
background: transparent;
padding: 0;
border-radius: 0;
}
blockquote {
border-left: 3px solid #cbd5e1;
margin: 0.5em 0;
padding: 0.1em 0 0.1em 0.8em;
color: #475569;
}
ul,
ol {
margin: 0 0 0.7em 1.5em;
padding: 0;
}
li {
margin: 0.15em 0;
}
table {
border-collapse: collapse;
margin: 0.6em 0;
width: 100%;
page-break-inside: avoid;
}
th,
td {
border: 1px solid #d1d5db;
padding: 0.4em 0.6em;
text-align: left;
vertical-align: top;
font-size: 10pt;
}
th {
background: #f3f4f6;
font-weight: 600;
}
img {
max-width: 100%;
page-break-inside: avoid;
}
hr {
border: 0;
border-top: 1px solid #e5e7eb;
margin: 1.2em 0;
}
#!/usr/bin/env python3
"""Convert Markdown files to styled PDFs using weasyprint + markdown.
Generic, brand-neutral. For Netresearch-branded output, pass
`--css path/to/netresearch-branding-skill/assets/markdown-pdf.css`.
"""
import argparse
import glob
import os
import sys
from pathlib import Path
import markdown
from weasyprint import CSS, HTML
SKILL_DIR = Path(__file__).resolve().parent.parent
DEFAULT_CSS = SKILL_DIR / "assets" / "style.css"
def convert(
input_files: list[str],
output_dir: str | None = None,
css_path: str | None = None,
) -> list[Path]:
css_file = Path(css_path) if css_path else DEFAULT_CSS
if not css_file.exists():
print(f"Error: CSS file not found: {css_file}", file=sys.stderr)
sys.exit(1)
css = css_file.read_text()
# Expand glob patterns
resolved: list[str] = []
for pattern in input_files:
matches = glob.glob(pattern)
if matches:
resolved.extend(matches)
elif Path(pattern).exists():
resolved.append(pattern)
else:
print(f"Warning: no files matched '{pattern}'", file=sys.stderr)
if not resolved:
print("Error: no input files found.", file=sys.stderr)
sys.exit(1)
written: list[Path] = []
for src in resolved:
src_path = Path(src)
if not src_path.exists():
print(f"Skipping {src}: file not found", file=sys.stderr)
continue
if output_dir:
os.makedirs(output_dir, exist_ok=True)
dst = Path(output_dir) / src_path.with_suffix(".pdf").name
else:
dst = src_path.with_suffix(".pdf")
content = src_path.read_text()
html_body = markdown.markdown(
content, extensions=["tables", "fenced_code", "toc"]
)
html_doc = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{src_path.stem}</title>
</head>
<body>
{html_body}
</body>
</html>"""
HTML(string=html_doc).write_pdf(target=str(dst), stylesheets=[CSS(string=css)])
size = dst.stat().st_size
print(f"✓ converted {src_path} → {dst} ({size / 1024:.1f} KB)")
written.append(dst)
return written
def main() -> None:
parser = argparse.ArgumentParser(
description="Convert Markdown files to styled PDFs."
)
parser.add_argument(
"input_files",
nargs="+",
help="Markdown file paths or glob patterns",
)
parser.add_argument(
"-o",
"--output-dir",
help="Output directory (default: alongside input file)",
)
parser.add_argument(
"--css",
help="Path to a custom CSS file (default: assets/style.css)",
)
args = parser.parse_args()
convert(args.input_files, args.output_dir, args.css)
if __name__ == "__main__":
main()