The problem: a PDF has no paragraphs, and Markdown is only paragraphs
A PDF is a page-description format. It tells a renderer where to draw each glyph on the page, at what size, in what font, at what x and y coordinate. The format has no concept of a paragraph, a heading, a list, or a table. Those structures are visual — they emerge from the positioning, the spacing, and the font choices the author made when laying out the page. The PDF stores the result; it does not store the intent.
Markdown is the opposite. A Markdown document is structural — a paragraph is a block of text separated by blank lines, a heading is a line starting with number signs, a list is lines starting with dashes. The structure is the format. There is no positioning information, because the positioning is the renderer's job.
Converting a PDF to Markdown is therefore not a translation between two formats that store the same things. It is a reconstruction. The converter has to read the PDF's positioned glyphs, group them into lines by their y-coordinate, group the lines into paragraphs by the spacing between them, guess which lines are headings from their font size or all-caps style, guess which lines are list items from their leading bullet character, and emit structural Markdown that approximates what the author probably meant. Each of those steps is a heuristic, and heuristics are wrong on anything that does not match the assumption the heuristic was built on.
The PDF to Markdown Converter does this reconstruction in your browser, using Mozilla's pdf.js library to extract text from the PDF and a set of rules to mark up the result. It works well on simple, single-column, text-heavy PDFs with explicit headings. It produces garbage on multi-column academic papers, tables, scanned documents, and anything with complex layout. Knowing which is which is the difference between getting a usable result and getting a mess.
Fastest path
Open the PDF to Markdown Converter. Drag a PDF onto the dropzone, or click to pick a file. The tool extracts the text, marks up headings and lists, and shows the Markdown in an editor with a word count and a page count. Edit the result in the textarea if anything is wrong. Copy to clipboard, or download as a .md file. The whole thing runs in your browser — the PDF is not uploaded anywhere.
How the extraction actually works
The tool uses pdf.js, Mozilla's JavaScript PDF renderer, loaded from a CDN. pdf.js exposes a getTextContent() method on each page that returns an array of text-run objects. Each object has the run's text string and a transform matrix — six numbers that describe where the run sits on the page, what scale it is at, and what rotation it has. The y-coordinate (the sixth number in the matrix) tells you the vertical position of the baseline.
The converter uses those positions to group runs into lines. All runs with the same rounded y-coordinate belong to the same line. The lines are sorted top to bottom (descending y, in PDF's coordinate system), and the runs within a line are joined with spaces. The output is a text stream that approximates the reading order of the page.
That is the extraction. Everything after that is markup — the converter looks at each line and decides whether it is a heading, a list item, a code block, or a paragraph.
The markup heuristics, and where each breaks
The converter applies four rules to the extracted text. Each rule has a failure mode that you will hit on real documents.
All-caps lines under 60 characters become level-2 headings. The assumption is that authors use all-caps for section titles. The failure mode is that authors also use all-caps for emphasis, for acronyms in the middle of a paragraph, and for bylines. A line like "JOHN SMITH, EDITOR" surrounded by blank lines becomes a heading. So does "NOTE:" and "IMPORTANT:". On a legal document, the running header on every page ("CONTRACT — PAGE 4") becomes a heading. The result is a Markdown file peppered with false headings.
Short standalone lines under 60 characters become level-3 headings. The assumption is that short lines surrounded by blank lines are subheadings. The failure mode is that this rule catches every short paragraph, every one-line dialogue line in a play, every figure caption, and every block quote that happens to be short. A single-sentence paragraph in an essay becomes a heading. The Markdown structure ends up with far more headings than the source document had.
Lines starting with a bullet character become list items. The converter normalizes -, *, and the Unicode bullet • to a dash. This is the most reliable of the four rules — bullet characters are a strong signal. The failure mode is that numbered lists pass through unchanged (the converter does not normalize them), and lines that start with a dash in the source (an em-dash for a dialogue line, a minus sign in a math expression) get treated as list items.
Lines indented by four spaces or a tab become code blocks. This matches Markdown's own code-block rule, which means source PDFs that already contain indented text (a code listing, a block quote) get marked as code blocks whether or not they are code. The result is that a block quote from a novel becomes a code block, which renders in monospace on a Markdown viewer.
The honest summary: the markup is a guess. The guess is right on simple documents with explicit, all-caps, short headings and dash-bulleted lists. It is wrong on anything else, and you will spend time editing the output.
What is preserved, and what is lost
Knowing what does not survive the conversion is more useful than knowing what does.
Preserved: the raw text (for PDFs with a text layer), the approximate reading order (top to bottom), the page breaks (as horizontal rules), bullet lists, numbered lists (unchanged), and the rough heading structure (from the heuristics above).
Lost: bold, italic, and underline — pdf.js's getTextContent() does not expose font weight or style, so the converter has nothing to read. Links are not extracted. Images are not extracted (the tool is text-only). Tables are flattened into a list of cell contents with no row or column structure. Multi-column layouts are garbled — text from both columns at the same y-coordinate is merged into one line. Footnotes are mixed into the body text wherever their y-position places them, with no [^1] footnote syntax. Font sizes are available in the pdf.js data but are ignored — headings are guessed by the all-caps and short-line heuristics, not by font size.
Why scanned PDFs produce empty output
A scanned PDF is a series of page images. There is no text layer — the glyphs are pixels in a JPEG or a similar image, not positioned text runs. When the converter calls getTextContent() on a scanned page, it gets an empty array. The output is empty.
This is not a bug in the converter. It is a fundamental limit of text extraction. To get text out of a scanned PDF, you need optical character recognition — a system that looks at the image and guesses which characters the pixels represent. OCR is a different problem from text extraction, and it is a much harder one. The tool does not do OCR, and the UI says so explicitly.
For scanned PDFs, you need a different tool. Tesseract is the open-source OCR engine; there are wrappers that take a PDF and run Tesseract on each page. Adobe's online converter does OCR on scanned PDFs as part of its conversion. Google Drive's "open with Google Docs" path runs OCR as well. None of these are as accurate as text extraction from a real text-layer PDF, but they are the only option for scanned input.
Why multi-column layouts produce garbled text
A two-column academic paper is the worst case for a y-coordinate-based extractor. The converter groups text runs by their y-position. Text from the left column and text from the right column at the same y-position are merged into a single line. The result is a sentence from the left column followed by a sentence from the right column, on the same line, with no separation.
The fix would be column detection — clustering the x-positions of the text runs into groups, and extracting each column separately. The tool does not do this. Neither does basic pdf.js text extraction. For multi-column PDFs, you need a layout-aware extractor.
Pandoc, with the right options, handles some academic PDFs better than a browser-based extractor. Marker and docling are layout-aware PDF-to-markdown tools that detect columns, tables, and figures. They are heavier — you install them on a server or run them in a notebook — but they handle the cases a browser-based extractor cannot.
When to use this tool, and when to use something else
The browser-based converter is the right tool for a specific class of PDF: single-column, text-heavy, with explicit headings and dash-bulleted lists, and a real text layer. A government form, a resume, a single-column report, a blog post exported as a PDF — these convert cleanly.
It is the wrong tool for: multi-column academic papers (columns merge), tables (structure is lost), scanned PDFs (no text to extract), PDFs with heavy formatting like bold and italic links (formatting is lost), and PDFs with complex layout like sidebars and text boxes (reading order is wrong). For those, use pandoc, Marker, docling, or Adobe's converter, depending on the document and your tolerance for setup.
The advantage of the browser-based tool is that it runs locally, takes no setup, and produces editable output in seconds. The disadvantage is that it gives you a draft you have to clean up, not a finished Markdown file. The cleanup is the work. For a short, simple PDF, the cleanup is small. For a long, complex one, the cleanup is larger than starting from scratch with a better tool.
Gotchas
- Scanned PDFs produce empty output. The tool does not do OCR. A scanned PDF is an image with no text layer, and
getTextContent()returns nothing. The UI warns about this; the FAQ confirms it. Use Tesseract, Adobe's converter, or Google Drive for scanned input. - Multi-column layouts are garbled. The converter does not detect columns. Text from both columns at the same y-position is merged into one line. For academic papers, use pandoc, Marker, or docling instead.
- Tables are flattened. There is no table detection. The row and column structure is lost, and the cell contents appear as a list of lines. You have to rebuild the table in Markdown by hand.
- Bold and italic are lost. pdf.js's text extraction does not expose font weight or style. The converter has nothing to read. If the formatting matters, use a layout-aware extractor that reads font information.
- Short paragraphs become headings. The 60-character heading heuristic promotes any standalone line under 60 characters to a level-3 heading. Single-sentence paragraphs, dialogue, and figure captions all become headings. Be ready to demote them in the output.
- All-caps lines become headings. Any all-caps text under 60 characters becomes a level-2 heading, including emphasis, bylines, and running headers. On a legal document, the running header on every page becomes a heading. Edit the output.
- Numbered lists are not normalized. Bullet lists are normalized to dashes, but numbered lists pass through unchanged. If the source PDF uses a different numbering style (parentheses, brackets), the Markdown will not match standard syntax.
- The whole PDF loads into memory. There is no file size limit in the code. The entire PDF is read into an ArrayBuffer, and all page text is accumulated before rendering. A 200 MB PDF can crash the tab. The FAQ's "50 to 100 MB" claim is a guess, not a tested limit.
- No x-coordinate sorting within lines. The runs within a line are emitted in the order pdf.js returns them, not sorted by x-position. pdf.js usually returns them in reading order, but on complex layouts the order can break. If a line comes out jumbled, this is why.
Summary
- Converting a PDF to Markdown is a reconstruction, not a format conversion. A PDF stores positioned text runs; Markdown stores structural elements. The converter groups runs into lines by y-coordinate, sorts lines top to bottom, and applies heuristics to mark up headings, lists, and code blocks. Each heuristic has a failure mode that you will hit on real documents.
- The markup heuristics are: all-caps lines under 60 characters become level-2 headings, short standalone lines under 60 characters become level-3 headings, bullet lines become list items, and indented lines become code blocks. The 60-character threshold misclassifies short paragraphs, dialogue, and captions as headings. Edit the output.
- Text-layer PDFs convert cleanly. Scanned PDFs produce empty output — the tool does not do OCR. Multi-column layouts are garbled because the converter does not detect columns. Tables are flattened to a list of cell contents. Bold, italic, links, and images are lost, because pdf.js's text extraction does not expose them.
- The PDF to Markdown Converter runs entirely in your browser using pdf.js. The PDF is not uploaded anywhere. It is the right tool for single-column, text-heavy PDFs with explicit headings and a real text layer. It is the wrong tool for multi-column academic papers, tables, scanned documents, and heavily formatted PDFs. For those, use pandoc, Marker, docling, or Adobe's converter. The PDF to EPUB Converter handles the related e-reader case, the PDF Merger combines multiple PDFs before conversion, and the PDF to PowerPoint Converter handles the slides case.