How to Convert Markdown to PDF with JavaScript in NodeJS

Generate styled and tagged PDFs from Markdown with JavaScript, including tables and mapped image uploads.
Share this page

This JavaScript tutorial uses NodeJS to send Markdown, an image, and structured conversion options to the pdfRest Convert to PDF API Tool. The response is a tagged PDF with customized typography, page setup, and table presentation.

Why Convert Markdown to PDF with JavaScript?

Web applications frequently accept Markdown for project updates, user-authored documentation, and generated reports. Browser rendering works inside the product, but users still ask for a PDF they can download, circulate, or include with records outside the application.

A project-management service might assemble a quarterly status report in Markdown with milestone lists, a risk table, and an uploaded progress chart. Its NodeJS backend can map that chart to the image reference in the source and create a PDF export on demand. Stakeholders receive a coherent document rather than a screenshot or an unstyled print view.

This approach allows the same Markdown content model to serve both web and document channels. The conversion profile handles page boundaries, repeated table headers, colors, fonts, image alternate text, and tagging, leaving the JavaScript application responsible for the content rather than PDF layout calculations.

JavaScript Code Example for Converting Markdown to PDF

var axios = require("axios");
var fs = require("fs");
var FormData = require("form-data");

// By default, we use the US-based API service. This is the primary endpoint for global use.
var apiUrl = "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
 */
//var apiUrl = "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.
var inputPath = "/path/to/sample.md";
var imagePath = "/path/to/logo.png";
var form = new FormData();
form.append("file", fs.createReadStream(inputPath));
form.append("image_files", fs.createReadStream(imagePath));
form.append("structured_text_options", JSON.stringify({"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}}}}));
form.append("output", "pdf_from_markdown");

axios.post(apiUrl + "/pdf", form, { headers: { "Api-Key": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", ...form.getHeaders() }, maxBodyLength: Infinity })
.then((response) => { console.log(JSON.stringify(response.data, null, 2)); })
.catch((error) => { console.error(error.response ? error.response.data : error.message); process.exitCode = 1; });

Source: View the sample on GitHub

Breaking Down the Code

The sample uses axios to send the HTTP request, Node.js fs streams to read the local files, and form-data to construct the multipart request. When using this sample outside the pdfRest API samples repository, install the required packages with npm install axios form-data.

var apiUrl = "https://api.pdfrest.com";

The apiUrl 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.

var inputPath = "/path/to/sample.md";
var imagePath = "/path/to/logo.png";
var form = new FormData();

These values identify the Markdown source and local image file. The API determines that the source is Markdown from the uploaded file extension, so the options object does not need an input_format property.

form.append("file", fs.createReadStream(inputPath));
form.append("image_files", fs.createReadStream(imagePath));

The file form field uploads the Markdown source, while image_files uploads the local image. Both files are read as streams, which is appropriate for larger files and avoids loading them entirely into memory.

form.append("structured_text_options", JSON.stringify({"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 structured_text_options JSON configures the generated PDF. It sets the title and language, enables tagged output, defines page setup, and applies typography and table styling. The table options control relative column widths, repeated headers, borders, colors, padding, and alternating row fills for Markdown tables.

The markdown.image_sources object maps the Markdown image target sample-logo to the first uploaded image, represented by "upload_index": 0. The Markdown source should use the same target, such as ![Company logo](sample-logo). The image_alt_text setting supplies alternate text, while missing_image_alt_text is set to fail so that conversion stops if a meaningful image lacks alternate text.

form.append("output", "pdf_from_markdown");

The output value sets the output filename for the generated PDF.

axios.post(apiUrl + "/pdf", form, { headers: { "Api-Key": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", ...form.getHeaders() }, maxBodyLength: Infinity })
.then((response) => { console.log(JSON.stringify(response.data, null, 2)); })
.catch((error) => { console.error(error.response ? error.response.data : error.message); process.exitCode = 1; });

The axios.post call sends the multipart form to /pdf. The request includes the pdfRest API key and the form headers generated by form-data. A successful response is printed as formatted JSON, while an API or network error is written to standard error.

Beyond the Tutorial

This example shows how a Node.js 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.

NodeJS workflows only need to send the Markdown options that apply to their content and image strategy. Try alternate profiles in API Lab, then use the Convert to PDF documentation as the complete parameter reference.

Generate a self-service API Key now!
Create your FREE API Key to start processing PDFs in seconds, only possible with pdfRest.