DocumentGenerator Node Outputting blank or broken HTML Despite Correct Input JSON

DocumentGenerator Node Outputting blank or broken HTML Despite Correct Input JSON

Hi n8n Community,

I’m building a workflow to optimize a resume (from PDF) based on a job description, using Gemini and then generate an HTML version of the optimized resume using a specific template that I have created.

I’m running into trouble with the n8n-nodes-document-generator community node (the one that provides the “Template” or “DocumentGenerator” node). It’s outputting either blank or incorrect HTML even though the input data structure seems correct based on the node’s documentation and examples.

Workflow Objective & Flow:

  1. Webhook receives Resume PDF and Job Description text.

  2. Extract text from PDF.

  3. LLM 1 parses PDF text into structured JSON (parsedResumeJSON).

  4. Set node consolidates parsedResumeJSON, Job Description (jobDescription), and original Resume File Name (resumeFileName).

  5. LLM 2 takes the JSON and JD, optimizes the content, and outputs new JSON including companyName, jobTitle, ats_score, and the full optimized resume data (optimizedResumeData).

  6. Code node extracts only the optimizedResumeData object from LLM 2’s output and formats it as [ { "items": [ { resume data object } ] } ] based on documentation examples.

  7. DocumentGenerator node receives the output from the Code node. It’s configured with Render All Items with One Template = ON. The template uses {{#each items}} ... {{name}} ... {{/each}} syntax.

  8. (Remaining steps: Set node, Google Drive upload, Google Sheets logging)

The Problem:

The DocumentGenerator node (Step 7) runs without error but outputs either blank or broken HTML (Either none of the {{variable}} placeholders are filled or if they are filled then the structure is broken). This happens despite the Code node (Step 6) seemingly providing the correct input structure.

What I’ve Tried:

  • Different configurations of the DocumentGenerator node (Render All Items... ON/OFF).

  • Different output structures from the Code node ([ {data} ], [ { json: {data} } ], [ { items: [ {data} ] } ]).

The current configuration matches the documentation examples for the Render All Items... ON mode, but it’s still not rendering the data.

What is the error message (if any)?

There’s no specific error message from the DocumentGenerator node, just blank or broken output.

Please share your workflow

{
  "name": "JD based resume optimizer",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "WEBHOOK_PATH_PLACEHOLDER",
        "options": {
          "binaryData": true,
          "binaryPropertyName": "resumeFile"
        }
      },
      "id": "6dea02f4-0bd5-46b3-b864-b9acd8040dba",
      "name": "Start: Receive Resume & JD",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        -128,
        32
      ],
      "webhookId": "WEBHOOK_ID_PLACEHOLDER"
    },
    {
      "parameters": {
        "operation": "pdf",
        "binaryPropertyName": "resumeFile",
        "options": {}
      },
      "id": "ec495d7e-4d13-47a1-b2f7-7490553576ca",
      "name": "Parse: Read Resume Text",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1,
      "position": [
        80,
        32
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a35dd1f4-a347-40c7-b42b-7ea66b3e8529",
              "name": "parsedResumeJSON",
              "value": "={{ $json.content.parts[0].text }}",
              "type": "string"
            },
            {
              "id": "eb1695fc-a38b-4dac-a839-950efa7a1937",
              "name": "jobDescription",
              "value": "={{ decodeURIComponent($node[\"Start: Receive Resume & JD\"].json.query.jobDescription) }}",
              "type": "string"
            },
            {
              "id": "a6b88325-6d0d-46f5-ac1d-9696b4b881bb",
              "name": "resumeFileName",
              "value": "={{ decodeURIComponent($node[\"Start: Receive Resume & JD\"].json.query.resumeFileName) }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        656,
        32
      ],
      "id": "7fcc5a9f-fbcd-4505-9ed3-5b3bb18a5140",
      "name": "Set: Consolidate Data"
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "models/gemini-2.5-flash",
          "mode": "list",
          "cachedResultName": "models/gemini-2.5-flash"
        },
        "messages": {
          "values": [
            {
              "content": "=You are an expert resume parser. Take the following raw resume text and convert it into a *perfect JSON object*. The JSON structure MUST exactly match the provided Handlebars HTML template structure.\n\n**Resume Text:**\n{{ $json.text }}\n\n**Required JSON Structure (based on HTML template):**\n{\n  \"name\": \"Full Name\",\n  \"title\": \"Current or Target Job Title\",\n  \"phone\": \"Phone Number\",\n  \"email\": \"Email Address\",\n  \"linkedin_url\": \"Full LinkedIn URL\",\n  \"linkedin_text\": \"linkedin.com/in/username\",\n  \"location\": \"City, State\",\n  \"current_company\": \"Current Company Name\",\n  \"profile_summary\": \"The full professional summary, as a single HTML string with <p> tags.\",\n  \"profile_strengths_line\": \"A comma-separated list of strengths.\",\n  \"experience_entries\": [\n    {\n      \"job_title_company\": \"Job Title - Company Name\",\n      \"job_dates\": \"Month YYYY - Month YYYY\",\n      \"bullet_points\": [\n        \"Responsibility or achievement 1 (can contain <strong> tags)\",\n        \"Responsibility or achievement 2\"\n      ]\n    }\n  ],\n  \"education_entries\": [\n    {\n      \"degree_school\": \"Degree - University Name\",\n      \"education_dates\": \"Month YYYY\",\n      \"education_details\": \"e.g., Major, Minor, GPA\"\n    }\n  ],\n  \"skill_categories\": [\n    {\n      \"title\": \"Category Name (e.g., Languages)\",\n      \"list\": \"Comma, separated, list, of, skills\"\n    }\n  ],\n  \"awards\": [\n    \"Award or certification 1 (can contain <strong> tags)\"\n  ]\n}\n\n**Instructions:**\n1.  Populate every field. If no information is found for a field, return an empty string \"\" or an empty array [].\n2.  For \"profile_summary\" and \"bullet_points\", use basic HTML (like `<p>` and `<strong>`) as needed.\n3.  Return *only* the raw JSON object."
            }
          ]
        },
        "jsonOutput": true,
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "typeVersion": 1,
      "position": [
        304,
        32
      ],
      "id": "185f9310-3fb5-4deb-b05d-a719f2b5e804",
      "name": "LLM 1: Parse Resume to JSON",
      "retryOnFail": true,
      "credentials": {
        "googlePalmApi": {
          "id": "GEMINI_CREDENTIAL_ID_PLACEHOLDER",
          "name": "Google Gemini(PaLM) Api account"
        }
      }
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "9ec51270-da23-47a8-ab69-4be0bcb9fc65",
              "name": "llmOutput",
              "value": "={{ $json.htmlResult }}",
              "type": "object"
            },
            {
              "id": "724eef4e-b85a-49e6-ad13-0bf473ea7fcc",
              "name": "htmlResult",
              "value": "={{ $json.htmlResult }}",
              "type": "object"
            },
            {
              "id": "d24fe052-4b21-4c25-9943-4a0d844142dc",
              "name": "fileName",
              "value": "={{ $('Code: Parse LLM2 Output').item.json.name }}_{{ $json.llmOutput.companyName }}_{{ $now.toFormat('yyyy-MM-dd') }}_{{ $now.toFormat('HH-mm') }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1760,
        32
      ],
      "id": "de8052a6-a9cf-4f02-9345-e924a52379d7",
      "name": "Set: Final Prep for Google"
    },
    {
      "parameters": {
        "inputDataFieldName": "={{ $json.htmlResult }}",
        "name": "={{ $json.fileName }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "value": "FOLDER_ID_PLACEHOLDER",
          "mode": "id"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        2032,
        32
      ],
      "id": "a73ef77a-54fa-4e64-ac33-03d0e8fb53b1",
      "name": "Upload: Google Doc",
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "GDRIVE_CREDENTIAL_ID_PLACEHOLDER",
          "name": "Google Drive account"
        }
      }
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "SHEET_ID_PLACEHOLDER",
          "mode": "list",
          "cachedResultName": "JD based resume optimizer",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/SHEET_ID_PLACEHOLDER/edit?usp=drivesdk"
        },
        "sheetName": {
          "__rl": true,
          "value": "SHEET_GID_PLACEHOLDER",
          "mode": "list",
          "cachedResultName": "Sheet1",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/SHEET_ID_PLACEHOLDER/edit#gid=SHEET_GID_PLACEHOLDER"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "upload date": "={{ $now.toFormat('yyyy-MM-dd') }}",
            "upload time in hh:mm": "={{ $now.toFormat('HH:mm') }}",
            "uploaded resume name": "={{ $node[\"Set: Consolidate Data\"].json.resumeFileName }}",
            "full name": "={{ $node[\"Set: Final Prep for Google\"].json.llmOutput.optimizedResumeData.name }}",
            "current job title": "={{ $node[\"Set: Final Prep for Google\"].json.llmOutput.optimizedResumeData.title }}",
            "phone": "={{ $node[\"Set: Final Prep for Google\"].json.llmOutput.optimizedResumeData.phone }}",
            "current company": "={{ $node[\"Set: Final Prep for Google\"].json.llmOutput.optimizedResumeData.current_company }}",
            "email": "={{ $node[\"Set: Final Prep for Google\"].json.llmOutput.optimizedResumeData.email }}",
            "modified resume link": "={{ $json.webViewLink }}",
            "modified resume name": "={{ $json.name }}",
            "modified resume ats score": "={{ $node[\"Set: Final Prep for Google\"].json.llmOutput.ats_score }}"
          },
          "matchingColumns": [],
          "schema": [
            { "id": "upload date", "displayName": "upload date", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false },
            { "id": "upload time in hh:mm", "displayName": "upload time in hh:mm", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false },
            { "id": "uploaded resume name", "displayName": "uploaded resume name", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false },
            { "id": "full name", "displayName": "full name", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false },
            { "id": "current job title", "displayName": "current job title", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false },
            { "id": "current company", "displayName": "current company", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false },
            { "id": "phone", "displayName": "phone", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false },
            { "id": "email", "displayName": "email", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false },
            { "id": "modified resume link", "displayName": "modified resume link", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false },
            { "id": "modified resume name", "displayName": "modified resume name", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false },
            { "id": "modified resume ats score", "displayName": "modified resume ats score", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        2240,
        32
      ],
      "id": "c9f9986f-8f9f-496a-aa30-aa0ae976c94f",
      "name": "Append row in sheet",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "GSHEETS_CREDENTIAL_ID_PLACEHOLDER",
          "name": "jack Google Sheets account"
        }
      }
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "models/gemini-2.5-pro",
          "mode": "list",
          "cachedResultName": "models/gemini-2.5-pro"
        },
        "messages": {
          "values": [
            {
              "content": "=You are an expert ATS resume optimizer and career coach. You will be given a candidate's resume as a JSON object and a raw job description (JD) text. Your goal is to optimize the resume JSON object to get the highest possible ATS score while remaining 100% truthful.\n\n**Rules:**\n1.  **NO HALLUCINATION:** You MUST NOT fabricate, invent, or add new experiences. You can only *rephrase* existing bullet points to include keywords.\n2.  **NO EM-DASHES:** You MUST NOT use em-dashes (—).\n3.  **OPTIMIZE:** Strategically integrate keywords from the JD into the 'profile_summary', 'experience_entries' bullet points, and 'skill_categories' fields of the input JSON.\n4.  **TAILOR:** The 'profile_summary' and 'profile_strengths_line' must be tailored to the JD. Update the 'title' field to match the JD if appropriate.\n5.  **PARSE JD:** You must parse the raw `jobDescription` text to find the `companyName` and `jobTitle`.\n6.  **ESTIMATE SCORE:** Provide an estimated `ats_score` (0-100) for the *newly modified* resume content.\n7.  **OUTPUT STRUCTURE:** Return a single, new JSON object containing the `companyName`, `jobTitle`, `ats_score`, and the **complete, optimized resume data** within a key named `optimizedResumeData`. The structure inside `optimizedResumeData` MUST exactly match the input resume JSON structure.\n\n**Input Resume JSON:**\n{{ $node[\"Set: Consolidate Data\"].json.parsedResumeJSON }}\n\n**Input Job Description (Text):**\n{{ $node[\"Set: Consolidate Data\"].json.jobDescription }}\n\n**Required JSON Output Format:**\n{\n  \"companyName\": \"The Company Name (from JD)\",\n  \"jobTitle\": \"The Job Title (from JD)\",\n  \"ats_score\": 92,\n  \"optimizedResumeData\": {\n    \"name\": \"Full Name (Optimized)\",\n    \"title\": \"Job Title (Optimized)\",\n    \"phone\": \"Phone Number\",\n    \"email\": \"Email Address\",\n    \"linkedin_url\": \"LinkedIn URL\",\n    \"linkedin_text\": \"linkedin.com/in/username\",\n    \"location\": \"City, State\",\n    \"current_company\": \"Current Company\",\n    \"profile_summary\": \"<p>Optimized summary HTML...</p>\",\n    \"profile_strengths_line\": \"Optimized strengths line...\",\n    \"experience_entries\": [\n      {\n        \"job_title_company\": \"Optimized Job Title - Company\",\n        \"job_dates\": \"Dates\",\n        \"bullet_points\": [\n          \"Optimized bullet 1...\",\n          \"Optimized bullet 2...\"\n        ]\n      }\n    ],\n    \"education_entries\": [ ... ],\n    \"skill_categories\": [ ... ],\n    \"awards\": [ ... ]\n  }\n}\n\nReturn *only* the raw JSON object."
            }
          ]
        },
        "jsonOutput": true,
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "typeVersion": 1,
      "position": [
        864,
        32
      ],
      "id": "dee981b5-b144-4a14-8d55-104ed06e9075",
      "name": "LLM 2: Optimize Resume",
      "retryOnFail": true,
      "credentials": {
        "googlePalmApi": {
          "id": "GEMINI_CREDENTIAL_ID_PLACEHOLDER",
          "name": "Google Gemini(PaLM) Api account"
        }
      }
    },
    {
      "parameters": {
        "template": "<!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>{{name}} - {{title}}</title>\n    <link rel=\"preconnect

Share the output returned by the last node (Document Generator HTML Node)

[
{
“htmlResult”: “\n\n\n \n \n - \n \n \n <link href=“https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700;900&display=swap\\” rel=“stylesheet”>\n \n \n /* All CSS from our final version is included here */\n :root {\n --page-width: 816px; \n --page-padding: 40px;\n --main-font: ‘Lato’, -apple-system, BlinkMacSystemFont, “Segoe UI”, Roboto, “Helvetica Neue”, Arial, sans-serif;\n --body-font-size: 10.5pt;\n --line-height: 1.2; \n --header-bg: #1d2a4a;\n --header-text: #ffffff;\n --text-color: #333333;\n --footer-text: #aaaaaa;\n --footer-bg: #f5f5f5;\n }\n body {\n font-family: var(–main-font);\n font-size: var(–body-font-size);\n line-height: var(–line-height);\n color: var(–text-color);\n background-color: #f4f4f4;\n margin: 0;\n padding: 0;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n }\n .resume-container {\n width: var(–page-width);\n min-height: 1056px; \n margin: 20px auto;\n background-color: #ffffff;\n box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n }\n .resume-header {\n background-color: var(–header-bg);\n color: var(–header-text);\n text-align: center;\n padding: 18px var(–page-padding); \n }\n .resume-header .name {\n font-size: 2.1em; \n font-weight: 900; \n margin: 0 0 10px 0; \n }\n .contact-info {\n list-style: none;\n padding: 0;\n margin: 0;\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n gap: 10px 18px; \n }\n .contact-info li {\n font-size: 0.9em;\n font-weight: 400;\n display: flex;\n align-items: center;\n }\n .contact-info .icon {\n width: 14px;\n height: 14px;\n margin-right: 8px;\n fill: var(–header-text);\n }\n .contact-info a {\n text-decoration: none;\n color: var(–header-text);\n display: flex;\n align-items: center;\n }\n .resume-body {\n padding: 12px var(–page-padding) 8px; \n flex-grow: 1; \n }\n .resume-section {\n margin-bottom: 10px; \n }\n .resume-section:last-of-type {\n margin-bottom: 0;\n }\n .section-title {\n text-align: center;\n margin: 0 0 6px 0; \n }\n .section-title h2 {\n font-size: 1.1em; \n font-weight: 900;\n color: var(–text-color);\n margin: 0;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n .section-title .divider {\n width: 50px;\n height: 2px;\n background-color: #ddd;\n margin: 4px auto 0 auto; \n }\n #profile p {\n margin: 0 0 5px 0; \n text-align: justify; \n }\n #profile p:last-of-type {\n margin-bottom: 0;\n text-align: left; \n }\n #profile strong {\n font-weight: 900;\n color: #000;\n }\n .job-entry {\n margin-bottom: 8px; \n }\n .job-entry:last-of-type {\n margin-bottom: 0;\n }\n .job-header {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n flex-wrap: wrap;\n margin-bottom: 3px; \n }\n .job-company {\n font-size: 1.15em; \n font-weight: 900;\n color: #000;\n }\n .job-dates {\n font-size: 1em;\n font-weight: 700; \n color: var(–text-color);\n }\n .bullet-list {\n padding-left: 20px;\n margin: 0;\n }\n .bullet-point {\n margin-bottom: 3px; \n }\n .bullet-point:last-of-type {\n margin-bottom: 0;\n }\n .education-entry {\n margin-bottom: 5px; \n }\n .education-entry:last-of-type {\n margin-bottom: 0;\n }\n .education-header {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n font-weight: 900;\n }\n .education-degree {\n font-size: 1.1em;\n color: #000;\n }\n .education-dates {\n font-size: 1em;\n font-weight: 700; \n color: var(–text-color);\n }\n .education-details {\n margin-left: 0;\n font-size: 1em;\n font-weight: 400;\n }\n .skills-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 6px 25px; \n }\n .skills-category {\n margin-bottom: 0;\n }\n .skills-category h3 {\n font-size: 1.1em;\n font-weight: 900;\n color: #000;\n margin: 0 0 2px 0; \n }\n .skills-list {\n margin: 0;\n padding: 0;\n font-size: 0.95em;\n }\n .awards-list {\n padding-left: 20px;\n margin: 0;\n }\n .award-item {\n margin-bottom: 4px; \n }\n .award-item:last-of-type {\n margin-bottom: 0;\n }\n .resume-footer {\n background-color: var(–footer-bg);\n color: var(–footer-text);\n padding: 8px var(–page-padding); \n display: flex;\n justify-content: space-between;\n align-items: center;\n font-size: 0.9em;\n }\n @media print {\n body { background-color: #ffffff; font-size: 10pt; margin: 0; padding: 0; }\n .resume-container { margin: 0; padding: 0; border: none; box-shadow: none; width: 100%; min-height: 0; }\n .resume-body { padding: 12px 40px 8px; }\n .resume-header, .resume-footer { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n .resume-footer { position: fixed; bottom: 0; left: 0; right: 0; width: 100%; box-sizing: border-box; }\n .job-entry, .education-entry { page-break-inside: avoid; }\n @page { size: A4; margin: 0; }\n }\n \n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n

\n \n

| \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n
    \n PROFILE\n
    \n \n

    \n Strengths: \n \n \n\n \n

    \n WORK EXPERIENCE\n
    \n \n\n \n\n \n
    \n EDUCATION\n
    \n \n\n \n\n \n
    \n SKILLS & TOOLS\n
    \n \n
    \n \n \n\n \n
    \n AWARDS\n
    \n \n
      \n\n \n \n \n\n
      \n \n \n \n \n”
      }
      ]

      Information on your n8n setup

      • n8n version: Version 1.111.1
      • Database (default: SQLite): SQLite
      • n8n EXECUTIONS_PROCESS setting (default: own, main):main
      • Running n8n via (Docker, npm, n8n cloud, desktop app): npm
      • Operating system: Windows 10
      • Community Node Version: v1.0.10

hello @jack_knight

For community node issues, it’s better to reach out to the node’s author. The package on npm should have a link to GitHub, where you can open an issue.