file-to-markdown
Convert a Word document, PowerPoint deck, Excel workbook, web page, CSV, JSON, text file, email (.eml or Outlook .msg) or PDF into clean Markdown. It keeps headings, lists, tables, links, bold and italic, slide titles and speaker notes. It saves embedded images beside the Markdown and links them, shows tracked changes as pending rather than accepted, and reports everything else it dropped (charts, comments, formulas), so the content can be read, reviewed or fed to another skill. Use when the user hands over a .docx, .pptx, .xlsx, .html, .csv, .json, .eml, .msg or .pdf and says "read this", "turn this into markdown", "extract the text", or wants it critiqued or briefed.
Other ways to install
With the sibling skills it hands off to:
The whole pack it belongs to:
By hand, from a clone of the repo:
In Claude Code, with the converters plugin installed, invoke it as:
Try it
A realistic prompt from the skill's acceptance evals:
Northwind sent us their Q3 roadmap as northwind-q3.pptx (14 slides, a few charts, speaker notes on most slides). Turn it into markdown so we can critique it.
What a good result looks like
Example output
Example: a client's files converted for review · the skill's own worked example, source
What the user said: "Acme sent their migration brief, last quarter's roadmap deck and the budget workbook, plus a link to their status page. Turn them into something we can review."
The Word, PowerPoint and Excel files below are small test files built for this example (not shipped, because the repo keeps examples as text). The web page is inputs/fabrikam-status.html; a JSON export is inputs/accounts.json. Every block of output is what the script actually printed.
acme-brief.docx
A Word brief with Title and Heading styles, bulleted and numbered lists, a hyperlink, a table, an image, a comment and a tracked change. It is in inputs/.
python3 scripts/to_markdown.py acme-brief.docx -o acme-brief.md
stderr:
converted acme-brief.docx -> acme-brief.md: 4 headings, 5 list items, 1 images saved, 1 tables, 1 tracked deletions shown as {-- --}, 1 tracked insertions shown as {++ ++}
dropped (not in the Markdown): 1 comments
note: tracked changes are pending, not accepted: {++added++} and {--removed--} mark them. Accept or reject them in Word to settle the text
The image is saved as acme-brief-images/image1.png and linked where it sat. The tracked change stays pending.
Markdown:
# Acme migration brief
## Background
We are moving **three services** to the new platform, *starting in November*. See the [project page](https://acme.example/migration).
### Goals
- Cut hosting cost
- Retire the 2014 cluster
- Including the batch jobs
### Steps
1. Inventory services
2. Pilot with billing
Architecture sketch:

| **Risk** | **Owner** | **Due** |
|---|---|---|
| Vendor lock-in | Priya | 2026-11-01 |
| Data residency \| EU | Tom | 2026-12-15 |
Open question: who signs off the cutover?
Kept text {--old wording--}{++new wording++}
northwind-roadmap.pptx
A three-slide deck with a title slide, bullets at two levels, a picture and speaker notes on slide 2. It is in inputs/.
python3 scripts/to_markdown.py northwind-roadmap.pptx -o northwind-roadmap.md
stderr:
converted northwind-roadmap.pptx -> northwind-roadmap.md: 3 slides, 1 images saved, 1 slides with speaker notes
dropped: nothing detected
Markdown:
## Slide 1: Northwind Q3 roadmap
Product review, 12 October
## Slide 2: What we shipped
- **Self-serve onboarding**
- Cut setup time from 3 days to 4 hours
- Usage-based billing

> **Speaker notes:** Mention the two pilot customers by segment, not by name.
>
> Pause for questions here.
## Slide 3: Adoption
Source: product analytics, Sept
acme-budget.xlsx
A two-sheet workbook with shared strings, a rich-text cell, two date-formatted cells, two SUM formulas and a chart.
python3 scripts/to_markdown.py acme-budget.xlsx -o acme-budget.md
stderr:
converted acme-budget.xlsx -> acme-budget.md: 2 sheets
dropped (not in the Markdown): 1 charts, 2 formulas (cached values kept)
Markdown:
## Budget
| Line item | Q3 | Q4 | Start date |
|---|---|---|---|
| Hosting | 12500 | 13250.5 | 2026-10-01 |
| Support contract | 4000 | 4000 | 2026-11-01 |
| Total | 16500 | 17250.5 | |
## Notes
| Figures are placeholders. |
|---|
contoso.docx
An HTML page saved as .docx by macOS textutil, which writes no heading styles and types bullets as characters.
python3 scripts/to_markdown.py contoso.docx -o contoso.md
stderr:
converted contoso.docx -> contoso.md: 2 headings, 4 list items
dropped: nothing detected
note: no heading styles in the document; 2 heading(s) inferred from font size and bold, so check the levels
note: list items written as typed bullets or numbers were turned into a Markdown list
Markdown:
# Contoso onboarding guide
Welcome. Read **this first** and then *the checklist*. Questions go to the help page.
## Checklist
- Get a laptop
- Join the team channel
1. Book IT setup
2. Meet your buddy
**Day**
**What**
Mon
Setup
Tue
Shadowing
The table and the link in contoso.docx were already gone before this script saw the file: textutil wrote the table cells as separate paragraphs and the link as blue underlined text. That is why the reply must say "check the levels", and why a .docx exported from Word itself converts better than one produced by textutil.
fabrikam-status.html
python3 scripts/to_markdown.py examples/inputs/fabrikam-status.html -o out.md
stderr:
converted fabrikam-status.html -> out.md: 3 headings, 1 tables, 4 list items, 1 code blocks, 1 image links
dropped (not in the Markdown): 3 scripts/styles, 1 navigation menus, 1 images (embedded data)
Markdown:
# Fabrikam service status
All systems **operational** except *search*. Details on the [incident page](https://status.fabrikam.example/search).
## Open incidents
| ID | Service | Since |
|---|---|---|
| INC-7 | Search \| EU | 09:40 UTC |
| INC-8 | Exports | 11:05 UTC |
## What to do
1. Retry the search
2. If it fails, use filters:
- by date
- by owner
> We will post the next update by 14:00 UTC.
curl -s https://status.fabrikam.example/api | jq .

accounts.json
python3 scripts/to_markdown.py examples/inputs/accounts.json -o out.md
stderr:
converted accounts.json -> out.md: 2 rows
dropped: nothing detected
Markdown:
| id | name | tags | active | region |
|---|---|---|---|---|
| 1 | Acme | \["a", "b"\] | true | |
| 2 | Northwind | | | EU |
ops-review.pdf
A two-page PDF printed from Chrome, from an HTML page of the same report, so it has a title, a nested list, a table, a chart image, a Mermaid diagram kept as text, and a code block.
python3 scripts/to_markdown.py ops-review.pdf -o ops-review.md
stderr:
converted ops-review.pdf -> ops-review.md: 6 pdf headings guessed, 1 pdf tables, 2 pdf code blocks, 2 pages
dropped: nothing detected
note: PDF gives no structure, so it is inferred from the layout: tables from aligned columns, lists from indentation, and short standalone lines as ### headings. Check them against the PDF; images and charts are not recovered
Page 1 of ops-review.md, unedited:
<!-- page 1 -->
### Northwind Health: Q3 platform review
Prepared for the operations committee. Figures are placeholders for this example.
### Summary
- Uptime held at the target for the quarter
- Two incidents, both under 30 minutes
- INC-101: login latency
- INC-102: delayed reminder emails
- Migrate reminders to the new queue
- Retire the old scheduler
Decision needed: approve the scheduler retirement for October.
### Numbers
| Metric | Q2 | Q3 | Change |
|---|---|---|---|
| Uptime | 99.90% | 99.95% | up |
| Incidents | 4 | 2 | down |
| p95 login (ms) | 410 | 380 | down |
### Weekly active members, Q1 to Q4 (illustrative)
What to tell the user: the headings were guessed from short standalone lines, and the chart image is gone (a PDF's text layer has no images). The table and the list came through. A page with two columns of prose side by side is read in column order instead, with nothing inferred, and the script says which pages those were. A table that starts at the bottom of one page and continues on the next loses its header row to the previous page.
What to tell the user (for the first three files)
I converted all three. Before we review, here is what is not in the Markdown:
- Brief: one reviewer comment. Its image is saved beside the Markdown and linked. One tracked change is still pending ("old wording" deleted, "new wording" inserted), so it is marked, not accepted: say which way it should go.
- Deck: nothing. Its picture is saved and linked, and slide 2's speaker notes are kept.
- Workbook: the chart, and the two SUM formulas. Their last calculated values (16500 and 17250.5) are in the table, but the formulas themselves are not.
Want me to run a critique of the brief next?
How it's checked
Measured against plain Claude Code (2026-10-23): it met 66% of its checks, against 39% without it, and a blind judge preferred the agent without it. How skills are checked
39 assertions across 6 acceptance evals, and 9 trigger queries. Every skill's checks
What the output must do
When it should and shouldn't activate
Should activate:
- turn this Outlook .msg email into markdown so I can paste it into the ticket
- Convert this Word doc to markdown
- Extract the text and tables from budget.xlsx so you can read it
- Here's the deck as a pptx, pull out the slide text and speaker notes
- Turn this saved HTML page into clean markdown
Should leave to another skill:
- Convert my markdown notes into a .docx for the client
- Make an HTML version of README.md I can email
- Here's a messy Slack thread, turn it into a brief for the design doc
- Review this design doc markdown and tell me what to fix
Hands off to
- doc-critique — when the user wants the converted document reviewed. Critique the content against the archetype's quality bar.
- brief-intake — when the converted document is source material for another skill. Structure it into a brief the next generator can use without more questions.
Related skills
Source
SKILL.md · the whole folder on GitHub.