Automate PDF Generation with n8n: HTML Templates + One HTTP Node (No Puppeteer, No Code Nodes)

You can generate branded PDFs from n8n with a single HTTP Request node and no Puppeteer, no code nodes, and no self-hosted requirements. MarkupGo's template…

You can generate branded PDFs from n8n with a single HTTP Request node and no Puppeteer, no code nodes, and no self-hosted requirements. MarkupGo's template editor and Magic Template URL let you design once and trigger from any n8n workflow — webhook, schedule, or database row. Two patterns work: the standard API call with an API key and template body, or the zero-body Magic URL that passes all data through query parameters. The gotchas that break most n8n PDF setups are binary handling between nodes and retry logic for failed renders.

Build your invoice or report template in MarkupGo

Open the template editor and design with HTML/CSS or start from a preset. Add dynamic fields with double-brace syntax: {{customer}}, {{total}}, {{items}}. Save the template ID for the next steps. The same template already works for Make.com and Google Sheets workflows [1].

A template for a simple invoice might look like this:

<div style="font-family: system-ui; padding: 40px;">
  <h1>Invoice {{invoiceNumber}}</h1>
  <p>Customer: {{customer}}</p>
  <p>Date: {{date}}</p>
  <table>
    {{#each items}}
    <tr>
      <td>{{name}}</td>
      <td>{{price}}</td>
    </tr>
    {{/each}}
  </table>
  <p style="font-weight: bold;">Total: {{total}}</p>
</div>

Standard API pattern: one HTTP Request node

Add an HTTP Request node to your n8n workflow. Set the method to POST and the URL to:

/api/v1/pdf/buffer

Appending /buffer to the /pdf endpoint returns the PDF file directly rather than a JSON task object [2]. Use your full base URL as shown in Markupgo's API reference.

Set these headers:

  • Content-Type: application/json
  • x-api-key: your MarkupGo API key [2]

In the body, send JSON with this structure:

{
  "source": {
    "type": "template",
    "data": {
      "id": "YOUR_TEMPLATE_ID",
      "context": {
        "customer": "Acme Corp",
        "invoiceNumber": "INV-001",
        "total": "€1,250.00",
        "items": [
          {"name": "Consulting", "price": "€1,000.00"},
          {"name": "Hosting", "price": "€250.00"}
        ]
      }
    }
  },
  "options": {
    "properties": {
      "size": {"width": 210, "height": 297},
      "margins": {"top": 10, "bottom": 10, "left": 10, "right": 10}
    }
  }
}

The source.type field must be set to template, and source.data.id must contain your copied template ID [2].

Critical step: set the HTTP Request node's Response Format to File. Without this, n8n receives a JSON string instead of binary PDF data, and downstream nodes cannot attach or upload the file [3]. The binary property defaults to data.

Reference this binary property in later nodes using the expression:

{{ $binary.data }}

Or from a specific node:

{{ $("HTTP Request").item.binary.data }}
``` [[3]](https://www.width.ai/post/n8n-binary-data)

## Magic Template URL pattern: zero API body

The Magic Template URL lets you generate PDFs without any headers, JSON body, or code node [[4]](https://markupgo.com/docs/magic-template-url). The structure is:

https://render.markupgo.com/template/{templateId}.pdf?customer=Acme%20Corp&total=€1,250.00


Enable Magic URL for your template in the MarkupGo dashboard first. Then in n8n, use a single HTTP Request node with method GET and the full Magic URL as the URL field.

Pass line-item arrays with bracket syntax, since MarkupGo parses query parameters using the `qs` library with `arrayFormat: 'brackets'` [[4]](https://markupgo.com/docs/magic-template-url):

items[0]name=Consulting&items[0]price=€1,000.00&items[1]name=Hosting&items[1]price=€250.00


Cache reuse within 15 days serves identical URLs without consuming additional credits [[4]](https://markupgo.com/docs/magic-template-url).

The Magic URL is public — anyone with the URL can generate files and consume your credits. Add `json=true` to receive a task object response instead of the raw PDF, or `redirect=true` for a 302 redirect to the generated file. Both parameters help avoid exposing generated file URLs directly [[4]](https://markupgo.com/docs/magic-template-url).

For n8n workflows, `json=true` is usually the safer choice. You receive a task object with a `url` field, then fetch that URL in a second HTTP Request node if you need the binary file.

## Attach the PDF to email, WhatsApp, or Drive

The HTTP Request node returns binary property `data` by default when Response Format is set to File. Gmail and Send Email nodes need the binary property name to match what they expect in their attachment field.

If a downstream node expects a different binary property name, use the Edit Fields (Set) node to rename it. n8n's Edit Fields node now supports a binary data type for this purpose [[3]](https://www.width.ai/post/n8n-binary-data).

For Google Drive, set the **File** field in the Google Drive node to:


For WhatsApp Business Cloud, reference the same binary expression in the **Media** field.

The Extract From File node's **Input Binary Field** defaults to `data`, but must match the upstream node's binary property name exactly. A mismatch here produces empty files or "no data" errors [[3]](https://www.width.ai/post/n8n-binary-data).

## Retry failed renders without burning credits or creating loops

Enable **Retry On Fail** in the HTTP Request node only for HTTP status 429 and 5xx errors. Set the wait to 3 seconds with 3 attempts. This catches rate limits and transient render failures without consuming extra credits for immediate failures.

Fail immediately on 401 Unauthorized. Retrying an invalid API key creates unnecessary delay and noise. Route 401s to a separate error workflow or stop the execution.

Do not combine **Retry On Fail** with **On Error: Continue**. This combination swallows errors silently and passes undefined binary data to downstream nodes, which then fail with cryptic messages about missing attachments.

For production workflows, route all failures to a dedicated error workflow that posts to Slack or sends an email alert. Include the workflow ID, execution URL, and error message so you can diagnose without digging through execution logs.

## Set PDF expiration for sensitive documents

Add `expiresInSeconds` to the API body for auto-deletion. The range is 60 seconds to 90 days (7,776,000 seconds) [[2]](https://markupgo.com/docs/api/pdf).

```json
{
  "source": {
    "type": "template",
    "data": {
      "id": "YOUR_TEMPLATE_ID",
      "context": { /* your data */ }
    }
  },
  "expiration": {
    "expiresInSeconds": 86400
  }
}

Expired tasks are swept every minute. Use this for invoices, medical reports, or anything containing personal data that should not persist on the server. The file may remain reachable for up to 60 seconds past the requested expiration due to sweep interval timing [2].

Start with the free trial and reuse your existing templates

MarkupGo offers 100 credits per month with no credit card required. The same template used in Make.com or Google Sheets workflows works here without modification [1].

Upgrade paths scale with volume:

  • Lite: entry-level plan with a monthly credit allowance and a small template limit
  • Plus: mid-tier plan with a larger credit allowance and more templates
  • Pro: top-tier plan with the largest credit allowance and unlimited templates

Build your template once in the MarkupGo dashboard, then trigger it from n8n via standard API or Magic URL depending on whether you need full control or zero-configuration simplicity.

Sources

  1. markupgo.com
  2. markupgo.com
  3. n8n Binary Data: Complete Guide to File Handling (2026) (width.ai)
  4. markupgo.com