How to Convert Markdown to PDF in .NET with C#
This C# walkthrough converts a Markdown file into a tagged PDF using the pdfRest Convert to PDF API Tool. It combines the source, an uploaded image, and layout settings in a multipart request suitable for a .NET document workflow.
Why Convert Markdown to PDF with C#?
.NET teams commonly author runbooks, change notes, and internal standards in Markdown because it fits naturally with Git-based review. Formal approval processes, external distribution, and records management often require a PDF version with predictable pages and presentation.
For example, a release-management portal can collect approved Markdown change notes, a compatibility matrix, and a product diagram, then generate a PDF for the change-approval package. Reviewers get a stable artifact with styled tables and visible document structure, while developers avoid manually copying the content into Word or rebuilding it in a second template.
The API makes the export repeatable from the same source of truth. C# code can specify margins, fonts, colors, tagging, and image alternate text without automating desktop software or calculating where each Markdown element belongs on the page.
C# Code Example for Converting Markdown to PDF
/* * What this sample does: * - Converts structured Markdown input to PDF through the /pdf endpoint. * * Setup (environment): * - Set PDFREST_API_KEY=your_api_key_here * - Optional: set PDFREST_URL to override the API region. * * Usage: * dotnet run -- pdf-from-markdown-multipart*/ using Newtonsoft.Json.Linq; using System.Net.Http.Headers; using System.Text; namespace Samples.EndpointExamples.MultipartPayload; public static class PdfFromMarkdown { public static async Task Execute(string[] args) { var inputPath = args.Length > 0 ? args[0] : "/path/to/sample.md"; var imagePath = args.Length > 1 ? args[1] : "/path/to/logo.png"; var apiKey = Environment.GetEnvironmentVariable("PDFREST_API_KEY"); if (string.IsNullOrWhiteSpace(apiKey)) throw new InvalidOperationException("Missing PDFREST_API_KEY"); var baseUrl = Environment.GetEnvironmentVariable("PDFREST_URL") ?? "https://api.pdfrest.com"; using var client = new HttpClient { BaseAddress = new Uri(baseUrl) }; client.DefaultRequestHeaders.TryAddWithoutValidation("Api-Key", apiKey); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var options = JObject.Parse(@"{""title"":""Structured Content Sample"",""language"":""en-US"",""enable_tagging"":true,""page_setup"":{""size"":""Letter"",""orientation"":""portrait"",""margin"":{""top"":36,""right"":42,""bottom"":36,""left"":42}},""style"":{""font"":""Arial"",""heading_font"":""Arial"",""code_font"":""Courier"",""text_size"":11,""text_color_rgb"":[34,34,34],""heading_scale"":1.35,""table"":{""column_width_weights"":[2,3,2],""keep_header_with_first_row"":true,""repeat_headers_on_overflow"":true,""show_borders"":true,""border_width"":0.75,""border_color_rgb"":[180,188,200],""header_fill_color_rgb"":[33,64,98],""header_text_color_rgb"":[255,255,255],""row_fill_color_rgb"":[250,250,252],""alternate_row_fill_color_rgb"":[235,240,246],""cell_padding"":{""top"":6,""right"":8,""bottom"":6,""left"":8}}},""markdown"":{""image_alt_text"":{""sample-logo"":""Sample logo""},""missing_image_alt_text"":""fail"",""image_sources"":{""sample-logo"":{""upload_index"":0}}}}"); using var form = new MultipartFormDataContent(); var inputContent = new ByteArrayContent(await File.ReadAllBytesAsync(inputPath)); inputContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); form.Add(inputContent, "file", Path.GetFileName(inputPath)); var imageContent = new ByteArrayContent(await File.ReadAllBytesAsync(imagePath)); imageContent.Headers.ContentType = new MediaTypeHeaderValue("image/png"); form.Add(imageContent, "image_files", Path.GetFileName(imagePath)); form.Add(new StringContent(options.ToString()), "structured_text_options"); var response = await client.PostAsync("pdf", form); var result = await response.Content.ReadAsStringAsync(); Console.WriteLine(result); if (!response.IsSuccessStatusCode) Environment.ExitCode = 1; } }
Source: View the sample on GitHub
Breaking Down the Code
The Execute method accepts command-line arguments for the Markdown source and image file. These values can be supplied when running the sample, or you can replace the placeholder paths directly.
var inputPath = args.Length > 0 ? args[0] : "/path/to/sample.md"; var imagePath = args.Length > 1 ? args[1] : "/path/to/logo.png";
The first path identifies the Markdown source, while the second identifies the local image uploaded with the request. The API determines that the source is Markdown from the uploaded file extension, so the options JSON does not need an input_format property.
var apiKey = Environment.GetEnvironmentVariable("PDFREST_API_KEY");
if (string.IsNullOrWhiteSpace(apiKey)) throw new InvalidOperationException("Missing PDFREST_API_KEY");
The sample reads the pdfRest API key from the PDFREST_API_KEY environment variable and stops with a clear error if the key is missing.
var baseUrl = Environment.GetEnvironmentVariable("PDFREST_URL") ?? "https://api.pdfrest.com";
using var client = new HttpClient { BaseAddress = new Uri(baseUrl) };
The API base URL defaults to the US-based pdfRest service. Set PDFREST_URL when you need to use a different supported service region.
client.DefaultRequestHeaders.TryAddWithoutValidation("Api-Key", apiKey);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
These headers authenticate the request and indicate that the application expects a JSON response containing the generated PDF resource details and download URL.
var options = JObject.Parse(@"{""title"":""Structured Content Sample"",""language"":""en-US"",""enable_tagging"":true,""page_setup"":{""size"":""Letter"",""orientation"":""portrait"",""margin"":{""top"":36,""right"":42,""bottom"":36,""left"":42}},""style"":{""font"":""Arial"",""heading_font"":""Arial"",""code_font"":""Courier"",""text_size"":11,""text_color_rgb"":[34,34,34],""heading_scale"":1.35,""table"":{""column_width_weights"":[2,3,2],""keep_header_with_first_row"":true,""repeat_headers_on_overflow"":true,""show_borders"":true,""border_width"":0.75,""border_color_rgb"":[180,188,200],""header_fill_color_rgb"":[33,64,98],""header_text_color_rgb"":[255,255,255],""row_fill_color_rgb"":[250,250,252],""alternate_row_fill_color_rgb"":[235,240,246],""cell_padding"":{""top"":6,""right"":8,""bottom"":6,""left"":8}}},""markdown"":{""image_alt_text"":{""sample-logo"":""Sample logo""},""missing_image_alt_text"":""fail"",""image_sources"":{""sample-logo"":{""upload_index"":0}}}}");
The options object becomes the structured_text_options form field. It sets the PDF title and language, enables tagged output, configures the page, and applies typography and table styling. The table settings control relative column widths, repeated headers, borders, colors, padding, and alternating row fills for Markdown tables.
The markdown.image_sources configuration maps the Markdown image target sample-logo to the first uploaded image, represented by "upload_index": 0. The Markdown source should contain the same target, such as . The image_alt_text value supplies alternate text, while missing_image_alt_text is set to fail so that conversion stops if a meaningful image lacks alternate text.
using var form = new MultipartFormDataContent();
var inputContent = new ByteArrayContent(await File.ReadAllBytesAsync(inputPath));
inputContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
form.Add(inputContent, "file", Path.GetFileName(inputPath));
var imageContent = new ByteArrayContent(await File.ReadAllBytesAsync(imagePath));
imageContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
form.Add(imageContent, "image_files", Path.GetFileName(imagePath));
form.Add(new StringContent(options.ToString()), "structured_text_options");
The MultipartFormDataContent object holds the Markdown source, uploaded image, and JSON conversion options. The uploaded image is available to the Markdown conversion through its zero-based upload index.
var response = await client.PostAsync("pdf", form);
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
if (!response.IsSuccessStatusCode) Environment.ExitCode = 1;
The PostAsync call sends the multipart request to /pdf. The response is printed to the console, and the sample returns a nonzero exit status when the API reports an error.
Beyond the Tutorial
This example shows how a C# application can create a polished, tagged PDF from Markdown while controlling document presentation and resolving a local image in the same request. Tagged output adds logical document structure that can support accessibility and downstream processing, but it does not by itself guarantee conformance with a specific accessibility standard.
.NET applications may remove image, table, or typography fields that are unnecessary for a particular Markdown conversion. Validate representative requests in API Lab, and review the Convert to PDF reference before finalizing the integration.