{
  "nodes": [
    {
      "id": "0a08bdf1-87cf-4fa3-8d5a-a4a24ba3f965",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3120,
        -352
      ],
      "parameters": {
        "width": 432,
        "height": 944,
        "content": "## Facebook Post Scheduler with Approval Workflow\n\nThis workflow automates Facebook post scheduling from a Google\nSheets content calendar. It runs 4 times daily, reads approved\nposts scheduled for today, downloads images from Google Drive\n(if needed), schedules them via Facebook Graph API, and updates\nthe tracking sheet with published post URLs. Supports both\ntext-only posts and photo posts, with built-in approval workflow\nand post type filtering. Perfect for social media managers and\nagencies managing content calendars in Google Sheets.\n\n## How it works\n1. Runs daily at 9:35 AM, 10:35 AM, 11:35 AM, 12:35 PM.\n2. Loads Facebook Page ID and Access Token from credentials sheet.\n3. Reads posts with Approval Status = \"Good\" and Platform = \"Facebook\".\n4. Filters posts where Scheduled On date matches today.\n5. Loops through each post, skipping Story posts (not supported).\n6. If text-only post \u2192 schedules via /feed endpoint.\n7. If photo post \u2192 downloads image from Drive, schedules via /photos.\n8. Updates Google Sheet with published post URL.\n\n## Setup steps\n1. Create Google Sheet with: Scheduled On, Platform, Post Type,\n   Caption, Media URL, Approval Status, Post URL columns.\n2. Add .env sheet with Facebook Page ID and Page Access Token.\n3. Connect Google Sheets OAuth credentials.\n4. Connect Google Drive OAuth credentials.\n5. Update sheet URLs in all Google Sheets nodes.\n6. Activate workflow and test with sample posts."
      },
      "typeVersion": 1
    },
    {
      "id": "223e68a9-67a7-4fd9-affd-ddfba4bae584",
      "name": "Run Daily at Multiple Times",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -2656,
        16
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "35 9-12 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "9d86b344-9231-4a28-9304-462111414c2a",
      "name": "Load Facebook Credentials from Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -2416,
        16
      ],
      "parameters": {
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "=YOUR_SHEET_URL"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "4c3cf1a7-d9a0-4fa7-afc3-e8ee67bfa839",
      "name": "Read Approved Facebook Posts",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -2176,
        16
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "Good",
              "lookupColumn": "Approval Status"
            },
            {
              "lookupValue": "Facebook",
              "lookupColumn": "Platform"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 98565607,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1rpCJPyjTumUaJ3oH71cHxIq2m3jY8RQB0YCsZGaxMKg/edit#gid=98565607",
          "cachedResultName": "Post URL"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/1rpCJPyjTumUaJ3oH71cHxIq2m3jY8RQB0YCsZGaxMKg/edit?gid=98565607#gid=98565607"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "0513ca6c-0c83-4538-9c2b-0287f35c658d",
      "name": "Filter Posts Scheduled for Today",
      "type": "n8n-nodes-base.code",
      "position": [
        -1920,
        16
      ],
      "parameters": {
        "jsCode": "// Get today's date (just the date part, no time)\nconst today = new Date();\nconst todayDateString = today.toISOString().split('T')[0]; // Format: 2025-10-30\n\n// Filter items where Scheduled On matches today\nconst filteredItems = [];\n\nfor (const item of $input.all()) {\n  // Try different possible field names\n  const scheduledOn = item.json['Scheduled On'] || item.json['scheduled_on'] || item.json.scheduledOn;\n  \n  console.log(\"Scheduled On value:\", scheduledOn);\n  console.log(\"Type:\", typeof scheduledOn);\n  \n  if (scheduledOn) {\n    // Extract just the date part from the scheduled value\n    let scheduledDateString = '';\n    \n    if (typeof scheduledOn === 'string') {\n      // Extract YYYY-MM-DD from string like \"2025-10-30 10:00\" or \"2025-10-30 06-42\"\n      const dateMatch = scheduledOn.match(/(\\d{4}-\\d{2}-\\d{2})/);\n      if (dateMatch) {\n        scheduledDateString = dateMatch[1];\n      }\n    } else if (scheduledOn instanceof Date) {\n      scheduledDateString = scheduledOn.toISOString().split('T')[0];\n    }\n    \n    console.log(\"Extracted date:\", scheduledDateString);\n    console.log(\"Matches today?\", scheduledDateString === todayDateString);\n    \n    // If dates match, keep this item\n    if (scheduledDateString === todayDateString) {\n      filteredItems.push(item);\n    }\n  }\n}\n\nreturn filteredItems;"
      },
      "typeVersion": 2
    },
    {
      "id": "89021e3c-e877-4916-8d6b-6fd8187a1344",
      "name": "Loop Through Each Post",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -1680,
        16
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "2da9e213-0fc3-4928-9a94-39ebb3501360",
      "name": "Check if Platform is Facebook",
      "type": "n8n-nodes-base.switch",
      "position": [
        -1424,
        32
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "0176f6c6-1cc4-4dba-ace4-deda87fcb577",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.Platform }}",
                    "rightValue": "Facebook"
                  }
                ]
              }
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.3
    },
    {
      "id": "e5230dbb-8b74-4fcd-9650-28a798a9a919",
      "name": "Check if Not Story Post",
      "type": "n8n-nodes-base.if",
      "position": [
        -1168,
        32
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "fd5bc57a-2eb3-47f0-9169-404727aee6a3",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json['Post Type'] }}",
              "rightValue": "Story"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "c8e820dc-a9e1-4cd2-96d8-241cce67934b",
      "name": "Check if Text-Only or Photo Post",
      "type": "n8n-nodes-base.if",
      "position": [
        -912,
        16
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "70f67a63-3429-4f00-927c-3f20e02d19c6",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              },
              "leftValue": "={{ $json['Media URL'] }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "f8674e39-ea9f-4b72-872d-9548c42b998d",
      "name": "Download Image from Google Drive",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -704,
        32
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "url",
          "value": "={{ $json['Media URL'] }}"
        },
        "options": {},
        "operation": "download"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "18db8516-03cc-4bc2-bc3d-94cf0ddcc0c2",
      "name": "Schedule Facebook Photo Post",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -496,
        32
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v24.0/{{ $node['Load Facebook Credentials from Sheet'].json['Facebook Page ID'] }}/photos",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "name": "access_token",
              "value": "={{ $node['Load Facebook Credentials from Sheet'].json['Facebook Page Access Token'] }}"
            },
            {
              "name": "caption",
              "value": "={{ $json.Caption }}"
            },
            {
              "name": "source",
              "parameterType": "formBinaryData",
              "inputDataFieldName": "data"
            },
            {
              "name": "published",
              "value": "false"
            },
            {
              "name": "scheduled_publish_time",
              "value": "={{ Math.floor((new Date($json['Scheduled On'].replace(' ', 'T').replace(/-(\\d{2})$/, ':$1')).getTime() + 15 * 60 * 1000) / 1000) }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "7686a72b-9489-4a99-8b78-a85d227a94ec",
      "name": "Update Sheet with Photo Post URL",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -288,
        32
      ],
      "parameters": {
        "columns": {
          "value": {
            "Post URL": "=https://www.facebook.com/photo/?fbid={{ $json.id }}",
            "row_number": "={{ $('Check if Platform is Facebook').item.json.row_number }}",
            "Approval Status": "Published"
          },
          "schema": [
            {
              "id": "Scheduled On",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Scheduled On",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Platform",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Platform",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Post Type",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Post Type",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Caption",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Caption",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Media URL",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Media URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Approval Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Approval Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Notes: ADD SYBAL LOGO TO EACH IMAGE",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Notes: ADD SYBAL LOGO TO EACH IMAGE",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Post URL",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Post URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "row_number"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 98565607,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1rpCJPyjTumUaJ3oH71cHxIq2m3jY8RQB0YCsZGaxMKg/edit#gid=98565607",
          "cachedResultName": "Post URL"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "YOUR_SHEET_URL"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "9a6c6d99-40d1-4caa-a4eb-fe989dca6a44",
      "name": "Schedule Facebook Text Post",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -80,
        0
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v24.0/{{ $node['Load Facebook Credentials from Sheet'].json['Facebook Page ID'] }}/feed",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "message",
              "value": "={{ $json.Caption }}"
            },
            {
              "name": "access_token",
              "value": "={{ $node['Load Facebook Credentials from Sheet'].json['Facebook Page Access Token'] }}"
            },
            {
              "name": "published",
              "value": "false"
            },
            {
              "name": "scheduled_publish_time",
              "value": "={{ Math.floor(new Date($json['Scheduled On'].replace(/(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2})-(\\d{2})/, '$1-$2-$3T$4:$5:00-05:00')).getTime() / 1000) }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "7e19ffae-802a-4b9d-bb6c-a0878f44bd9e",
      "name": "Update Sheet with Text Post URL",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        112,
        0
      ],
      "parameters": {
        "columns": {
          "value": {
            "Post URL": "={{ \"https://www.facebook.com/\" + $json.id.split('_')[0] + \"/posts/\" + $json.id.split('_')[1] }}",
            "row_number": "={{ $('Check if Platform is Facebook').item.json.row_number }}",
            "Approval Status": "Published"
          },
          "schema": [
            {
              "id": "Scheduled On",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Scheduled On",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Platform",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Platform",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Post Type",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Post Type",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Caption",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Caption",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Media URL",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Media URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Approval Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Approval Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Notes: ADD SYBAL LOGO TO EACH IMAGE",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Notes: ADD SYBAL LOGO TO EACH IMAGE",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Post URL",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Post URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "row_number"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 98565607,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1rpCJPyjTumUaJ3oH71cHxIq2m3jY8RQB0YCsZGaxMKg/edit#gid=98565607",
          "cachedResultName": "Post URL"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "=YOUR_SHEET_URL"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "68a2dafd-1bfc-4abc-acfb-89f38702381f",
      "name": "Merge All Post Types",
      "type": "n8n-nodes-base.merge",
      "position": [
        432,
        16
      ],
      "parameters": {
        "numberInputs": 3
      },
      "typeVersion": 3.2
    },
    {
      "id": "e434a18c-2ba6-430a-affc-1b18edf8377a",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2640,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 208,
        "content": "## Schedule & Credentials\n\nTriggers 4x daily and loads Facebook\ncredentials from Google Sheets for\nAPI authentication."
      },
      "typeVersion": 1
    },
    {
      "id": "1af64505-4ca4-4a8a-b03f-821b9be1e38c",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2176,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 592,
        "height": 208,
        "content": "## Data Loading & Filtering\n\nReads approved Facebook posts from sheet,\nfilters by today's date, and loops through\neach post for processing."
      },
      "typeVersion": 1
    },
    {
      "id": "35b50cbb-69f5-49cc-a9a8-19b0dd335401",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1424,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 240,
        "content": "## Post Type Routing\n\nChecks platform and post type, then routes\nto appropriate publishing flow (text-only\nvs photo, skips Stories)."
      },
      "typeVersion": 1
    },
    {
      "id": "08e48517-9758-474e-ba11-40cce53f5988",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -704,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 224,
        "content": "## Photo Post Publishing\n\nDownloads image from Google Drive, schedules\nFacebook photo post via Graph API, updates\nsheet with published photo URL."
      },
      "typeVersion": 1
    },
    {
      "id": "8e44a18d-5ea9-4223-b71a-a1c49862884d",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -112,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 224,
        "content": "## Text Post Publishing\n\nSchedules Facebook text-only post via\nGraph API, updates sheet with published\npost URL, merges with other branches."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Merge All Post Types": {
      "main": [
        [
          {
            "node": "Loop Through Each Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Through Each Post": {
      "main": [
        [],
        [
          {
            "node": "Check if Platform is Facebook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if Not Story Post": {
      "main": [
        [
          {
            "node": "Check if Text-Only or Photo Post",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge All Post Types",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Run Daily at Multiple Times": {
      "main": [
        [
          {
            "node": "Load Facebook Credentials from Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Facebook Text Post": {
      "main": [
        [
          {
            "node": "Update Sheet with Text Post URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Approved Facebook Posts": {
      "main": [
        [
          {
            "node": "Filter Posts Scheduled for Today",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Facebook Photo Post": {
      "main": [
        [
          {
            "node": "Update Sheet with Photo Post URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if Platform is Facebook": {
      "main": [
        [
          {
            "node": "Check if Not Story Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Sheet with Text Post URL": {
      "main": [
        [
          {
            "node": "Merge All Post Types",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if Text-Only or Photo Post": {
      "main": [
        [
          {
            "node": "Schedule Facebook Text Post",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Download Image from Google Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Image from Google Drive": {
      "main": [
        [
          {
            "node": "Schedule Facebook Photo Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Posts Scheduled for Today": {
      "main": [
        [
          {
            "node": "Loop Through Each Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Sheet with Photo Post URL": {
      "main": [
        [
          {
            "node": "Merge All Post Types",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Load Facebook Credentials from Sheet": {
      "main": [
        [
          {
            "node": "Read Approved Facebook Posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}