AutomationFlowsData & Sheets › Subflow — PDF Generate

Subflow — PDF Generate

Subflow — PDF Generate. Uses executeWorkflowTrigger, httpRequest, postgres, errorTrigger. Event-driven trigger; 11 nodes.

Event trigger★★★★☆ complexity11 nodesExecute Workflow TriggerHTTP RequestPostgresError Trigger
Data & Sheets Trigger: Event Nodes: 11 Complexity: ★★★★☆ Added:

This workflow follows the Error Trigger → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Subflow \u2014 PDF Generate",
  "nodes": [
    {
      "id": "pd000001-0001-0001-0001-000000000001",
      "name": "PDF Generate Trigger",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1,
      "position": [
        200,
        300
      ],
      "parameters": {}
    },
    {
      "id": "pd000002-0002-0002-0002-000000000002",
      "name": "Render HTML Template",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        300
      ],
      "parameters": {
        "language": "javaScript",
        "jsCode": "// Render HTML for the requested template_kind.\n// v1 supports LANDLORD_MONTHLY_REPORT only.\n// Expand with additional cases as new template kinds are added.\n\nconst t = $('PDF Generate Trigger').first().json;\nconst kind = t.template_kind || '';\nconst data = t.data || {};\nconst outputFilename = t.output_filename || 'report';\n\nif (kind !== 'LANDLORD_MONTHLY_REPORT') {\n  return [{ json: { html: '', error: 'UNSUPPORTED_TEMPLATE_KIND', template_kind: kind, output_filename: outputFilename } }];\n}\n\nconst landlordName = data.landlord_name || 'Landlord';\nconst reportMonth = data.report_month || '';\nconst properties = Array.isArray(data.properties) ? data.properties : [];\nconst financials = data.financials || {};\n\nconst propertyRows = properties.map(function(p) {\n  return '<tr><td>' + (p.address || '') + '</td><td>' + (p.type || '') + '</td><td>' + (p.tenant || 'Vacant') + '</td><td>' + (p.rent_ghs !== undefined ? 'GHS ' + Number(p.rent_ghs).toLocaleString() : '') + '</td><td>' + (p.status || '') + '</td></tr>';\n}).join('');\n\nconst html = '<!DOCTYPE html>\\n<html lang=\"en\">\\n<head>\\n<meta charset=\"UTF-8\">\\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\\n<title>Monthly Landlord Report</title>\\n<style>\\nbody { font-family: Arial, sans-serif; margin: 40px; color: #333; }\\nh1 { color: #1a1a2e; border-bottom: 2px solid #e4a400; padding-bottom: 8px; }\\nh2 { color: #1a1a2e; margin-top: 32px; }\\ntable { width: 100%; border-collapse: collapse; margin-top: 16px; }\\nth { background: #1a1a2e; color: white; padding: 10px 12px; text-align: left; }\\ntd { padding: 8px 12px; border-bottom: 1px solid #ddd; }\\ntr:nth-child(even) td { background: #f9f9f9; }\\n.financials-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 16px; }\\n.fin-card { background: #f0f4ff; border-radius: 8px; padding: 16px; }\\n.fin-card .label { font-size: 12px; color: #666; text-transform: uppercase; }\\n.fin-card .value { font-size: 22px; font-weight: bold; color: #1a1a2e; margin-top: 4px; }\\n.footer { margin-top: 48px; font-size: 12px; color: #888; border-top: 1px solid #eee; padding-top: 16px; }\\n</style>\\n</head>\\n<body>\\n<h1>Monthly Property Report</h1>\\n<p>Prepared for: <strong>' + landlordName + '</strong></p>\\n<p>Period: <strong>' + reportMonth + '</strong></p>\\n\\n<h2>Your Properties</h2>\\n<table>\\n<thead><tr><th>Address</th><th>Type</th><th>Tenant</th><th>Rent</th><th>Status</th></tr></thead>\\n<tbody>' + propertyRows + '</tbody>\\n</table>\\n\\n<h2>Financial Summary</h2>\\n<div class=\"financials-grid\">\\n<div class=\"fin-card\"><div class=\"label\">Total Rent Collected</div><div class=\"value\">GHS ' + Number(financials.total_rent_collected_ghs || 0).toLocaleString() + '</div></div>\\n<div class=\"fin-card\"><div class=\"label\">Outstanding Rent</div><div class=\"value\">GHS ' + Number(financials.outstanding_rent_ghs || 0).toLocaleString() + '</div></div>\\n<div class=\"fin-card\"><div class=\"label\">Occupancy Rate</div><div class=\"value\">' + (financials.occupancy_rate_pct || 0) + '%</div></div>\\n</div>\\n\\n<div class=\"footer\">Generated ' + new Date().toLocaleDateString('en-GH', {timeZone: 'Africa/Accra'}) + ' &bull; Real Estate Automation Platform &bull; Confidential</div>\\n</body>\\n</html>';\n\nreturn [{ json: { html, template_kind: kind, output_filename: outputFilename } }];"
      }
    },
    {
      "id": "pd000003-0003-0003-0003-000000000003",
      "name": "Render Error?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        680,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-no-error",
              "leftValue": "={{ $json.error ?? '' }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "pd000004-0004-0004-0004-000000000004",
      "name": "Return Template Error",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        920,
        420
      ],
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            {
              "id": "tpl-err-success",
              "name": "success",
              "value": false,
              "type": "boolean"
            },
            {
              "id": "tpl-err-error",
              "name": "error",
              "value": "={{ $json.error ?? 'UNSUPPORTED_TEMPLATE_KIND' }}",
              "type": "string"
            },
            {
              "id": "tpl-err-pdf",
              "name": "pdf_base64",
              "value": "",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "pd000005-0005-0005-0005-000000000005",
      "name": "Generate PDF via Browserless",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "onError": "continueErrorOutput",
      "position": [
        920,
        200
      ],
      "parameters": {
        "method": "POST",
        "url": "http://re-browserless:3000/pdf",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Token ' + $env.BROWSERLESS_TOKEN }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "contentType": "json",
        "jsonBody": "={{ { html: $json.html, options: { format: 'A4', printBackground: true } } }}",
        "options": {
          "timeout": 30000,
          "retry": {
            "enabled": true,
            "maxTries": 2,
            "waitBetweenTries": 2000
          },
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      }
    },
    {
      "id": "pd000006-0006-0006-0006-000000000006",
      "name": "Log Browserless Error",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        1160,
        340
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO workflow_errors (workflow_name, execution_id, node_name, error_message, input_payload) VALUES ('subflow-pdf-generate', $1, 'Generate PDF via Browserless', $2, $3::jsonb)",
        "options": {
          "queryReplacement": "={{ [$execution.id, $json.message || 'Browserless HTTP error', '{\"node\":\"Generate PDF via Browserless\"}'] }}"
        }
      }
    },
    {
      "id": "pd000007-0007-0007-0007-000000000007",
      "name": "Return Browserless Error",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1400,
        340
      ],
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            {
              "id": "bl-err-success",
              "name": "success",
              "value": false,
              "type": "boolean"
            },
            {
              "id": "bl-err-error",
              "name": "error",
              "value": "BROWSERLESS_ERROR",
              "type": "string"
            },
            {
              "id": "bl-err-pdf",
              "name": "pdf_base64",
              "value": "",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "pd000008-0008-0008-0008-000000000008",
      "name": "Encode PDF Base64",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1160,
        200
      ],
      "parameters": {
        "language": "javaScript",
        "jsCode": "// The Browserless /pdf endpoint returns binary PDF data.\n// n8n surfaces binary responses as base64 in the binary property.\n// We extract it and compute byte size for the caller.\nconst binaryData = $input.item.binary;\nlet pdfBase64 = '';\nlet byteSize = 0;\n\nif (binaryData && binaryData.data) {\n  pdfBase64 = binaryData.data.data || '';\n  // Base64 length * 3/4 approximates byte size\n  byteSize = Math.floor(pdfBase64.length * 3 / 4);\n} else if (binaryData && binaryData.file) {\n  pdfBase64 = binaryData.file.data || '';\n  byteSize = Math.floor(pdfBase64.length * 3 / 4);\n}\n\nconst trigger = $('PDF Generate Trigger').first().json;\nreturn [{ json: { pdf_base64: pdfBase64, byte_size: byteSize, kind: trigger.template_kind || '', output_filename: trigger.output_filename || '' } }];"
      }
    },
    {
      "id": "pd000009-0009-0009-0009-000000000009",
      "name": "Return PDF Result",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1400,
        200
      ],
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            {
              "id": "pdf-success",
              "name": "success",
              "value": true,
              "type": "boolean"
            },
            {
              "id": "pdf-base64",
              "name": "pdf_base64",
              "value": "={{ $json.pdf_base64 ?? '' }}",
              "type": "string"
            },
            {
              "id": "pdf-byte-size",
              "name": "byte_size",
              "value": "={{ $json.byte_size ?? 0 }}",
              "type": "number"
            },
            {
              "id": "pdf-kind",
              "name": "kind",
              "value": "={{ $json.kind ?? '' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "pd000010-0010-0010-0010-000000000010",
      "name": "Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "typeVersion": 1,
      "position": [
        200,
        600
      ],
      "parameters": {}
    },
    {
      "id": "pd000011-0011-0011-0011-000000000011",
      "name": "Log Unhandled Error",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        440,
        600
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO workflow_errors (workflow_name, execution_id, node_name, error_message, error_stack, input_payload) VALUES ('subflow-pdf-generate', $1, $2, $3, $4, $5::jsonb)",
        "options": {
          "queryReplacement": "={{ [$json.execution.id, $json.execution.lastNodeExecuted || 'unknown', $json.execution.error.message || 'unknown error', ($json.execution.error.stack ? $json.execution.error.stack.split('\\n')[0] : ''), '{\"source\":\"error_trigger\"}'] }}"
        }
      }
    }
  ],
  "connections": {
    "PDF Generate Trigger": {
      "main": [
        [
          {
            "node": "Render HTML Template",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Render HTML Template": {
      "main": [
        [
          {
            "node": "Render Error?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Render Error?": {
      "main": [
        [
          {
            "node": "Generate PDF via Browserless",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Return Template Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate PDF via Browserless": {
      "main": [
        [
          {
            "node": "Encode PDF Base64",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Browserless Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Encode PDF Base64": {
      "main": [
        [
          {
            "node": "Return PDF Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Browserless Error": {
      "main": [
        [
          {
            "node": "Return Browserless Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Trigger": {
      "main": [
        [
          {
            "node": "Log Unhandled Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "tags": [
    {
      "name": "real-estate-automation"
    },
    {
      "name": "subflow"
    },
    {
      "name": "version-1.0"
    }
  ]
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Subflow — PDF Generate. Uses executeWorkflowTrigger, httpRequest, postgres, errorTrigger. Event-driven trigger; 11 nodes.

Source: https://github.com/jameszokah/real-estate-n8n-workflow/blob/ff6c75dac6b332a260a52efcb695ac4f3c962ed3/n8n-workflows/subflows/pdf-generate.json — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

Agendamiento_v2. Uses n8n-nodes-evolution-api, redis, httpRequest, executeWorkflowTrigger. Event-driven trigger; 59 nodes.

N8N Nodes Evolution Api, Redis, HTTP Request +3
Data & Sheets

Cancelacion_v2. Uses executeWorkflowTrigger, redis, httpRequest, n8n-nodes-evolution-api. Event-driven trigger; 46 nodes.

Execute Workflow Trigger, Redis, HTTP Request +3
Data & Sheets

Youtube Searcher. Uses splitInBatches, httpRequest, manualTrigger, executeWorkflowTrigger. Event-driven trigger; 21 nodes.

HTTP Request, Execute Workflow Trigger, Postgres +1
Data & Sheets

Log errors and avoid sending too many emails. Uses errorTrigger, postgres, stickyNote, emailSend. Event-driven trigger; 16 nodes.

Error Trigger, Postgres, Email Send +2
Data & Sheets

Most of the time, it’s necessary to log all errors that occur. However, in some cases, a scheduled task or service consuming excessive resources might trigger a surge of errors.

Error Trigger, Postgres, Email Send +2