This workflow corresponds to n8n.io template #17763 — we link there as the canonical source.
This workflow follows the Form 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 →
{
"meta": {
"templateCredsSetupCompleted": false
},
"name": "Import CSV or Excel and schedule social posts every 5 hours with PinFlow",
"tags": [],
"nodes": [
{
"id": "befda17c-30da-41c3-924a-ee00461ee83b",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1584,
-176
],
"parameters": {
"width": 480,
"height": 896,
"content": "## Import CSV or Excel and schedule social posts every 5 hours with PinFlow\n\n### How it works\n\nThis workflow lets a user upload a CSV, XLSX, or XLS campaign file and imports its rows for social media scheduling. It validates the rows, spaces scheduled content on a five-hour cadence, then loops through each row to download its image and upload it to PinFlow. For each valid row, it schedules corresponding Pinterest, Instagram, and Facebook posts before moving to the next row.\n\n### Setup steps\n\n- Configure the form trigger so it accepts the campaign spreadsheet as a binary file upload.\n- Ensure the spreadsheet has the columns expected by the validation code, such as image URL, post text/caption, destination account or board fields, links, and any timing-related fields used by the workflow.\n- Set up PinFlow credentials on all PinFlow nodes and verify the selected Pinterest, Instagram, and Facebook destinations are available.\n- Review the extract-from-file nodes to confirm each file format reads the correct sheet, delimiter, headers, and binary property from the upload.\n- Test with a small campaign file first to confirm images download successfully and posts are scheduled at the intended five-hour interval.\n\n### Customization\n\nAdjust the validation code to change required columns, default values, or the five-hour scheduling interval. The platform scheduling chain can also be modified to add, remove, or reorder social networks."
},
"typeVersion": 1
},
{
"id": "8332b61f-2c62-4c10-aa51-850b9eb7e625",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1024,
96
],
"parameters": {
"color": 7,
"width": 432,
"height": 352,
"content": "## Upload and detect file\n\nStarts the workflow from a form upload and routes the submitted campaign file based on whether it is CSV, XLSX, or XLS."
},
"typeVersion": 1
},
{
"id": "eb10d2b2-ace2-46fe-9837-c9c453a05b2e",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-544,
-176
],
"parameters": {
"color": 7,
"width": 240,
"height": 784,
"content": "## Extract spreadsheet rows\n\nParses the uploaded file using the matching extractor for CSV, XLSX, or XLS formats and outputs campaign rows for downstream processing."
},
"typeVersion": 1
},
{
"id": "02cdc1e7-4065-4c15-a1fa-6cc441437b4c",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-272,
96
],
"parameters": {
"color": 7,
"width": 480,
"height": 320,
"content": "## Validate and loop rows\n\nValidates the imported campaign data, calculates the scheduled posting sequence, and iterates through each campaign row one at a time."
},
"typeVersion": 1
},
{
"id": "851ee820-dff9-4078-98d2-2b3a9ab5709b",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
272,
80
],
"parameters": {
"color": 7,
"width": 448,
"height": 336,
"content": "## Prepare PinFlow image\n\nDownloads the image referenced by the current row and uploads it to PinFlow so it can be attached to scheduled social posts. Failed or completed image-handling paths return control to the row loop."
},
"typeVersion": 1
},
{
"id": "36bb30f5-7db9-44bc-9388-d5856bdb2ed6",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
800,
-32
],
"parameters": {
"color": 7,
"width": 672,
"height": 304,
"content": "## Schedule social posts\n\nCreates the scheduled posts in PinFlow across Pinterest, Instagram, and Facebook, then returns to the loop to process the next imported row."
},
"typeVersion": 1
},
{
"id": "2f13cbb2-8fd2-4c24-96b1-4071d2a441cf",
"name": "When Campaign File Uploaded",
"type": "n8n-nodes-base.formTrigger",
"position": [
-980,
260
],
"parameters": {
"options": {
"path": "pinflow-bulk-campaign",
"buttonLabel": "Validate and schedule",
"respondWithOptions": {
"values": {
"respondWith": "text",
"formSubmittedText": "The campaign file was received and is being scheduled."
}
}
},
"formTitle": "PinFlow Bulk Campaign Import",
"formFields": {
"values": [
{
"fieldName": "data",
"fieldType": "file",
"fieldLabel": "Campaign file",
"multipleFiles": false,
"requiredField": true,
"acceptFileTypes": ".csv,.xlsx,.xls"
},
{
"fieldName": "fileFormat",
"fieldType": "dropdown",
"fieldLabel": "File format",
"defaultValue": "CSV",
"fieldOptions": {
"values": [
{
"option": "CSV"
},
{
"option": "Excel XLSX"
},
{
"option": "Excel XLS"
}
]
},
"requiredField": true
},
{
"fieldName": "intervalHours",
"fieldType": "number",
"fieldLabel": "Hours between posts",
"defaultValue": "5",
"requiredField": true
},
{
"fieldName": "firstPublishAt",
"fieldType": "text",
"fieldLabel": "First publish time (optional ISO UTC)",
"placeholder": "2026-08-04T08:00:00Z",
"requiredField": false
}
]
},
"responseMode": "onReceived",
"authentication": "basicAuth",
"formDescription": "Upload a CSV or Excel campaign file. Every row is validated, copied to PinFlow storage, and scheduled across Pinterest, Instagram, and Facebook."
},
"typeVersion": 2.5
},
{
"id": "68acd0db-d39c-4198-9a03-6c1c3c054162",
"name": "Route by File Format",
"type": "n8n-nodes-base.switch",
"position": [
-740,
260
],
"parameters": {
"mode": "expression",
"output": "={{ $json.fileFormat === 'CSV' ? 0 : ($json.fileFormat === 'Excel XLSX' ? 1 : 2) }}",
"numberOutputs": 3
},
"typeVersion": 3.4
},
{
"id": "b66c4eca-d680-4218-9bb9-b357341f31e6",
"name": "Extract CSV Data",
"type": "n8n-nodes-base.extractFromFile",
"position": [
-500,
80
],
"parameters": {
"options": {
"enableBOM": true,
"headerRow": true,
"relaxQuotes": true
},
"operation": "csv",
"binaryPropertyName": "data"
},
"typeVersion": 1.1
},
{
"id": "891b6259-4c15-4295-a058-c433547dad98",
"name": "Extract XLSX Data",
"type": "n8n-nodes-base.extractFromFile",
"position": [
-500,
260
],
"parameters": {
"options": {
"headerRow": true,
"includeEmptyCells": true
},
"operation": "xlsx",
"binaryPropertyName": "data"
},
"typeVersion": 1.1
},
{
"id": "7ce97ed3-e579-4a90-b4b9-af43823fe2e5",
"name": "Extract XLS Data",
"type": "n8n-nodes-base.extractFromFile",
"position": [
-500,
440
],
"parameters": {
"options": {
"headerRow": true,
"includeEmptyCells": true
},
"operation": "xls",
"binaryPropertyName": "data"
},
"typeVersion": 1.1
},
{
"id": "a4a323d3-e214-4ee9-879b-46f50ef3aadd",
"name": "Validate and Schedule Posts",
"type": "n8n-nodes-base.code",
"position": [
-220,
260
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "const rows = $input.all();\nconst form = $('When Campaign File Uploaded').first().json;\nconst intervalHours = Math.min(Math.max(Number(form.intervalHours || 5), 1), 168);\nconst requestedStart = String(form.firstPublishAt || '').trim();\nconst first = requestedStart ? new Date(requestedStart) : new Date(Date.now() + 60 * 60 * 1000);\nif (Number.isNaN(first.getTime())) throw new Error('firstPublishAt must be a valid ISO datetime');\nif (!rows.length) throw new Error('The spreadsheet has no content rows');\nreturn rows.map((item, index) => {\n const row = item.json;\n const imageUrl = String(row.image_url || row.imageUrl || '').trim();\n const title = String(row.title || '').trim();\n const description = String(row.description || row.caption || '').trim();\n const destinationUrl = String(row.destination_url || row.destinationUrl || row.link || '').trim();\n if (!imageUrl) throw new Error(`Row ${index + 2}: image_url is required`);\n if (!/^https:\\/\\//i.test(imageUrl)) throw new Error(`Row ${index + 2}: image_url must use HTTPS`);\n if (!title && !description) throw new Error(`Row ${index + 2}: title or description is required`);\n let scheduledAt;\n if (row.scheduled_at || row.scheduledAt) {\n const explicit = new Date(row.scheduled_at || row.scheduledAt);\n if (Number.isNaN(explicit.getTime())) throw new Error(`Row ${index + 2}: invalid scheduled_at`);\n scheduledAt = explicit.toISOString();\n } else {\n scheduledAt = new Date(first.getTime() + index * intervalHours * 3600000).toISOString();\n }\n return { json: {\n rowNumber: index + 2, imageUrl, title, description, destinationUrl, scheduledAt,\n instagramCaption: String(row.instagram_caption || description || title).trim(),\n facebookMessage: String(row.facebook_message || description || title).trim()\n } };\n});",
"language": "javaScript"
},
"typeVersion": 2
},
{
"id": "bd6a3ed7-ec14-4f25-a159-0fc9f573d597",
"name": "Loop Over Post Rows",
"type": "n8n-nodes-base.splitInBatches",
"position": [
60,
260
],
"parameters": {
"options": {},
"batchSize": 1
},
"typeVersion": 3
},
{
"id": "a392c49b-2562-4f76-9df5-cecbdbfe9aa7",
"name": "Fetch Post Image",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"maxTries": 3,
"position": [
320,
260
],
"parameters": {
"url": "={{ $json.imageUrl }}",
"options": {
"timeout": 60000,
"response": {
"response": {
"responseFormat": "file",
"outputPropertyName": "data"
}
}
}
},
"retryOnFail": true,
"typeVersion": 4.4,
"waitBetweenTries": 2000
},
{
"id": "da1a8136-ea26-4c8e-87ef-16db695953fd",
"name": "Upload Image to PinFlow",
"type": "n8n-nodes-pinflow-pinterest.pinFlow",
"onError": "continueErrorOutput",
"maxTries": 3,
"position": [
580,
260
],
"parameters": {
"resource": "media",
"operation": "uploadImage",
"binaryPropertyName": "data"
},
"retryOnFail": true,
"typeVersion": 1,
"waitBetweenTries": 2000
},
{
"id": "e46efb57-d235-4a97-b319-3a90be708257",
"name": "Schedule Pinterest Pin",
"type": "n8n-nodes-pinflow-pinterest.pinFlow",
"onError": "continueRegularOutput",
"maxTries": 3,
"position": [
840,
100
],
"parameters": {
"link": "={{ $('Validate and Schedule Posts').item.json.destinationUrl }}",
"title": "={{ $('Validate and Schedule Posts').item.json.title }}",
"boardId": "",
"mediaUrl": "={{ $json.url }}",
"resource": "pin",
"operation": "schedule",
"description": "={{ $('Validate and Schedule Posts').item.json.description }}",
"scheduledAt": "={{ $('Validate and Schedule Posts').item.json.scheduledAt }}",
"pinCreateMode": "scheduled",
"pinterestProfileId": ""
},
"retryOnFail": true,
"typeVersion": 1,
"waitBetweenTries": 2000
},
{
"id": "9bcf665a-d570-4404-8c18-6ce3e5f0f2c7",
"name": "Schedule Instagram Post",
"type": "n8n-nodes-pinflow-pinterest.pinFlow",
"onError": "continueRegularOutput",
"maxTries": 3,
"position": [
1080,
100
],
"parameters": {
"resource": "instagramPost",
"operation": "create",
"instagramCaption": "={{ $('Validate and Schedule Posts').item.json.instagramCaption }}",
"instagramMediaUrl": "={{ $('Upload Image to PinFlow').item.json.url }}",
"instagramMediaType": "image",
"instagramProfileId": "",
"instagramScheduledAt": "={{ $('Validate and Schedule Posts').item.json.scheduledAt }}",
"instagramCreateStatus": "scheduled"
},
"retryOnFail": true,
"typeVersion": 1,
"waitBetweenTries": 2000
},
{
"id": "159f7529-8818-46a5-9eec-c446338c9ee8",
"name": "Schedule Facebook Post",
"type": "n8n-nodes-pinflow-pinterest.pinFlow",
"onError": "continueRegularOutput",
"maxTries": 3,
"position": [
1320,
100
],
"parameters": {
"resource": "facebookPost",
"operation": "create",
"facebookPageId": "",
"facebookMessage": "={{ $('Validate and Schedule Posts').item.json.facebookMessage }}",
"facebookMediaUrl": "={{ $('Upload Image to PinFlow').item.json.url }}",
"facebookMediaType": "image",
"facebookScheduledAt": "={{ $('Validate and Schedule Posts').item.json.scheduledAt }}",
"facebookCreateStatus": "scheduled"
},
"retryOnFail": true,
"typeVersion": 1,
"waitBetweenTries": 2000
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "f3a0a150-e447-45cc-b1b7-a861e5f0c00d",
"connections": {
"Extract CSV Data": {
"main": [
[
{
"node": "Validate and Schedule Posts",
"type": "main",
"index": 0
}
]
]
},
"Extract XLS Data": {
"main": [
[
{
"node": "Validate and Schedule Posts",
"type": "main",
"index": 0
}
]
]
},
"Fetch Post Image": {
"main": [
[
{
"node": "Upload Image to PinFlow",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop Over Post Rows",
"type": "main",
"index": 0
}
]
]
},
"Extract XLSX Data": {
"main": [
[
{
"node": "Validate and Schedule Posts",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Post Rows": {
"main": [
[],
[
{
"node": "Fetch Post Image",
"type": "main",
"index": 0
}
]
]
},
"Route by File Format": {
"main": [
[
{
"node": "Extract CSV Data",
"type": "main",
"index": 0
}
],
[
{
"node": "Extract XLSX Data",
"type": "main",
"index": 0
}
],
[
{
"node": "Extract XLS Data",
"type": "main",
"index": 0
}
]
]
},
"Schedule Facebook Post": {
"main": [
[
{
"node": "Loop Over Post Rows",
"type": "main",
"index": 0
}
]
]
},
"Schedule Pinterest Pin": {
"main": [
[
{
"node": "Schedule Instagram Post",
"type": "main",
"index": 0
}
]
]
},
"Schedule Instagram Post": {
"main": [
[
{
"node": "Schedule Facebook Post",
"type": "main",
"index": 0
}
]
]
},
"Upload Image to PinFlow": {
"main": [
[
{
"node": "Schedule Pinterest Pin",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop Over Post Rows",
"type": "main",
"index": 0
}
]
]
},
"Validate and Schedule Posts": {
"main": [
[
{
"node": "Loop Over Post Rows",
"type": "main",
"index": 0
}
]
]
},
"When Campaign File Uploaded": {
"main": [
[
{
"node": "Route by File Format",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow collects a CSV/XLSX/XLS campaign file via an n8n form, validates each row, then uploads images to PinFlow and schedules posts for Pinterest, Instagram, and Facebook at a defined interval (default 5 hours). Receives a file upload through an authenticated n8n form…
Source: https://n8n.io/workflows/17763/ — 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 is designed to take user inputs in order to generate an image using the Riverflow 2.0 model through the Replicate API. It can handle both image generation as well as image editing. Addit
Product Marketing Pack Generator (UAE). Uses formTrigger, httpRequest, googleDrive, gmail. Event-driven trigger; 24 nodes.
[n8n] YouTube Channel Advanced RSS Feeds Generator. Uses formTrigger, httpRequest, stopAndError. Event-driven trigger; 21 nodes.
Sales teams looking to build targeted company lead lists
Formtrigger Workflow. Uses formTrigger, httpRequest, bannerbear, discord. Event-driven trigger; 16 nodes.