LlmFiles
Converter to Markdown
LlmFiles/Guides/PPTX to Markdown

PPTX to Markdown

A slide deck is the worst-behaved input a Markdown converter can get, and it is worth saying why: a deck barely contains structure. A title is a text box with a bigger font, a bullet level is an attribute on a paragraph, and reading order is whatever order the shapes happen to sit in. Below is what this converter does with a deck, measured on test files rather than promised — including the one thing it does badly.

Updated 25 Oct 2026 · LlmFiles

A PPTX is a zip of XML, not a picture

Rename a .pptx to .zip and it opens like any Office file: one part per slide, one per layout, and two that matter — ppt/presentation.xml, which lists slides in presentation order, and ppt/slides/slideN.xml, which holds the text runs. No rule in the format forces a title to be a title; the authoring app decides that when it writes the file.

deck.pptx
├── [Content_Types].xml
├── ppt/
│   ├── presentation.xml              # sldIdLst — the slide order
│   ├── slides/slide1.xml … slide4.xml # one XML part per slide
│   ├── notesSlides/notesSlide2.xml   # speaker notes, pulled in by the slide
│   ├── charts/chart1.xml             # the numbers live here, not on the slide
│   └── media/image1.png              # pictures, renamed on import
└── docProps/core.xml                 # title, author, dates

Because the text really is text in XML, the conversion is a parse, not an OCR guess — which is why a four-slide deck returns in about a tenth of a second.

unzip -l deck.pptx | grep -E 'slides/|notes' tells you how many slides there are and how many of them carry notes before you upload anything.

What LlmFiles returns

Measured on a four-slide test deck — a title slide, bullets with sub-bullets plus speaker notes, a 4×3 table and two text boxes with mixed emphasis — 36 KB in, 640 characters out across 27 lines. The output, verbatim:

<!-- Slide number: 1 -->
# Q3 Platform Review
Engineering · October 2026

<!-- Slide number: 2 -->
# What shipped
Batch queue
up to 20 files per request
progress per file
Recent conversions in the browser
10 entries, localStorage only

### Notes:
Speaker note: mention the 25 MB free-tier limit.

<!-- Slide number: 3 -->
# Numbers
| Metric | Q2 | Q3 |
| --- | --- | --- |
| Conversions | 1,204 | 3,981 |
| p95 latency | 1.8 s | 1.1 s |
| Error rate | 0.9% | 0.3% |

<!-- Slide number: 4 -->
# Risks
Vendor lock-in: the old pipeline depends on a cloud OCR API
Free tier abuse: 12 of 18 visitors hit the limit

Three things to notice. Every slide opens with an HTML comment carrying its number, so the file can be split later without guessing where slides begin. The slide title is a real # heading — but there are four of them in a four-slide file, so the document never has a single top-level title. And the bullets arrive as bare lines: no -, no indentation, nothing that says which line belongs under which.

The mapping, measured element by element

Every row here was checked against real output, not copied from documentation:

In the deckIn the MarkdownNote
Slide title placeholder# headingOne per slide, so a deck has no single document title
Body placeholder paragraphsplain lines, one per paragraph- markers and indentation are dropped
Sub-bullets at level 1 and 2plain lines, identical to level 0The hierarchy is lost — see the next section
Speaker notes### Notes: then the textOnly on slides that have notes; no empty heading otherwise
Native chart### Chart plus a pipe tableOne row per category, one column per series
Table shapepipe table with row 0 as headerRow 0 becomes the header whether or not it is one
Merged cellstext joined by a space, covered cell emptyMeasured: | 12 48 | | 9 |
Picture![dot.png](Picture2.jpg)Alt is the filename, target is an internal part name
Text box, autoshapeplain paragraph linesFile order, not necessarily the visual order
Hyperlink inside a runthe text onlyThe URL is dropped entirely
Bold, italic, underlinedroppedFormatting inside a paragraph does not survive
Bullet glyphs, numbering, colour, themenothingPresentation, and presentation is discarded
Empty slidethe slide comment and nothing elseKept so numbering stays true
docProps/core.xml metadatanot readTitle and author never reach the output
UTF-8 text, &, curly quotespreserved exactlyÖffnen, naïve, &mdash; all intact

Bullet levels are the main casualty

This is the honest weak point. The converter writes one line per paragraph and ignores the level attribute, so a three-level checklist arrives completely flat:

# Checklist
Pre-flight
curl -I /health
expect 200
curl -I /pricing
expect 200
smoke on mobile viewport

Nothing in that text says that expect 200 belongs to curl -I /health. For a person skimming a summary it is tolerable. For a retrieval pipeline it needs a decision: a chunker that splits on headings will keep the parent and its children together, which is usually what you want, but any downstream step that needs the tree cannot get it from the Markdown.

When the hierarchy is load-bearing, keep python-pptx in the pipeline for that step — paragraph.level is right there — and use the Markdown as the human-readable view of the same deck:

from pptx import Presentation

