Zie619/n8n-workflows · error · Error

Quote not found

Error message

Quote not found

What it means

Thrown by 'Prepare Overlay Text (Quote & Author)1' when $('Get data from Google Sheet').first().json['Quote (Thai)'] is falsy. The node reads the Thai quote and pen name from the first item of the Google Sheets node and builds an ffmpeg drawtext filter (word-wrapping, fonts, layout) for a video overlay. A missing column value — renamed column, empty cell, or empty sheet — triggers this.

Source

Thrown at workflows/Wait/1400_Wait_Code_Automation_Webhook.json:822

        "options": {
          "append": false
        },
        "fileName": "=SoundBackground.mp3",
        "operation": "write"
      },
      "typeVersion": 1,
      "notes": "This readWriteFile node performs automated tasks as part of the workflow."
    },
    {
      "id": "b9332740-c4e0-40f9-bc0d-c550c8f0f96d",
      "name": "Prepare Overlay Text (Quote & Author)1",
      "type": "n8n-nodes-base.code",
      "position": [
        300,
        860
      ],
      "parameters": {
        "jsCode": "// Define separate configuration for the quote and the author\nconst quoteFont = \"Kanit-Italic.ttf\";      \nconst quoteFontSize = 70;\nconst authorFont = \"Kanit-Italic.ttf\";     \nconst authorFontSize = 50;\nconst fontColor = \"white\";\nconst lineHeightMultiplier = 1.1;\nconst videoWidth = 1080;\nconst margin = 40;  \n\n// Effective width for the quote text\nconst effectiveVideoWidth = videoWidth - 2 * margin;\n\n// Estimate average character width based on quoteFontSize\nconst avgCharWidth = quoteFontSize * 0.6;\nconst maxCharsPerLine = Math.floor(effectiveVideoWidth / avgCharWidth);\n\n// Retrieve the quote and author from \"Select Random Video, Music & Quote\"\nconst transcript = $('Get data from Google Sheet').first().json['Quote (Thai)'];\nif (!transcript) {\n  throw new Error(\"Quote not found\");\n}\n\nconst author = $('Get data from Google Sheet').first().json['Pen Name (Thai)'];\nif (!author) {\n  throw new Error(\"Author not found\");\n}\n\n// Split transcript into words and group them into lines\nconst words = transcript.split(' ');\nconst lines = [];\nlet currentLine = \"\";\nlet currentCharCount = 0;\n\nwords.forEach(word => {\n  const wordLength = word.length;\n  const additionalSpace = currentLine ? 1 : 0;\n  const potentialLength = currentCharCount + additionalSpace + wordLength;\n  if (potentialLength <= maxCharsPerLine) {\n    currentLine += (currentLine ? \" \" : \"\") + word;\n    currentCharCount = potentialLength;\n  } else {\n    lines.push(currentLine);\n    currentLine = word;\n    currentCharCount = wordLength;\n  }\n});\nif (currentLine) {\n  lines.push(currentLine);\n}\n\n// Calculate layout for the quote block\nconst lineHeight = quoteFontSize * lineHeightMultiplier;\nconst totalHeight = lines.length * lineHeight;\n\n// Build drawtext commands for quote lines\nconst quoteCommands = lines.map((line, index) => {\n  const escapedLine = line.replace(/'/g, \"\\\\'\");\n  return `drawtext=fontfile=${quoteFont}:text='${escapedLine}':fontsize=${quoteFontSize}:fontcolor=${fontColor}:x=(w-text_w)/2:y=((h-${totalHeight})/2)+(${index}*${lineHeight})`;\n});\n\n// Build the drawtext command for author\nconst authorY = `((h-${totalHeight})/2)+(${lines.length}*${lineHeight})+20`;\nconst escapedAuthor = author.replace(/'/g, \"\\\\'\");\nconst authorCommand = `drawtext=fontfile=${authorFont}:text='${escapedAuthor}':fontsize=${authorFontSize}:fontcolor=${fontColor}:x=w-text_w-${margin}:y=${authorY}`;\n\n// Combine all commands into one drawtext filter string\nconst fullDrawTextFilter = quoteCommands.concat(authorCommand).join(\", \");\n\n// Return the prepared filter string\nreturn {\n  json: {\n    drawText: fullDrawTextFilter\n  }\n};"
      },
      "typeVersion": 2,
      "notes": "This code node performs automated tasks as part of the workflow."
    },
    {
      "id": "79764093-f6ca-459b-a73c-3326fe82fafa",
      "name": "Generate Final Video Clip1",
      "type": "n8n-nodes-base.executeCommand",
      "position": [
        480,
        860
      ],
      "parameters": {
        "command": "=ffmpeg -i {{ $('Save Video Background Locally1').item.json.fileName }} -i {{ $('Save Music Background Locally1').item.json.fileName }} -filter_complex \"[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920[vid]; color=black@0.3:size=1080x1920:d=10[bg]; [vid][bg]overlay=shortest=1[bgvid]; [bgvid]{{ $json.drawText }}[outv]; [1:a]volume=0.8[aout]\" -map \"[outv]\" -map \"[aout]\" -aspect 9:16 -c:v libx264 -c:a aac -shortest output.mp4 -y"
      },
      "typeVersion": 1,
      "notes": "This executeCommand node performs automated tasks as part of the workflow."
    },

View on GitHub (pinned to 94007c1445)

Solutions

  1. Pin the output of 'Get data from Google Sheet' and check the exact keys of item 0 (spacing/casing of 'Quote (Thai)').
  2. Fix the sheet: restore the header, fill the empty cell, or update the Sheets node column selection to include 'Quote (Thai)' and 'Pen Name (Thai)'.
  3. Filter rows upstream so only rows with a non-empty quote can reach this node.
  4. Fail with context: include Object.keys(row) in the error message to make the next occurrence self-diagnosing.

Example fix

// before
const transcript = $('Get data from Google Sheet').first().json['Quote (Thai)'];
if (!transcript) {
  throw new Error("Quote not found");
}

// after
const sheetRow = $('Get data from Google Sheet').first().json;
const transcript = sheetRow['Quote (Thai)'];
if (!transcript || !String(transcript).trim()) {
  throw new Error(`Quote not found. Available columns: ${Object.keys(sheetRow).join(', ')}`);
}
Defensive patterns

Strategy: validation

Validate before calling

const row = $('Get data from Google Sheet').first().json;
const quote = row['Quote (Thai)'];
const author = row['Pen Name (Thai)'];
if (!quote || !String(quote).trim() || !author) {
  throw new Error(`Quote/author missing. Sheet columns: ${Object.keys(row).join(', ')}`);
}

Type guard

const hasQuoteRow = (row) =>
  typeof row?.['Quote (Thai)'] === 'string' && row['Quote (Thai)'].trim() !== '' &&
  typeof row?.['Pen Name (Thai)'] === 'string' && row['Pen Name (Thai)'].trim() !== '';

Prevention

When it happens

Trigger: The sheet column was renamed from 'Quote (Thai)' (extra space, different casing, English name), the selected row has an empty quote cell, the sheet lookup returned zero matching rows (first() then yields an empty json), or the Sheets node's 'Which Sheet/Columns' config drifted from the live sheet.

Common situations: Shared Google Sheets edited by humans (renamed headers, cleared cells), a filter/random-select step picking an incomplete row, or the Sheets node configured to return only certain columns so 'Quote (Thai)' is absent from json.

Related errors


AI-assisted analysis of Zie619/n8n-workflows@94007c1445 (2026-08-15). Data as JSON: /api/errors/5ed23b17551406fa. Report an issue: GitHub.