Convert PDF to Markdown for AI and RAG Pipelines in .NET
October 24, 2026 · 5 min read · GroupDocs Team
Language models and vector search work on text, but a PDF stores its text as positioned glyphs, page by page. Before you can embed a handbook or ask a model about a contract, you need the words back in reading order, with the structure that gives them meaning. Markdown is a good target: paragraphs, lists, and tables survive, and it is far more compact than HTML.
This tutorial uses PDF to Markdown (PdfToMdConverter) from GroupDocs.Conversion.LowCode to turn a folder of PDFs into Markdown, and then splits the Markdown into chunks ready for an embedding model.
What You Need
- .NET 6.0 or later on Windows, macOS, or Linux
- The package:
dotnet add package GroupDocs.Conversion.LowCode- A
pdfsfolder with the documents to convert
Convert a Folder of PDFs
using System.IO;using GroupDocs.Conversion.LowCode;// Set your licenseLicense.Set("GroupDocs.Conversion.LowCode.lic");Directory.CreateDirectory("markdown");foreach (var pdf in Directory.GetFiles("pdfs", "*.pdf")){ var markdown = Path.Combine("markdown", Path.GetFileNameWithoutExtension(pdf) + ".md"); // Convert the PDF and leave its images out new PdfToMdConverter(pdf).Convert(markdown, options => options.MarkdownOptions.ExportImagesAsBase64 = false);}open System.IOopen GroupDocs.Conversion.LowCode// Set your licenseLicense.Set("GroupDocs.Conversion.LowCode.lic")Directory.CreateDirectory("markdown") |> ignorefor pdf in Directory.GetFiles("pdfs", "*.pdf") do let markdown = Path.Combine("markdown", Path.GetFileNameWithoutExtension(pdf) + ".md") // Convert the PDF and leave its images out (new PdfToMdConverter(pdf)).Convert(markdown, fun options -> options.MarkdownOptions.ExportImagesAsBase64 <- false)Imports System.IOImports GroupDocs.Conversion.LowCode' Set your licenseLicense.Set("GroupDocs.Conversion.LowCode.lic")Directory.CreateDirectory("markdown")For Each pdf In Directory.GetFiles("pdfs", "*.pdf") Dim markdown = Path.Combine("markdown", Path.GetFileNameWithoutExtension(pdf) & ".md") ' Convert the PDF and leave its images out Dim converter As New PdfToMdConverter(pdf) converter.Convert(markdown, Sub(options) options.MarkdownOptions.ExportImagesAsBase64 = False)NextEach PDF becomes a .md file of the same name in the markdown folder.
Why Leave the Images Out
By default, PdfToMdConverter embeds every image in the Markdown as a Base64 data URI. That keeps the document self-contained, but an embedding model can’t read Base64, and it fills a prompt with tokens that carry no meaning. In our test with three pages of a business plan, the Markdown was 1.8 KB without its two images and 78 KB with them.
With ExportImagesAsBase64 = false, the images are left out of the Markdown entirely.
Keep the Images as Files
If your pipeline describes or indexes images separately, write them to files with an image saving callback. The converter calls it once per image: give the image a file name and a stream to write to, and the Markdown links to that name.
using System.IO;using GroupDocs.Conversion.LowCode;using GroupDocs.Conversion.Options.Convert;Directory.CreateDirectory("markdown");new PdfToMdConverter("handbook.pdf").Convert("markdown/handbook.md", options => options.MarkdownOptions.ImageSavingCallback = new SaveImagesNextToMarkdown("markdown", "handbook"));// Writes the images next to the Markdown file: handbook-1.jpeg, handbook-2.png, and so onclass SaveImagesNextToMarkdown : IMarkdownImageSavingCallback{ private readonly string folder; private readonly string prefix; private int count; public SaveImagesNextToMarkdown(string folder, string prefix) { this.folder = folder; this.prefix = prefix; } public void ImageSaving(MarkdownImageSavingArgs args) { // A file name without a folder: the Markdown links to it as it is var name = $"{prefix}-{++count}{Path.GetExtension(args.ImageFileName)}"; args.ImageFileName = name; args.ImageStream = File.Create(Path.Combine(folder, name)); }}The Markdown then contains links such as . ImageFileName must be a plain file name: a name with a folder in it makes the conversion fail.
What the Markdown Looks Like
PDF has no notion of a heading or a paragraph, so the converter rebuilds the structure from the layout. Knowing what comes out helps you split it well:
- Paragraphs are separated by blank lines, and bulleted lists become Markdown lists.
- Tables become Markdown tables. A table of contents is a table too, and its entries link to page numbers, such as
[Introduction](3). - Headings come out as bold lines (
**INTRODUCTION**), not as#headings, because the PDF only says that the text is large and bold. - Running headers and footers repeat as text on every page.
So split on blank lines rather than on # headings. If repeated headers add noise to your search results, drop the short lines that occur on many pages before you split.
Split the Markdown into Chunks
Embedding models take a limited amount of text, and search works best when each chunk holds one idea. The following code splits every Markdown file at blank lines into chunks of up to 1,500 characters, never in the middle of a paragraph, and writes them to a JSON Lines file:
using System.Collections.Generic;using System.IO;using System.Text;using System.Text.Json;using System.Text.RegularExpressions;using var output = new StreamWriter(Path.Combine("markdown", "chunks.jsonl"));foreach (var file in Directory.GetFiles("markdown", "*.md")){ var number = 0; foreach (var text in Chunk(File.ReadAllText(file), 1500)) { var record = new { source = Path.GetFileName(file), chunk = number++, text }; output.WriteLine(JsonSerializer.Serialize(record)); }}// Groups paragraphs into chunks of up to maxLength characters. A longer paragraph becomes a chunk of its own.static IEnumerable<string> Chunk(string markdown, int maxLength){ var chunk = new StringBuilder(); foreach (var block in Regex.Split(markdown, @"\n\s*\n")) { var paragraph = block.Trim(); if (paragraph.Length == 0) continue; if (chunk.Length > 0 && chunk.Length + paragraph.Length > maxLength) { yield return chunk.ToString().Trim(); chunk.Clear(); } chunk.Append(paragraph).Append("\n\n"); } if (chunk.Length > 0) yield return chunk.ToString().Trim();}Each line of chunks.jsonl is one chunk with its source file and position, for example {"source":"handbook.md","chunk":0,"text":"..."}. Pass the text values to your embedding model and store the vectors with source and chunk, so every answer can point back to its document.
Tips
- Scanned PDFs hold pictures of pages, not text. Run them through OCR before you convert them.
- Password-protected PDFs open with the
Passwordload option:new PdfToMdConverter("contract.pdf", options => options.Password = "secret"). - Form fields and annotations can be flattened or hidden with the PDF load options; see Using the PDF to Markdown Converter.
- Streams: the converter also reads a PDF from a
Streamand writes the Markdown to one, so you can convert an upload in memory.
Licensing
PDF to Markdown costs a one-time $99 per developer: perpetual, royalty-free, with no subscriptions. Without a license, the converter processes only the first three pages of each PDF, adds an evaluation message to the Markdown, and converts up to 10 documents per process, which matters for a batch. To test on your whole folder, request a free temporary license.
Learn More
- PDF to Markdown on our products site
- How to Convert PDF to Markdown in .NET in the Knowledge Base
- Introducing GroupDocs.Conversion.LowCode: all ten converters
Questions? Ask on the Community Forum.