This workflow follows the Google Drive → Google Sheets 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 →
{
"name": "Shotstack Pending Row Video Automation Pipeline",
"nodes": [
{
"parameters": {
"content": "## \u2699\ufe0f Setup Required \u2014 Read Before Activating\n\n**How this workflow works** \u2014 This version does not use the Google Sheets Row Added trigger. It runs on a schedule, reads the sheet, selects the first row whose `status` cell is blank, marks it as `processing`, renders the video, uploads it to Drive, then updates the same row.\n\n**1. Sheet columns** \u2014 Your Google Sheet must have these exact header columns:\n- `video_id`\n- `title`\n- `image_url`\n- `script`\n- `status`\n- `video_url`\n\n**2. Pending rows** \u2014 A row is considered pending when `status` is blank. The workflow processes one pending row per scheduled run. To retry a row, clear its `status` cell. Keep `video_id` unique for every row.\n\n**3. Google Sheet URL** \u2014 Replace `YOUR_GOOGLE_SHEET_URL` in:\n- `Get Rows from Sheet`\n- `Mark Row Processing`\n- `Update Sheet (Success)`\n- `Update Sheet (Failed)`\n\n**4. Sheet tab** \u2014 Replace `YOUR_SHEET_NAME` with the visible tab name at the bottom of your Google Sheet, for example `Sheet1`.\n\n**5. Matching column** \u2014 All Google Sheets update nodes use `video_id` as the matching column for **Append or Update Row**. Your sheet header must be exactly `video_id`. If n8n refreshes the sheet schema after you select your own sheet, confirm that **Column to Match On / Matching Columns** is still set to `video_id`.\n\n**6. Shotstack Template ID** \u2014 In `Render Video`, replace `YOUR_SHOTSTACK_TEMPLATE_ID` in the body expression with your real Shotstack template ID.\n\n**7. Google Drive folder** \u2014 In `Upload to Google Drive`, replace `YOUR_GOOGLE_DRIVE_FOLDER_ID` or select the Drive folder where finished videos should be saved. Replace `YOUR_GOOGLE_DRIVE_FOLDER_NAME` with the folder's display name if editing the JSON directly.\n\n**8. Credentials** \u2014 Configure credentials in each node:\n- **Google Sheets OAuth2** \u2192 `Get Rows from Sheet`, `Mark Row Processing`, `Update Sheet (Success)`, and `Update Sheet (Failed)`\n- **Google Drive OAuth2** \u2192 `Upload to Google Drive`\n- **Header Auth (Shotstack)** \u2192 `Render Video`\n - Name: `x-api-key`\n - Value: your Shotstack Stage API key\n\n**9. Webhook callback** \u2014 `Wait for Webhook` is configured to resume on a **POST** request and respond immediately with HTTP `200`. Do not edit the callback URL manually; n8n generates it at runtime using `$execution.resumeUrl`, and the `Render Video` node sends it to Shotstack as the `CALLBACK_URL` merge field.\n\n**10. Activate the workflow** \u2014 Manual runs process one pending row immediately. When active, the Schedule Trigger checks the sheet every minute. Adjust the interval if needed.\n\n**11. Go live** \u2014 When ready for production, in `Render Video`, change `/edit/stage/` to `/edit/v1/` and swap in your Production API key.\n",
"height": 900,
"width": 620,
"color": 3
},
"id": "766f9681-d2f6-420e-923b-66258e189ad4",
"name": "Setup Notes",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1260,
360
]
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 1
}
]
}
},
"id": "3ffd353c-7ebf-4ac8-9a17-9b56eacebdc1",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-1260,
80
]
},
{
"parameters": {
"operation": "read",
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_URL",
"mode": "url"
},
"sheetName": {
"__rl": true,
"value": "YOUR_SHEET_NAME",
"mode": "name",
"cachedResultName": "YOUR_SHEET_NAME"
},
"options": {
"dataLocationOnSheet": {
"values": {
"rangeDefinition": "detectAutomatically"
}
}
}
},
"id": "30be788c-a7fe-4c5a-b1bd-a9fa36c9b442",
"name": "Get Rows from Sheet",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-1040,
80
]
},
{
"parameters": {
"jsCode": "const rows = $input.all();\n\nconst pending = rows.find((item) => {\n const status = String(item.json.status ?? '').trim().toLowerCase();\n return status === '';\n});\n\nif (!pending) {\n // Nothing to process on this run.\n return [];\n}\n\nconst row = pending.json;\nconst required = ['video_id', 'title', 'image_url', 'script'];\nfor (const field of required) {\n if (!String(row[field] ?? '').trim()) {\n throw new Error(`Pending row is missing required field: ${field}`);\n }\n}\n\nreturn [\n {\n json: {\n video_id: String(row.video_id).trim(),\n title: String(row.title).trim(),\n image_url: String(row.image_url).trim(),\n script: String(row.script).trim(),\n status: String(row.status ?? '').trim(),\n video_url: String(row.video_url ?? '').trim(),\n },\n },\n];\n"
},
"id": "5b43c17b-8507-40e0-b97c-9bd6586a37d9",
"name": "Select Next Pending Row",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-820,
80
]
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_URL",
"mode": "url"
},
"sheetName": {
"__rl": true,
"value": "YOUR_SHEET_NAME",
"mode": "name",
"cachedResultName": "YOUR_SHEET_NAME"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"video_id": "={{ $json.video_id }}",
"status": "processing",
"video_url": "={{ $json.video_url || '' }}"
},
"matchingColumns": [
"video_id"
],
"schema": [
{
"id": "video_id",
"displayName": "video_id",
"required": false,
"defaultMatch": true,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "status",
"displayName": "status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "video_url",
"displayName": "video_url",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"id": "52a039aa-c829-4175-b217-e0f507256adb",
"name": "Mark Row Processing",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-600,
80
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.shotstack.io/edit/stage/templates/render",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify({'id':'YOUR_SHOTSTACK_TEMPLATE_ID','merge':[{'find':'TITLE','replace':$node['Select Next Pending Row'].json.title},{'find':'IMAGE','replace':$node['Select Next Pending Row'].json.image_url},{'find':'VOICEOVER','replace':$node['Select Next Pending Row'].json.script},{'find':'CALLBACK_URL','replace':$execution.resumeUrl + ($execution.resumeUrl.includes('?') ? '&' : '?') + 'video_id=' + encodeURIComponent($node['Select Next Pending Row'].json.video_id)}]}) }}",
"options": {}
},
"id": "e2fd750d-5d58-4bf3-a1d4-2f470d1aa5e1",
"name": "Render Video",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-380,
80
]
},
{
"parameters": {
"resume": "webhook",
"httpMethod": "POST",
"responseMode": "onReceived",
"responseCode": 200,
"options": {}
},
"id": "49b34558-26c5-432c-af5c-e15fd0dd41d4",
"name": "Wait for Webhook",
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
-160,
80
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "013d4104-77fd-40aa-bd6e-6dc577566d9a",
"name": "video_id",
"value": "={{ $json.query?.video_id || $json.body?.query?.video_id || $node['Select Next Pending Row'].json.video_id }}",
"type": "string"
},
{
"id": "79bce67e-e35b-45da-a795-d49752b77091",
"name": "status",
"value": "={{ $json.status || $json.body?.status }}",
"type": "string"
},
{
"id": "1fc3fc50-3409-4adb-9e84-85fb62d2d873",
"name": "url",
"value": "={{ $json.url || $json.body?.url }}",
"type": "string"
},
{
"id": "4a240545-8451-44bf-8ddb-3d9b16aef937",
"name": "error",
"value": "={{ $json.error || $json.body?.error }}",
"type": "string"
}
]
},
"options": {}
},
"id": "1cc2ff20-3538-45fb-bc08-c199efa5ff40",
"name": "Normalize Callback",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
60,
80
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"id": "111f0797-1429-4973-99e1-86fe681b6e82",
"leftValue": "={{ $json.status }}",
"rightValue": "done",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "1319385b-5135-4f11-b5b4-3c03be77efe1",
"name": "Check Render Status",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
280,
80
]
},
{
"parameters": {
"url": "={{ $json.url }}",
"options": {
"response": {
"response": {
"responseFormat": "file"
}
}
}
},
"id": "d02ab86e-6f2e-40ec-bda2-4b064a0b6d45",
"name": "Download Video",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
500,
-40
]
},
{
"parameters": {
"name": "={{ $('Normalize Callback').item.json.video_id + '.mp4' }}",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"folderId": {
"__rl": true,
"value": "YOUR_GOOGLE_DRIVE_FOLDER_ID",
"mode": "id",
"cachedResultName": "YOUR_GOOGLE_DRIVE_FOLDER_NAME"
},
"options": {}
},
"id": "ad378b8a-c334-467d-904d-a8521ebf54fc",
"name": "Upload to Google Drive",
"type": "n8n-nodes-base.googleDrive",
"typeVersion": 3,
"position": [
720,
-40
]
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_URL",
"mode": "url"
},
"sheetName": {
"__rl": true,
"value": "YOUR_SHEET_NAME",
"mode": "name",
"cachedResultName": "YOUR_SHEET_NAME"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"video_id": "={{ $('Normalize Callback').item.json.video_id }}",
"status": "done",
"video_url": "={{ $json.webViewLink || $json.webContentLink || ('https://drive.google.com/file/d/' + $json.id + '/view') }}"
},
"matchingColumns": [
"video_id"
],
"schema": [
{
"id": "video_id",
"displayName": "video_id",
"required": false,
"defaultMatch": true,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "status",
"displayName": "status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "video_url",
"displayName": "video_url",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"id": "ba934ffd-b341-42fe-b03b-391ccf3a4732",
"name": "Update Sheet (Success)",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
940,
-40
]
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_URL",
"mode": "url"
},
"sheetName": {
"__rl": true,
"value": "YOUR_SHEET_NAME",
"mode": "name",
"cachedResultName": "YOUR_SHEET_NAME"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"video_id": "={{ $json.video_id }}",
"status": "failed"
},
"matchingColumns": [
"video_id"
],
"schema": [
{
"id": "video_id",
"displayName": "video_id",
"required": false,
"defaultMatch": true,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "status",
"displayName": "status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"id": "bd8b49e0-d812-4bcb-bde8-17c428e1983b",
"name": "Update Sheet (Failed)",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
500,
220
]
}
],
"connections": {
"Schedule Trigger": {
"main": [
[
{
"node": "Get Rows from Sheet",
"type": "main",
"index": 0
}
]
]
},
"Get Rows from Sheet": {
"main": [
[
{
"node": "Select Next Pending Row",
"type": "main",
"index": 0
}
]
]
},
"Select Next Pending Row": {
"main": [
[
{
"node": "Mark Row Processing",
"type": "main",
"index": 0
}
]
]
},
"Mark Row Processing": {
"main": [
[
{
"node": "Render Video",
"type": "main",
"index": 0
}
]
]
},
"Render Video": {
"main": [
[
{
"node": "Wait for Webhook",
"type": "main",
"index": 0
}
]
]
},
"Wait for Webhook": {
"main": [
[
{
"node": "Normalize Callback",
"type": "main",
"index": 0
}
]
]
},
"Normalize Callback": {
"main": [
[
{
"node": "Check Render Status",
"type": "main",
"index": 0
}
]
]
},
"Check Render Status": {
"main": [
[
{
"node": "Download Video",
"type": "main",
"index": 0
}
],
[
{
"node": "Update Sheet (Failed)",
"type": "main",
"index": 0
}
]
]
},
"Download Video": {
"main": [
[
{
"node": "Upload to Google Drive",
"type": "main",
"index": 0
}
]
]
},
"Upload to Google Drive": {
"main": [
[
{
"node": "Update Sheet (Success)",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"meta": {
"templateCredsSetupCompleted": false
},
"nodeGroups": [],
"tags": []
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Shotstack Pending Row Video Automation Pipeline. Uses googleSheets, httpRequest, googleDrive. Scheduled trigger; 13 nodes.
Source: https://gist.github.com/echessa/e3f3f78e7220432539a2bf2710d06aff — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow monitors Google Calendar for events indicating that a customer will visit the company today or the next day, retrieves the required details, and sends reminder notifications to the relev
LefBot — V1 Master Pipeline (11 Feb). Uses googleSheets, httpRequest, googleDrive. Scheduled trigger; 37 nodes.
LefBot V1 Master Pipeline 11Feb. Uses googleSheets, httpRequest, googleDrive. Scheduled trigger; 37 nodes.
LefBot — V1 Master Pipeline (11 Feb v2). Uses googleSheets, httpRequest, googleDrive. Scheduled trigger; 37 nodes.
Googledrive Workflow. Uses googleDrive, httpRequest, googleSheets, linear. Scheduled trigger; 34 nodes.