Iteration Layer

Convert Markdown to Styled PDF in n8n

6 min read Document Generation

The n8n Forum’s Most Repeated Question

Search the n8n community forum for “Markdown to PDF” and you will find the same question asked repeatedly, with no satisfying answer. The replies fall into three categories.

“Use a Function node with Puppeteer.” This requires a Chromium binary in your n8n environment, headless browser rendering, and a pile of JavaScript to set up page sizes, margins, and print styles. Self-hosted n8n can make this work with enough effort. n8n Cloud cannot — there is no browser binary available.

“Use the HTML-to-PDF community node.” These nodes convert raw HTML, not Markdown. You need to convert Markdown to HTML first (another Function node), then style it with inline CSS (another Function node), then pass it to the PDF node. Three nodes of glue code, and the output still looks like a web page printed to PDF — no proper headers, footers, or page numbers.

“Export from Google Docs.” Create a Google Doc, write content via the API, export as PDF. This technically works but is not automatable for dynamic content. The Google Docs API for formatting is verbose and fragile, and you are creating throwaway documents just to get a PDF.

None of these produce a professional document with consistent typography, a header on every page, a footer with page numbers, and controlled margins.

Markdown In, Styled PDF Out

Iteration Layer Document Generation accepts Markdown content in paragraph blocks — via the markdown field — and renders it into a fully styled PDF. You control the fonts, page size, margins, header, footer, page numbers, heading styles, link colors, and table styling through a JSON document definition.

The Markdown content goes into "type": "paragraph" blocks with a "markdown" field instead of a "text" field. The rendering engine parses the Markdown and applies your style definitions — headings get your heading font and size, links get your link color, code blocks get your code font, tables get your table styling. The output is a PDF that looks like it came from a design tool, not a browser print dialog.

One API call. Fixed credit cost per document. No browser binary, no HTML intermediate step, no throwaway Google Docs.

The Workflow: Webhook to Styled PDF

Here is what we are building in n8n: a webhook that receives Markdown content, converts it into a styled PDF with headers, footers, and page numbers, and stores the result. Three nodes. No Function nodes. No Puppeteer.

Step 1: Webhook Trigger

Open the n8n canvas and add a new node. Search for “Webhook” and add it.

In the node settings, set the HTTP Method to POST. Set Response Mode to “Last Node” so the workflow returns the generated PDF to the caller. The webhook will accept a JSON body with the Markdown content.

A typical request body:

{
  "title": "Project Status Report",
  "author": "Engineering Team",
  "markdown": "## Executive Summary\n\nThe migration to the new platform is **on track** for the Q2 deadline...\n\n## Milestones\n\n| Milestone | Status | Date |\n|-----------|--------|------|\n| Database migration | Complete | March 15 |\n| API v2 rollout | In progress | April 1 |\n| Legacy deprecation | Planned | May 15 |"
}

The Markdown content comes from wherever your process starts — a CMS, a form submission, a GitHub webhook, a previous workflow step, or a manual API call.

Step 2: Iteration Layer (Document Generation)

Add an Iteration Layer node after the webhook trigger. In the Resource dropdown, select Document Generation. Set the Format to pdf.

In the Document JSON field, define the full document structure. Here is a concrete example that converts the incoming Markdown into a styled PDF with a header, footer, and page numbers:

