AutomationFlowsData & Sheets › Automate Document Creation from n8n Workflow

Automate Document Creation from n8n Workflow

Original n8n title: __create-doc

__Create-doc. Uses executeWorkflowTrigger, googleDrive, googleSheets. Event-driven trigger; 5 nodes.

Event trigger★★★★☆ complexity5 nodesExecute Workflow TriggerGoogle DriveGoogle Sheets
Data & Sheets Trigger: Event Nodes: 5 Complexity: ★★★★☆ Added:

This workflow follows the Execute Workflow Trigger → Google Drive 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": "__Create-doc",
  "nodes": [
    {
      "parameters": {
        "inputSource": "passthrough"
      },
      "id": "54bf80ac-69f1-4cb3-b807-ab7ed4c8d760",
      "name": "When Executed by Another Workflow",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Updated Format Document code to handle both API and scraping paths\n// Log the incoming raw data for debugging\nconsole.log(\"Format Document - Received Data:\", JSON.stringify($json));\n\n// Get the workflow execution context to trace back to original input\nconst workflowInput = $items(\"When Executed by Another Workflow\")[0]?.json || {};\nconsole.log(\"Original workflow input:\", JSON.stringify(workflowInput));\n\n// Handle multiple possible data formats\nlet parsedData = {};\nlet transcript = \"\";\nlet originalUrl = \"\";\n\n// Look for data directly in parameters field - this is how the agent passes data to tools\nconst params = $json.parameters || {};\nif (params && Object.keys(params).length > 0) {\n  console.log(\"Found parameters object with keys:\", Object.keys(params));\n  \n  // Extract values from parameters if available\n  if (params.style) parsedData.style = params.style;\n  if (params.videoId) parsedData.videoId = params.videoId;\n  if (params.summary) parsedData.summary = params.summary;\n  if (params.transcript) {\n    parsedData.transcript = params.transcript;\n    transcript = params.transcript;\n  }\n  if (params.prompt) parsedData.prompt = params.prompt;\n  if (params.URL || params.url) {\n    parsedData.URL = params.URL || params.url;\n    originalUrl = params.URL || params.url;\n  }\n}\n\n// Check workflow input for URL if not found yet\nif (!originalUrl && workflowInput.URL) {\n  originalUrl = workflowInput.URL;\n  parsedData.URL = workflowInput.URL;\n}\nif (!originalUrl && workflowInput.url) {\n  originalUrl = workflowInput.url;\n  parsedData.URL = workflowInput.url;\n}\n\n// Check for data in query string if it exists\nif ($json.query && typeof $json.query === 'string') {\n  try {\n    // Attempt to parse as JSON\n    const queryData = JSON.parse($json.query);\n    console.log(\"Successfully parsed query as JSON:\", Object.keys(queryData));\n    \n    // Merge with parsedData, prioritizing existing values\n    Object.keys(queryData).forEach(key => {\n      if (!parsedData[key]) parsedData[key] = queryData[key];\n    });\n    \n    // Special handling for URL\n    if (queryData.URL && !originalUrl) {\n      originalUrl = queryData.URL;\n      parsedData.URL = queryData.URL;\n    }\n  } catch (e) {\n    console.log(\"Query couldn't be parsed as JSON:\", e.message);\n    \n    // If it's not JSON but looks like a summary, store as summary\n    if ($json.query.includes('Learning Objectives') || \n        $json.query.includes('Presentation Structure') || \n        $json.query.includes('CISCO')) {\n      \n      parsedData.summary = $json.query;\n      // Try to determine style from content\n      if (!parsedData.style) {\n        parsedData.style = $json.query.includes('Learning Objectives') ? 'educational' : \n                  $json.query.includes('Presentation Structure') ? 'presentation' : \n                  $json.query.includes('CISCO') ? 'llm_prompt' : 'unknown';\n      }\n    }\n  }\n}\n\n// Look for values in various locations with specific properties\nconst possibleFields = ['style', 'videoId', 'summary', 'transcript', 'prompt', 'URL', 'url'];\nconst possibleContainers = ['output', 'response', 'success', 'result', 'data'];\n\n// Search in top level fields\npossibleFields.forEach(field => {\n  const normalized = field.toLowerCase();\n  if (!parsedData[normalized] && $json[field] !== undefined && $json[field] !== null) {\n    parsedData[normalized] = $json[field];\n    if (normalized === 'transcript') transcript = $json[field];\n    if ((normalized === 'url' || field === 'URL') && !originalUrl) {\n      originalUrl = $json[field];\n      parsedData.URL = $json[field];\n    }\n    console.log(`Found ${field} in top level`);\n  }\n});\n\n// Search in nested containers\npossibleContainers.forEach(container => {\n  if ($json[container] && typeof $json[container] === 'object') {\n    possibleFields.forEach(field => {\n      const normalized = field.toLowerCase();\n      if (!parsedData[normalized] && $json[container][field] !== undefined && $json[container][field] !== null) {\n        parsedData[normalized] = $json[container][field];\n        if (normalized === 'transcript') transcript = $json[container][field];\n        if ((normalized === 'url' || field === 'URL') && !originalUrl) {\n          originalUrl = $json[container][field];\n          parsedData.URL = $json[container][field];\n        }\n        console.log(`Found ${field} in ${container} object`);\n      }\n    });\n  }\n});\n\n// If still no URL, try to construct it from videoId\nif (!originalUrl && parsedData.videoId) {\n  originalUrl = `https://www.youtube.com/watch?v=${parsedData.videoId}`;\n  parsedData.URL = originalUrl;\n  console.log(\"Constructed URL from videoId:\", originalUrl);\n}\n\n// Format the content based on the style type\nfunction formatContent(content, style, videoId, transcriptText) {\n  // Get current date for the document\n  const currentDate = new Date().toISOString().split('T')[0]; // YYYY-MM-DD\n  \n  // Create a document header based on style\n  let formattedContent = '';\n  \n  // Add document title and metadata\n  if (style === 'educational') {\n    formattedContent += `# Educational Summary: YouTube Video ${videoId}\\n`;\n    formattedContent += `*Generated on ${currentDate}*\\n\\n`;\n    formattedContent += `---\\n\\n`;\n  } \n  else if (style === 'llm_prompt') {\n    formattedContent += `# LLM Prompt Conversion: YouTube Video ${videoId}\\n`;\n    formattedContent += `*Generated on ${currentDate}*\\n\\n`;\n    formattedContent += `---\\n\\n`;\n  }\n  else if (style === 'presentation') {\n    formattedContent += `# Presentation Structure Analysis: YouTube Video ${videoId}\\n`;\n    formattedContent += `*Generated on ${currentDate}*\\n\\n`;\n    formattedContent += `---\\n\\n`;\n  }\n  else {\n    formattedContent += `# Summary: YouTube Video ${videoId}\\n`;\n    formattedContent += `*Generated on ${currentDate}*\\n\\n`;\n    formattedContent += `---\\n\\n`;\n  }\n  \n  // Add the main content\n  if (content && content.length > 0) {\n    formattedContent += content;\n  } else {\n    formattedContent += \"No summary content available.\";\n  }\n  \n  // If we have a transcript and it's not already included, add it\n  if (transcriptText && transcriptText.length > 0 && !content.includes(transcriptText)) {\n    formattedContent += \"\\n\\n## Original Transcript\\n\\n```\\n\" + transcriptText + \"\\n```\";\n  }\n  \n  // Add footer\n  formattedContent += `\\n\\n---\\n\\n*This document was automatically generated by the YouTube Summarizer Bot.*`;\n  \n  return formattedContent;\n}\n\n// Set final values with fallbacks\nconst style = parsedData.style || \"unknown\";\nconst videoId = parsedData.videoId || \"unknown\";\nconst summary = parsedData.summary || parsedData.content || \"No summary content available.\";\nconst prompt = parsedData.prompt || null;\nconst url = parsedData.url || parsedData.URL || originalUrl || \"\";\n\n// CRITICAL: Ensure we have a URL for the Update Sheet node\nif (!url) {\n  console.error(\"WARNING: No URL found for Update Sheet node. This will cause an error.\");\n  // Try one more time to construct from videoId\n  if (videoId && videoId !== \"unknown\") {\n    const constructedUrl = `https://www.youtube.com/watch?v=${videoId}`;\n    parsedData.URL = constructedUrl;\n    console.log(\"Emergency URL construction:\", constructedUrl);\n  }\n}\n\n// Log the extracted parameters\nconsole.log(\"Document Parameters:\", {\n  style: style,\n  videoId: videoId,\n  summary_length: summary ? summary.length : 0,\n  transcript_length: transcript ? transcript.length : 0,\n  has_prompt: Boolean(prompt),\n  prompt_length: prompt ? prompt.length : 0,\n  url: url || parsedData.URL || \"MISSING\"\n});\n\n// Create the document title\nlet docTitle;\nconst customTitle = parsedData.title || null;\n\nif (customTitle) {\n  docTitle = customTitle;\n} else {\n  // Default titles based on style\n  if (style === 'educational') {\n    docTitle = `Educational Summary: YouTube ${videoId}`;\n  } \n  else if (style === 'llm_prompt') {\n    docTitle = `LLM Prompt Conversion: YouTube ${videoId}`;\n  }\n  else if (style === 'presentation') {\n    docTitle = `Presentation Structure Analysis: YouTube ${videoId}`;\n  }\n  else {\n    docTitle = `Summary: YouTube ${videoId}`;\n  }\n}\n\n// Format the content for Google Docs\nconst formattedContent = formatContent(summary, style, videoId, transcript);\n\n// Return the parameters for Google Docs creation with ALL available fields\nreturn {\n  json: {\n    title: docTitle,\n    content: formattedContent,\n    mimeType: \"application/vnd.google-apps.document\",\n    // Original input fields preserved - ENSURE URL IS SET\n    style: style,\n    videoId: videoId,\n    prompt: prompt,\n    summary: summary,\n    transcript: transcript,\n    URL: url || parsedData.URL || originalUrl || `https://www.youtube.com/watch?v=${videoId}`,\n    // Add any extra fields we can extract\n    timestamp: new Date().toISOString(),\n    // Include the original source data \n    sourceData: parsedData,\n    // Debug info to track URL\n    _debug: {\n      hadOriginalUrl: !!originalUrl,\n      hadParsedUrl: !!parsedData.URL,\n      finalUrl: url || parsedData.URL || originalUrl,\n      urlSource: originalUrl ? \"original\" : parsedData.URL ? \"parsed\" : \"constructed\"\n    }\n  }\n};"
      },
      "id": "d6bd3030-5ea2-4c49-bc23-2df96102f016",
      "name": "Format Document",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        208,
        0
      ]
    },
    {
      "parameters": {
        "fileContent": "={{ $json.content }}",
        "name": "={{ $json.title }}",
        "resolveData": true,
        "parents": [
          "1iy25hLcBkJ_awr6qp0DDug03RFYWUTb0"
        ],
        "options": {}
      },
      "id": "ed719abb-d8a8-4b5e-b1ed-b8decdff4aba",
      "name": "Create Document",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 2,
      "position": [
        400,
        0
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "1RARvRfDAb07rOHM9FULpcidicQZb4KxuhnTknFAa-cQ",
          "mode": "list",
          "cachedResultName": "vidSummarizer",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1RARvRfDAb07rOHM9FULpcidicQZb4KxuhnTknFAa-cQ/edit?usp=drivesdk"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Sheet1",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1RARvRfDAb07rOHM9FULpcidicQZb4KxuhnTknFAa-cQ/edit#gid=0"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "URL": "={{ $('Format Document').first().json.URL }}",
            "Summary Style": "={{ $('Format Document').first().json.style }}",
            "Document URL": "={{ $json.webViewLink }}",
            "Document ID": "={{ $json.id }}"
          },
          "matchingColumns": [
            "URL",
            "Summary Style"
          ],
          "schema": [
            {
              "id": "URL",
              "displayName": "URL",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Transcript",
              "displayName": "Transcript",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Summary Style",
              "displayName": "Summary Style",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Document URL",
              "displayName": "Document URL",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Document ID",
              "displayName": "Document ID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "id": "41853d08-7437-49c4-bcee-487b1646b959",
      "name": "Update Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        800,
        0
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Get input data\nconst input = $input.item.json;\n\n// Log what we're receiving for debugging\nconsole.log(\"Prepare Response - Raw input:\", input);\n\n// Extract values safely with fallbacks\n// Fix: Use webViewLink as primary source for document URL\nconst style = input.style || \"unknown\";\nconst videoId = input.videoId || input.id || \"unknown\";\nconst docUrl = input.webViewLink || input.alternateLink || input.webContentLink || \"\";\nconst prompt = input.prompt || null;\n\n// Add more debugging to trace what's happening\nconsole.log(\"Prepare Response - Data extracted:\", {\n  input_keys: Object.keys(input),\n  style: style,\n  videoId: videoId,\n  docUrl: docUrl,\n  has_prompt: Boolean(prompt),\n  webViewLink: input.webViewLink, \n  alternateLink: input.alternateLink,\n  webContentLink: input.webContentLink,\n  inputId: input.id\n});\n\n// Create a response based on the summary style\nlet responseMessage = '';\n\n// Check if we have a valid doc URL, if not provide an error message\nif (!docUrl) {\n  console.log(\"WARNING: No document URL found in input\");\n  responseMessage = `I processed your YouTube video (${videoId}), but I couldn't generate a valid document link. Please try again or check the system logs.`;\n} else {\n  if (style === 'educational') {\n    responseMessage = `I've created an Educational Summary for the YouTube video (${videoId}). You can view it here: ${docUrl}`;\n  }\n  else if (style === 'llm_prompt') {\n    responseMessage = `I've created an LLM Prompt Conversion for the YouTube video (${videoId}). You can view the full analysis here: ${docUrl}`;\n    \n    // Add the extracted prompt if available\n    if (prompt) {\n      responseMessage += `\\n\\nHere's the CISCO-formatted prompt that you can copy and paste:\\n\\n${prompt}`;\n    }\n  }\n  else if (style === 'presentation') {\n    responseMessage = `I've created a Presentation Structure Analysis for the YouTube video (${videoId}). You can view it here: ${docUrl}`;\n  }\n  else {\n    responseMessage = `I've created a summary for the YouTube video (${videoId}). You can view it here: ${docUrl}`;\n  }\n}\n\n// Return the final response with all available data for maximum traceability\nreturn {\n  json: {\n    success: Boolean(docUrl),\n    docUrl: docUrl,\n    style: style,\n    videoId: videoId,\n    prompt: prompt,\n    response: responseMessage,\n    output: responseMessage,\n    sourceData: input  // Include the original input for debugging\n  }\n};"
      },
      "id": "41ed58bd-639d-4f0c-80cf-1e95691d054a",
      "name": "Prepare Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1008,
        0
      ]
    }
  ],
  "connections": {
    "When Executed by Another Workflow": {
      "main": [
        [
          {
            "node": "Format Document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Document": {
      "main": [
        [
          {
            "node": "Create Document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Document": {
      "main": [
        [
          {
            "node": "Update Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Sheet": {
      "main": [
        [
          {
            "node": "Prepare Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {},
  "versionId": "9c0c4b16-df7e-49c6-9fe1-6fc19fbefc70",
  "id": "PYCushSh8ivRbKG4",
  "tags": []
}

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

__Create-doc. Uses executeWorkflowTrigger, googleDrive, googleSheets. Event-driven trigger; 5 nodes.

Source: https://github.com/MyAiAd/MyVA-prev/blob/ac00431a79390d6df43def92b40ef2291d7ce0ef/workflows/__Create-doc.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

Intelligent URL Validation - Validates PDF URLs before attempting download, extracting filenames from URLs and generating fallback names when needed, preventing wasted processing time Binary File Hand

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

LINE Image Handler. Uses executeWorkflowTrigger, googleSheets, httpRequest, googleDrive. Event-driven trigger; 17 nodes.

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

PCN. Uses googleSheets, httpRequest, @n-octo-n/n8n-nodes-json-database, itemLists. Event-driven trigger; 60 nodes.

Google Sheets, HTTP Request, @N Octo N/N8N Nodes Json Database +3
Data & Sheets

The workflow automates the process of gathering extensive keyword data for a "Main Keyword." It starts by reading initial parameters from a Google Sheets template, creates a new dedicated Google Sheet

Google Sheets, Google Drive, HTTP Request
Data & Sheets

🔥 March Sale – n8n Community Members Get ideoGener8r for Just $27! (Reg. $47) Use Coupon Code: (Valid until 3/31/2025 for n8n community members)

HTTP Request, Google Drive, Google Sheets