Skip to main content

n8n

Prerequisites

  • A PolyDoc account and API key - sign up for free, no credit card required. The free plan includes 150 PDF conversions per month.
  • An n8n instance - self-host for free or use n8n Cloud.

Use Case 1: Invoice PDFs from a Spreadsheet

Automatically generate an invoice PDF for every new row added to a Google Sheet and email it to the recipient.
Google Sheets trigger (new row) → Google Sheets (read line items) → Code → HTTP Request (PolyDoc API) → Gmail

Prepare your data source

Create two Google Sheets tabs: invoices and invoice_items. Examples below - import via File → Import (CSV/XLSX).

invoice_numberinvoice_dateinvoice_due_dateinvoice_subtotalinvoice_tax_rateinvoice_tax_amountinvoice_totalcustomer_namecustomer_emailcustomer_streetcustomer_citycustomer_country
INV-2026-0012026-01-152026-02-1412000.11201320John Doejohn.doe@example.com456 Customer AvenueBeautiful CityUK
invoice_numberquantitynamedescriptionpriceoriginal_price
INV-2026-0011Web Design ServicesCustom website design and development for company homepage800
INV-2026-0011Logo DesignProfessional logo design with 3 revision rounds300
INV-2026-0011SEO OptimizationSearch engine optimization for 10 target keywords100150

Create a workflow and add the trigger

Open your n8n instance and click Add workflow. Give it a descriptive name like "Invoices → PDF → Email".

Add Google Sheets → On Row Added as the first node. Connect your Google account, choose the spreadsheet, and select the invoices tab.

Run a test fetch so you have a sample row available for mapping in later nodes.

app.n8n.cloud

Next, chain nodes in this order: Google Sheets Get Row(s) on invoice_items with a built-in filter on invoice_number, Code to assemble the PolyDoc body, HTTP Request (PolyDoc), then Gmail.

Read and filter invoice_items

Add a Google Sheets node after the trigger with operation Get Row(s). Point Document at the same spreadsheet and Sheet at invoice_items.

Under Filters, click Add Filter and set:

  • Column: invoice_number
  • Value: {{ $('Google Sheets Trigger').item.json.invoice_number }} (Expression mode)

Only rows matching the new invoice's number pass through - those become your line items. Using the built-in Filters section avoids a separate Filter node.

app.n8n.cloud

Merge header fields and build items

Add a Code node after the Filter (set mode to Run Once for All Items). Paste this script - it reads the trigger header row and all filtered line items, casts numeric fields, and emits the full PolyDoc body with source and templateData:

// Run Once for All Items mode
const TEMPLATE_ID = 'YOUR_TEMPLATE_ID'; // from the PolyDoc Dashboard
const header = $('Google Sheets Trigger').first().json;
const lineItems = $input.all();

const items = lineItems.map(item => ({
quantity: Number(item.json.quantity),
name: item.json.name,
description: item.json.description,
price: Number(item.json.price),
...(item.json.original_price
? { original_price: Number(item.json.original_price) }
: {}),
}));

return [{
json: {
source: `[template:${TEMPLATE_ID}]`,
templateData: {
invoice_number: header.invoice_number,
invoice_date: header.invoice_date,
invoice_due_date: header.invoice_due_date,
invoice_subtotal: Number(header.invoice_subtotal),
invoice_tax_rate: Number(header.invoice_tax_rate),
invoice_tax_amount: Number(header.invoice_tax_amount),
invoice_total: Number(header.invoice_total),
customer_name: header.customer_name,
customer_email: header.customer_email,
customer_street: header.customer_street,
customer_city: header.customer_city,
customer_country: header.customer_country,
items,
},
},
}];
app.n8n.cloud

Add the PolyDoc HTTP request

