{
  "id": "s55hiq5G5KtKtCmL",
  "name": "Scheduled Social Post Packages",
  "tags": [],
  "nodes": [
    {
      "id": "5b20fc7a-e8e9-444d-85d6-6438a161cb67",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -16,
        80
      ],
      "parameters": {
        "width": 480,
        "height": 896,
        "content": "## Scheduled Social Post Packages\n\n### How it works\n\nThis workflow collects details for a scheduled social post package through a form, validates the submitted inputs, and stops early with an error message when required data is missing or invalid. For valid submissions, it chooses a Google Drive destination, checks whether a matching package already exists, and either notifies the user about the duplicate or creates a new folder. It then prepares and uploads the package files, showing a success message when complete or cleaning up the partial folder and reporting an upload error if something fails.\n\n### Setup steps\n\n- Configure the form trigger fields for the post package details the workflow requires, such as package name, schedule information, captions, assets, or destination metadata.\n- Connect Google Drive credentials for the nodes that search, create folders, upload files, and remove incomplete packages.\n- Set the correct parentFolderId in the Set Destination Folder node, or update its expression to choose a folder dynamically.\n- Review the validation and file-preparation code nodes so their required fields, naming conventions, and generated files match your social post package format.\n- Customize the user-facing form response nodes for input errors, duplicate package notices, successful creation, and upload failures.\n\n### Customization\n\nCommon customization points include the validation rules, Google Drive parent folder selection, duplicate-detection naming logic, generated package file structure, and the wording of each form response."
      },
      "typeVersion": 1
    },
    {
      "id": "c0657e70-e1be-485b-8243-6b4e92c5d83f",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        528,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 896,
        "height": 576,
        "content": "## Collect and validate inputs\n\nStarts the workflow from a form submission, validates the package details, branches based on validity, and displays an input error for invalid submissions."
      },
      "typeVersion": 1
    },
    {
      "id": "de9b63e8-3047-4953-8308-4f26366cfad3",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1472,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 720,
        "height": 352,
        "content": "## Check destination duplicates\n\nSets the target Google Drive parent folder, searches for an existing package, and decides whether the submission is a duplicate."
      },
      "typeVersion": 1
    },
    {
      "id": "eb105169-2abe-4738-bfbe-bd5e75822195",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2240,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 496,
        "height": 560,
        "content": "## Handle duplicate or create\n\nHandles the duplicate-check branch by either showing a duplicate notice or creating the new package folder in Google Drive."
      },
      "typeVersion": 1
    },
    {
      "id": "afb45b5b-0884-47d2-bbd7-e40fdc2969e7",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2784,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 496,
        "height": 352,
        "content": "## Prepare and upload files\n\nBuilds the package file payloads and uploads them into the newly created Google Drive folder."
      },
      "typeVersion": 1
    },
    {
      "id": "d3e46d2a-0c2e-4dd1-b3da-83cb8bd0b429",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3328,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 352,
        "content": "## Show creation success\n\nDisplays the final confirmation when all package files upload successfully."
      },
      "typeVersion": 1
    },
    {
      "id": "2eda94a1-9d89-48b7-aa78-c9304dda9796",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3328,
        496
      ],
      "parameters": {
        "color": 7,
        "width": 496,
        "height": 352,
        "content": "## Clean failed upload\n\nRemoves the incomplete Google Drive package after an upload problem and shows the user an upload error message."
      },
      "typeVersion": 1
    },
    {
      "id": "7c5ef41b-fc07-41d4-b67e-85cb0a486e80",
      "name": "When Post Details Collected",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        608,
        240
      ],
      "parameters": {
        "options": {
          "path": "social-post-package",
          "buttonLabel": "Create package",
          "appendAttribution": false
        },
        "formTitle": "Create a Scheduled Social Post Package",
        "formFields": {
          "values": [
            {
              "fieldName": "scheduled_date",
              "fieldType": "date",
              "fieldLabel": "Scheduled date",
              "requiredField": true
            },
            {
              "fieldName": "scheduled_time",
              "fieldLabel": "Scheduled time (24h HH:MM)",
              "placeholder": "e.g. 18:00",
              "requiredField": true
            },
            {
              "fieldName": "post_text",
              "fieldType": "textarea",
              "fieldLabel": "Post text",
              "requiredField": true
            },
            {
              "fieldName": "folder_slug",
              "fieldLabel": "Package label (optional)",
              "placeholder": "e.g. sakura-river"
            },
            {
              "fieldName": "images",
              "fieldType": "file",
              "fieldLabel": "Images (1\u20134; PNG/JPG)",
              "requiredField": true,
              "acceptFileTypes": ".png,.jpg,.jpeg"
            }
          ]
        },
        "responseMode": "lastNode",
        "formDescription": "Choose a date and time, add your post text, and upload 1\u20134 PNG/JPG images. The workflow creates a tidy Google Drive package containing tweet.txt and numbered images."
      },
      "typeVersion": 2.6
    },
    {
      "id": "c382f885-ca7b-4a77-a1d8-43886143b4b3",
      "name": "Validate Inputs with Code",
      "type": "n8n-nodes-base.code",
      "position": [
        848,
        240
      ],
      "parameters": {
        "jsCode": "const j = $json;\nconst binary = $input.first().binary || {};\nconst errors = [];\n\nconst dateStr = (j.scheduled_date || '').toString().trim();\nconst timeStr = (j.scheduled_time || '').toString().trim();\nconst caption = (j.post_text || '').toString();\nconst slugRaw = (j.folder_slug || '').toString().trim();\n\n// Date: strict YYYY-MM-DD and a real calendar date.\nconst dm = dateStr.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\nif (!dm) {\n  errors.push('Invalid scheduled date (use YYYY-MM-DD).');\n} else {\n  const year = Number(dm[1]);\n  const month = Number(dm[2]);\n  const day = Number(dm[3]);\n  const parsed = new Date(Date.UTC(year, month - 1, day));\n  const isRealDate =\n    parsed.getUTCFullYear() === year &&\n    parsed.getUTCMonth() === month - 1 &&\n    parsed.getUTCDate() === day;\n\n  if (!isRealDate) {\n    errors.push('Invalid scheduled date (choose a real calendar date).');\n  }\n}\n\n// Time: HH:MM, 24-hour.\nconst tm = timeStr.match(/^([01]?\\d|2[0-3]):([0-5]\\d)$/);\nif (!tm) {\n  errors.push('Invalid scheduled time (use 24-hour HH:MM, e.g. 18:00).');\n}\n\n// Post text must not be empty.\nif (!caption.trim()) {\n  errors.push('Post text is empty.');\n}\n\n// Images: 1\u20134 PNG/JPG files.\nconst keys = Object.keys(binary);\nconst allowedExt = ['png', 'jpg', 'jpeg'];\n\nif (keys.length === 0) {\n  errors.push('At least one image is required.');\n}\nif (keys.length > 4) {\n  errors.push('No more than four images are allowed.');\n}\n\nfor (const k of keys) {\n  const b = binary[k] || {};\n  const ext = (b.fileExtension || (b.fileName || '').split('.').pop() || '').toLowerCase();\n  const mime = (b.mimeType || '').toLowerCase();\n  const okExt = allowedExt.includes(ext);\n  const okMime = mime === 'image/png' || mime === 'image/jpeg' || mime === 'image/jpg';\n\n  if (!okExt && !okMime) {\n    errors.push('Unsupported file type: ' + (b.fileName || k) + ' (only PNG/JPG allowed).');\n  }\n}\n\n// Canonical package folder: YYYY-MM-DD_HHMM[__label]\nconst hhmm = tm ? (tm[1].padStart(2, '0') + tm[2]) : '0000';\nconst slug = slugRaw\n  .toLowerCase()\n  .replace(/[^a-z0-9]+/g, '-')\n  .replace(/^-+|-+$/g, '');\n\nlet folderName = dateStr + '_' + hhmm;\nif (slug) folderName += '__' + slug;\n\nreturn [{\n  json: {\n    valid: errors.length === 0,\n    errorMessage: errors.join(' '),\n    folderName,\n    caption,\n    slug,\n  },\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "4e239762-b2e8-45dd-97b7-8a9fdff911c0",
      "name": "If Inputs Valid",
      "type": "n8n-nodes-base.if",
      "position": [
        1088,
        240
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "valid",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.valid }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "20e89c46-2d18-4783-920f-81400cf2d530",
      "name": "Search for Package in Drive",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        1776,
        240
      ],
      "parameters": {
        "filter": {
          "folderId": {
            "__rl": true,
            "mode": "id",
            "value": "={{ $('Set Google Drive Folder ID').first().json.parentFolderId }}"
          },
          "whatToSearch": "folders"
        },
        "options": {
          "fields": [
            "id",
            "name"
          ]
        },
        "resource": "fileFolder",
        "returnAll": true,
        "queryString": "={{ $('Validate Inputs with Code').first().json.folderName }}"
      },
      "typeVersion": 3,
      "alwaysOutputData": true
    },
    {
      "id": "f2b596d1-ca0d-4315-9817-9b881b9179a7",
      "name": "Check Duplicate Package",
      "type": "n8n-nodes-base.if",
      "position": [
        2016,
        240
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "exists",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $('Search for Package in Drive').all().filter(f => f.json.name === $('Validate Inputs with Code').first().json.folderName).length > 0 }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "51e3a389-b567-45cd-8496-77dfe891b78a",
      "name": "Display Duplicate Message",
      "type": "n8n-nodes-base.form",
      "position": [
        2432,
        240
      ],
      "parameters": {
        "options": {},
        "operation": "completion",
        "completionTitle": "Nothing was created",
        "completionMessage": "=A package named \"{{ $('Validate Inputs with Code').first().json.folderName }}\" already exists in the destination folder. Nothing new was created."
      },
      "typeVersion": 2.5
    },
    {
      "id": "c47f80b7-ba1d-4927-b33c-7ddb3fc7de4b",
      "name": "Create Folder in Google Drive",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        2432,
        464
      ],
      "parameters": {
        "name": "={{ $('Validate Inputs with Code').first().json.folderName }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Google Drive Folder ID').first().json.parentFolderId }}"
        },
        "resource": "folder"
      },
      "typeVersion": 3
    },
    {
      "id": "49c790a5-d035-4c1e-9067-6b8195565eee",
      "name": "Prepare Files with Code",
      "type": "n8n-nodes-base.code",
      "position": [
        2864,
        240
      ],
      "parameters": {
        "jsCode": "\nconst folderId = $('Create Folder in Google Drive').first().json.id;\nconst caption = $('Validate Inputs with Code').first().json.caption;\nconst trigger = $('When Post Details Collected').first();\nconst binary = trigger.binary || {};\n\nconst items = [];\n\n// tweet.txt from the caption\nconst txtBinary = await this.helpers.prepareBinaryData(\n  Buffer.from(caption, 'utf8'),\n  'tweet.txt',\n  'text/plain',\n);\nitems.push({ json: { fileName: 'tweet.txt', folderId }, binary: { data: txtBinary } });\n\n// images -> 01.<ext>, 02.<ext> ...\nconst keys = Object.keys(binary).sort();\nlet i = 0;\nfor (const k of keys) {\n  i++;\n  const b = binary[k];\n  const ext = (b.fileExtension || (b.fileName || '').split('.').pop() || 'png').toLowerCase();\n  const idx = String(i).padStart(2, '0');\n  items.push({ json: { fileName: idx + '.' + ext, folderId }, binary: { data: b } });\n}\n\nreturn items;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "17f2f8b1-32ce-43ee-9553-357ed6b4aee2",
      "name": "Upload Files to Google Drive",
      "type": "n8n-nodes-base.googleDrive",
      "onError": "continueErrorOutput",
      "position": [
        3104,
        240
      ],
      "parameters": {
        "name": "={{ $json.fileName }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.folderId }}"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "5863bc59-f00e-4699-bbf6-d7e1512edf5f",
      "name": "Display Package Creation Notice",
      "type": "n8n-nodes-base.form",
      "position": [
        3424,
        240
      ],
      "parameters": {
        "options": {},
        "operation": "completion",
        "completionTitle": "Package created",
        "completionMessage": "=Created \"{{ $('Validate Inputs with Code').first().json.folderName }}\" with tweet.txt and your numbered images in Google Drive."
      },
      "typeVersion": 2.5
    },
    {
      "id": "4fa8030e-2387-4b45-9856-544a9fe3bfa0",
      "name": "Delete Incomplete Package",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        3408,
        656
      ],
      "parameters": {
        "options": {
          "deletePermanently": false
        },
        "resource": "folder",
        "operation": "deleteFolder",
        "folderNoRootId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Create Folder in Google Drive').first().json.id }}"
        }
      },
      "executeOnce": true,
      "typeVersion": 3
    },
    {
      "id": "5b63f140-339d-458a-9b53-703cc4394aea",
      "name": "Display Upload Error Form",
      "type": "n8n-nodes-base.form",
      "position": [
        3648,
        656
      ],
      "parameters": {
        "options": {},
        "operation": "completion",
        "completionTitle": "Package creation failed",
        "completionMessage": "An upload failed, so the incomplete folder was removed. Nothing was left behind. Please try again."
      },
      "typeVersion": 2.5
    },
    {
      "id": "57b89aa2-334c-405b-bb30-96ddd4b3c394",
      "name": "Display Input Error Form",
      "type": "n8n-nodes-base.form",
      "position": [
        1264,
        464
      ],
      "parameters": {
        "options": {},
        "operation": "completion",
        "completionTitle": "Nothing was created",
        "completionMessage": "=Please fix the following and submit again: {{ $json.errorMessage }}"
      },
      "typeVersion": 2.5
    },
    {
      "id": "ece5c33e-80a4-449a-9c04-eb1a99fee5e9",
      "name": "Set Google Drive Folder ID",
      "type": "n8n-nodes-base.set",
      "position": [
        1536,
        240
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1d86d3a9-3852-489a-ba50-ca866a147bff",
              "name": "parentFolderId",
              "type": "string",
              "value": "PASTE_GOOGLE_DRIVE_PARENT_FOLDER_ID_HERE"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "f7e3405b-e7eb-4a58-a6d5-18faefc26d6e",
  "nodeGroups": [],
  "connections": {
    "If Inputs Valid": {
      "main": [
        [
          {
            "node": "Set Google Drive Folder ID",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Display Input Error Form",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Duplicate Package": {
      "main": [
        [
          {
            "node": "Display Duplicate Message",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create Folder in Google Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Files with Code": {
      "main": [
        [
          {
            "node": "Upload Files to Google Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete Incomplete Package": {
      "main": [
        [
          {
            "node": "Display Upload Error Form",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Inputs with Code": {
      "main": [
        [
          {
            "node": "If Inputs Valid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Google Drive Folder ID": {
      "main": [
        [
          {
            "node": "Search for Package in Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search for Package in Drive": {
      "main": [
        [
          {
            "node": "Check Duplicate Package",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Post Details Collected": {
      "main": [
        [
          {
            "node": "Validate Inputs with Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Files to Google Drive": {
      "main": [
        [
          {
            "node": "Display Package Creation Notice",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Delete Incomplete Package",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Folder in Google Drive": {
      "main": [
        [
          {
            "node": "Prepare Files with Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}