prs = Presentation("deck.pptx")
for i, slide in enumerate(prs.slides, 1):
    title = slide.shapes.title
    print(f"\n## {title.text if title else f'Slide {i}'}")
    for shape in slide.shapes:
        if shape == title or not shape.has_text_frame:
            continue
        for para in shape.text_frame.paragraphs:
            print("  " * para.level + "- " + para.text)

Speaker notes survive, and that is rare

Notes are a separate part of the package, which is why most converters ignore them without saying so. Here they come out under a ### Notes: heading at the end of the slide they belong to. In a real deck that block is often the most useful text on the page: the caveat the presenter said out loud, the link nobody put on the slide, the number that did not fit.

The heading is a genuine ###, so a heading-based chunker keeps the notes attached to their own slide instead of pooling them at the end of the document.

Charts come out as tables

A chart built in PowerPoint is a data part, not a picture, and it is read:

# Latency by month

### Chart

| Category | p95, ms |
|---|---|
| Jul | 1800.0 |
| Aug | 1400.0 |
| Sep | 1100.0 |

Values keep their raw float form — 1800.0, not 1,800 — and axis titles, number formats and anything in the legend beyond the series name are lost. A chart pasted as a screenshot has no data part, so you get the image line and no numbers.

Images, links and formatting

A picture becomes a Markdown image whose alt text is the original filename and whose target is the internal part name, ![dot.png](Picture2.jpg). Nothing serves that path — the media file stays inside the package and is not extracted.

Hyperlinks are dropped: a run pointing at a documentation URL arrived as plain text with no URL anywhere in the output. Bold and italic collapse into plain text. Merged cells collapse into one cell. If any of that matters, the .pptx stays the source of truth and the Markdown is a readable copy.

Legacy .ppt and .pptm

Dispatch is by extension, and two PowerPoint extensions do not take the presentation path. A .pptm — a deck with macros, same zip container as .pptx — and a legacy binary .ppt both fall back to dumping raw bytes in a code block. One of them is a one-line fix: renaming .pptm to .pptx converts correctly, verified on a real file. For .ppt there is no shortcut; re-save it first:

soffice --convert-to pptx old.ppt

Convert a deck

  1. Open the converter and drop the .pptx in — up to 25 MB on the free plan.
  2. Check the preview: headings, slide comments, notes.
  3. Download the .md, or copy the text.
  4. Several decks at once: put them in one .zip and each file inside comes back as its own .md, or post to the batch endpoint described in the API reference.

Splitting a deck into one file per slide

A 40-slide deck measured 108 KB in and produced 2,440 words in a single file — pleasant to read, awkward as a corpus where you want one idea per chunk. The slide comments make the split mechanical:

import re, pathlib

text = pathlib.Path("deck.md").read_text()
parts = re.split(r"<!-- Slide number: (\d+) -->\n", text)
# ['', '1', 'slide 1', '2', 'slide 2', ...]
for num, body in zip(parts[1::2], parts[2::2]):
    body = body.strip()
    if body:
        pathlib.Path(f"slide-{int(num):02d}.md").write_text(body + "\n")

Each file then starts with its own # heading — exactly the boundary a retrieval chunker wants. For chapter-sized chunks instead, group the slides by their comment numbers and concatenate.

Limits, and what it costs

Free: 5 files a day from one IP address, up to 25 MB each, no sign-up. Pro is not on sale yet — the plan and its limits are defined, the buy button is not live. If you need decks above 25 MB or automation at volume, leave a request on the pricing page and you will hear when it opens.

For pipelines: POST /api/convert for one file, /api/convert/batch for up to 20 in a single request, /api/convert/zip for an archive. Fields and error codes are documented on the API page. Parsing happens on this server with a local library; the deck is not handed to a third-party conversion API.

Frequently asked questions

Does the conversion keep bullet levels?

No. One line per paragraph, with the bullet marker and indentation dropped. A three-level checklist arrives as seven flat lines. The python-pptx snippet above recovers the levels if you need them.

Are speaker notes included?

Yes, under a ### Notes: heading at the end of the slide they belong to, and only for slides that actually have notes.

Why did my chart turn into a table?

Because a native PowerPoint chart stores its data as a separate part, and that is more useful as a table than as a picture. Axis titles and number formats are not carried over. A chart pasted as an image has no data part and produces no numbers.

Can it convert .pptm or .ppt?

Rename .pptm to .pptx — same container, and it then converts normally. A legacy binary .ppt has to be re-saved as .pptx first; it currently comes back as a dump of raw bytes.

Is the deck uploaded to a third party?

No. Conversion runs on the server behind this site with a local parsing library, not through an external conversion API. On-prem deployment is on the roadmap, not shipped.

What are the limits?

Free: 5 files a day per IP, up to 25 MB each, no sign-up. Pro is not being sold yet; requests are collected on the pricing page.

Convert a file

Drag a document into the window and get Markdown you can read, diff and feed to a model. Free: 5 files a day, up to 25 MB.