Add an HTTP Request node after the Code node and configure:

  • Method: POST
  • URL: https://api.polydoc.tech/pdf/convert
  • Authentication: Generic Credential Type → Header Auth
  • Create a credential with Name Authorization and Value Bearer YOUR_API_KEY (get your key from the Dashboard)
  • Add header: X-Sandbox = true
  • Body Content Type: JSON
  • Specify Body: Using JSON, set the body to {{ $json }} - this passes the consolidated PolyDoc body (source + templateData) from the Code node straight through.
  • Options → Response: set Response Format to File so the PDF binary is available for the next node
app.n8n.cloud

Send the generated PDF

Add a Gmail node after the HTTP Request. Configure:

  • To: {{ $('Google Sheets Trigger').item.json.customer_email }}
  • Subject: Your PDF - {{ $('Google Sheets Trigger').item.json.invoice_number }}
  • Attachments: select the binary data from the HTTP Request node
app.n8n.cloud

Test workflow and activate

Click Test workflow to run it once with a sample row. Verify the PDF arrives in the recipient's inbox. When satisfied, toggle the workflow to Active so it runs automatically on new rows.

app.n8n.cloud

Use Case 2: Webhook Returns a PDF

Accept incoming JSON via a webhook, generate a PDF with PolyDoc, and return it directly in the HTTP response.
Webhook node → HTTP Request (PolyDoc API) → Respond to Webhook

Add webhook trigger

Create a new workflow. Add a Webhook node as the trigger and configure:

  • HTTP Method: POST
  • Respond: Using 'Respond to Webhook' Node - required so the final node can return the PDF binary instead of n8n's default JSON response.

Copy the Test URL shown in the node - you'll use it in the next step.

app.n8n.cloud

Send test data to the webhook

Send a test POST in the shape PolyDoc accepts - source plus a templateData object - so the next step can forward the body as-is:

curl -X POST <YOUR_TEST_URL> \
-H "Content-Type: application/json" \
-d '{
"source": "[template:YOUR_TEMPLATE_ID]",
"templateData": {
"invoice_number": "INV-2026-001",
"invoice_date": "2026-01-15",
"invoice_due_date": "2026-02-14",
"invoice_subtotal": 1200,
"invoice_tax_rate": 0.1,
"invoice_tax_amount": 120,
"invoice_total": 1320,
"customer_name": "John Doe",
"customer_email": "john.doe@example.com",
"customer_street": "456 Customer Avenue",
"customer_city": "Beautiful City",
"customer_country": "UK",
"items": [
{"quantity": 1, "name": "Web Design Services", "description": "Custom website design and development for company homepage", "price": 800},
{"quantity": 1, "name": "Logo Design", "description": "Professional logo design with 3 revision rounds", "price": 300},
{"quantity": 1, "name": "SEO Optimization", "description": "Search engine optimization for 10 target keywords", "price": 100, "original_price": 150}
]
}
}'

n8n captures the payload and exposes it as {{ $json.body }} in the Webhook node output. If your caller can't shape the body this way, insert a Code node before the HTTP Request (see Use Case 1 Step 4 for the pattern) to wrap arbitrary inputs into source + templateData.

app.n8n.cloud

Add PolyDoc HTTP request

Add an HTTP Request node and configure it the same way as in Use Case 1:

  • URL: https://api.polydoc.tech/pdf/convert
  • Method: POST
  • Header Auth credential: Authorization = Bearer YOUR_API_KEY
  • Header: X-Sandbox = true
  • Body Content Type: JSON
  • Specify Body: Using JSON, set the body to {{ $json.body }} - the webhook payload already carries the canonical source + templateData shape, so it forwards untouched.
  • Response Format: File
app.n8n.cloud

Respond with the PDF binary

Add a Respond to Webhook node. Set Respond With to Binary and select the PDF data from the previous node. This returns the generated PDF directly to the caller.

app.n8n.cloud

Test workflow and activate

Click Execute workflow on the canvas and POST another test payload to confirm the PDF is returned. When satisfied, click Publish in the top-right - the Production URL (under the Webhook node's Production URL tab, different from the Test URL) then accepts requests continuously, no Execute-workflow click required per call.

app.n8n.cloud