{
  "metadata": {
    "title": "{{ $json.body.title }}",
    "author": "{{ $json.body.author }}"
  },
  "page": {
    "size": "A4",
    "margin": {
      "top": 80,
      "bottom": 60,
      "left": 50,
      "right": 50
    }
  },
  "styles": {
    "font": {
      "family": "Inter",
      "size": 11,
      "color": "#1a1a2e"
    },
    "headings": {
      "h1": { "size": 24, "color": "#1a1a2e", "margin_bottom": 12 },
      "h2": { "size": 18, "color": "#2d2d44", "margin_bottom": 8 },
      "h3": { "size": 14, "color": "#4a4a6a", "margin_bottom": 6 }
    },
    "links": {
      "color": "#0066cc",
      "underline": true
    },
    "code": {
      "font_family": "JetBrains Mono",
      "background_color": "#f5f5f5",
      "border_radius": 4
    }
  },
  "header": {
    "content": [
      {
        "type": "paragraph",
        "text": "{{ $json.body.title }}",
        "style": {
          "font_size": 9,
          "color": "#888888"
        }
      }
    ],
    "border_bottom": {
      "color": "#e0e0e0",
      "width": 1
    }
  },
  "footer": {
    "content": [
      {
        "type": "paragraph",
        "text": "{{ $json.body.author }} — Page {{page_number}} of {{total_pages}}",
        "style": {
          "font_size": 9,
          "color": "#888888",
          "align": "center"
        }
      }
    ],
    "border_top": {
      "color": "#e0e0e0",
      "width": 1
    }
  },
  "content": [
    {
      "type": "headline",
      "text": "{{ $json.body.title }}",
      "level": 1
    },
    {
      "type": "paragraph",
      "markdown": "{{ $json.body.markdown }}"
    }
  ]
}

The key pieces:

  • Page setup: A4 with generous margins. The top margin is larger to accommodate the header. Adjust size to "Letter" for US standard.
  • Styles: Global font, heading hierarchy with decreasing sizes, link styling, and code block formatting. These styles apply to all Markdown content in the document.
  • Header: Appears on every page. The document title in small gray text with a subtle bottom border. The header sits inside the top margin area.
  • Footer: Appears on every page. Author name, page number, and total page count — centered with a top border. {{page_number}} and {{total_pages}} are built-in template variables that the rendering engine resolves.
  • Content: A headline block for the document title, followed by a paragraph block with "markdown" instead of "text". The Markdown field accepts the full Markdown string and renders it according to the style definitions above.

The paragraph block with the markdown field is what makes this work. All Markdown syntax — headings, bold, italic, links, tables, code blocks, lists — renders with your defined styles. No HTML intermediate. No CSS. The JSON definition controls everything.

Step 3: Google Drive / S3 / Email Node

The Iteration Layer node outputs the PDF as n8n binary data. Connect it to whatever storage or delivery node your workflow needs.

Google Drive: Add a Google Drive node, set the operation to “Upload”, select the target folder, and map the binary data. The PDF lands in the shared drive.

S3: Add an AWS S3 node, set the bucket and key path, and upload the binary data. Use n8n expressions in the key to include the document title or a timestamp.

Email: Add a Send Email node, configure SMTP or OAuth, and attach the binary data. The recipient gets a styled PDF in their inbox.

Return to caller: If Response Mode on the webhook is set to “Last Node”, the PDF binary streams back as the HTTP response. The calling application gets the PDF directly without any intermediate storage.

Chaining: DOCX to Branded PDF

This is where the Markdown approach becomes powerful for document conversion. Combine Iteration Layer Document to Markdown with Document Generation to convert any document format into a consistently branded PDF.

The use case: you receive DOCX files from different teams, each with their own formatting. You need them all in a consistent PDF format with your organization’s branding — same fonts, same header, same footer, same page layout.

In n8n, this is two Iteration Layer nodes:

  • Node 1 (Document to Markdown): Convert the DOCX to clean Markdown. The source formatting is stripped. Headers, tables, lists, and body text come through as Markdown structure.
  • Node 2 (Document Generation): Take the Markdown output and render it into a styled PDF using your brand’s document definition. Every document comes out with the same look, regardless of how the original DOCX was formatted.

The Markdown acts as a normalization layer. Whatever formatting chaos exists in the source document — Comic Sans headings, inconsistent margins, random font sizes — gets reduced to semantic structure. Your document definition then applies consistent styling on top.

Get Started

Install the Iteration Layer community node from the n8n UI — search for n8n-nodes-iterationlayer under Settings > Community Nodes. The Document Generation docs cover all styling options, content block types, header/footer configuration, and template variables. The n8n integration docs walk through every resource and parameter.

Start with a short Markdown string and a minimal document definition — just the content block with a markdown field. Verify the output renders your Markdown correctly. Then add styles, headers, footers, and page numbers incrementally. Once the template looks right, wire it to your content source. Sign up to get your API key.

Build your first workflow in minutes

Chain our APIs together and ship a complete pipeline before lunch. Free trial credits included — no credit card required.