How to Convert Markdown to PDF with Python
This Python example converts Markdown to PDF through the pdfRest Convert to PDF API Tool. It uploads the source and an image, applies structured page and table settings, and requests tagged output for a complete automated report workflow.
Why Convert Markdown to PDF with Python?
Python programs regularly produce Markdown summaries from analytics, testing, and data-processing jobs. Markdown is convenient for assembling headings, findings, code, and tables, but decision-makers often want a PDF that can be reviewed, annotated, and distributed as a finished report.
A model-evaluation pipeline, for instance, can write its metrics and observations to Markdown, include a comparison table, and save a generated chart. A Python step can submit the report and chart to pdfRest, map the image reference, and return a styled PDF for the project record. Analysts keep an easy-to-generate source format while stakeholders receive a familiar document.
The request also demonstrates how automation can enforce consistent margins, fonts, colors, table behavior, language metadata, alternate text, and tagging across every run. That consistency is difficult to achieve with ad hoc Markdown print commands and requires no separate HTML conversion stage.
Python Code Example for Converting Markdown to PDF
import json
import os
import requests
# By default, we use the US-based API service. This is the primary endpoint for global use.
api_url = "https://api.pdfrest.com"
# For GDPR compliance and enhanced performance for European users, you can switch to the EU-based service by uncommenting the URL below.
# For more information visit https://pdfrest.com/pricing#how-do-eu-gdpr-api-calls-work
#api_url = "https://eu-api.pdfrest.com"
# This sample converts Markdown input to a tagged PDF through multipart /pdf.
# It demonstrates structured_text_options, table styling, tagging, and uploaded Markdown image mapping.
input_path = "/path/to/sample.md"
image_path = "/path/to/logo.png"
options = json.loads(r'''{
"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
}
}
}
}''')
from requests_toolbelt import MultipartEncoder
with open(input_path, "rb") as input_file, open(image_path, "rb") as image_file:
fields = {
"file": (os.path.basename(input_path), input_file, "text/plain"),
"structured_text_options": json.dumps(options),
"image_files": (os.path.basename(image_path), image_file, "image/png"),
}
form = MultipartEncoder(fields=fields)
response = requests.post(api_url + "/pdf", data=form, headers={
"Accept": "application/json",
"Content-Type": form.content_type,
"Api-Key": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
})
print("Response status code: " + str(response.status_code))
if response.ok:
print(json.dumps(response.json(), indent=2))
else:
print(response.text)
raise SystemExit(1)
Source: View the sample on GitHub
Breaking Down the Code
The sample uses Python's built-in json and os modules, the requests HTTP library, and MultipartEncoder from requests-toolbelt. When using this example outside the pdfRest API samples repository, install its dependencies with python -m pip install requests requests-toolbelt.
api_url = "https://api.pdfrest.com"
The api_url value selects the US-based pdfRest service by default. An EU endpoint is included as a commented alternative when that service region is appropriate for your workflow.
input_path = "/path/to/sample.md"
image_path = "/path/to/logo.png"
options = json.loads(r'''{
"title": "Structured Content Sample",
"language": "en-US",
...
}''')
These values identify the Markdown source, local image file, and structured-text conversion options. The API determines that the source is Markdown from the uploaded file extension, so the options object does not need an input_format property.
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.
fields = {
"file": (os.path.basename(input_path), input_file, "text/plain"),
"structured_text_options": json.dumps(options),
"image_files": (os.path.basename(image_path), image_file, "image/png"),
}
The multipart fields include the Markdown source as file, the JSON configuration as structured_text_options, and the local image as image_files. The uploaded image is available to the Markdown conversion through its zero-based upload index.
form = MultipartEncoder(fields=fields)
response = requests.post(api_url + "/pdf", data=form, headers={
"Accept": "application/json",
"Content-Type": form.content_type,
"Api-Key": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
})
MultipartEncoder prepares the multipart request and supplies the required Content-Type header, including its boundary. The requests.post call sends the request to /pdf. Replace the placeholder Api-Key value with your own pdfRest API key.
if response.ok:
print(json.dumps(response.json(), indent=2))
else:
print(response.text)
raise SystemExit(1)
The response status code is printed first. On success, the script formats and prints the JSON response, which includes the generated PDF resource details and download URL. On failure, it prints the API error and exits with a nonzero status.
Beyond the Tutorial
This example shows how a Python 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.
Python automation can simplify this profile whenever its Markdown does not contain mapped images, styled tables, or other optional features. Compare request variations with API Lab, and find all supported settings in the Convert to PDF documentation.