{
  "name": "Sync Facebook events to Google Calendar with Apify",
  "nodes": [
    {
      "id": "e703fb7e-ddcc-4cab-879a-f4e2a1903c60",
      "name": "Sticky Note 71c2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -820,
        -990
      ],
      "parameters": {
        "content": "## Sync Facebook events to Google Calendar with Apify\n\n### Who's it for\nAnyone who maintains a \"what's on\" calendar and is tired of updating it by hand: city guides, local\nnewsletters, venue and community managers, or someone who just wants a scene in their own calendar.\n\n### How it works\nEvery morning the workflow searches Facebook for public events matching your query or page URL.\nIt creates or refreshes one Google Calendar entry per event. Recurring events are expanded, so a\nweekly quiz appears on every one of its dates rather than only the next one. Events with no start time are\nskipped because they cannot be scheduled, and a missing end time defaults to two hours.\nAn event needs a Facebook ID to prevent duplicates. Cancelled events that still appear are marked CANCELLED;\nitems no longer returned by a search are not removed from your calendar.\n\n### How to set up\n1. Add an Apify credential. Your API token is in the Apify Console under Settings → Integrations.\n2. Add a Google Calendar credential and choose the target calendar.\n3. Open **Settings** and set `searchQuery` or `startUrl` (a Facebook page events URL), plus `maxEvents`.\nClear `searchQuery` when using only `startUrl`.\n4. Change the schedule if daily is not what you want.\n\n### Requirements\nAn Apify account and a Google account. No Facebook account, cookies or extension — the scraper reads\nonly what Facebook shows a signed-out visitor.\n\n### How to customize\nSeveral narrow queries collect more than one broad one, because results are bounded per query: run\n\"jazz Prague\" and \"comedy Prague\" rather than \"Prague\". Swap the Google Calendar node for Notion,\nAirtable or a database if you would rather store than schedule.\n\nCost: $3 per 1,000 events on Apify Free, proxies included, no start fee. A daily search may\nreturn the same events again; each returned event is charged again, even when its calendar entry exists.\n",
        "width": 900,
        "height": 950
      }
    },
    {
      "id": "bef41e95-b685-4e31-be52-af482a596299",
      "name": "Sticky Note 1043",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -700,
        200
      ],
      "parameters": {
        "content": "## 1. Configure\nSet a search query or page events URL, plus the maximum number of events. Clear the other field if using only one.",
        "width": 420,
        "height": 170,
        "color": 7
      }
    },
    {
      "id": "9b5ad538-00b3-4437-89bf-7fe1886da5e5",
      "name": "Sticky Note 5735",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -260,
        200
      ],
      "parameters": {
        "content": "## 2. Scrape\nRuns the Facebook Events Scraper on Apify and reads the results out of the run's dataset.",
        "width": 420,
        "height": 148,
        "color": 7
      }
    },
    {
      "id": "2cb739a3-fedf-4d41-ba3f-a0669cbc5002",
      "name": "Sticky Note b18d",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        220,
        200
      ],
      "parameters": {
        "content": "## 3. Expand and sync\nRecurring events become one entry per date. A stable event ID refreshes that entry on the next daily run.",
        "width": 420,
        "height": 148,
        "color": 7
      }
    },
    {
      "id": "901dda5e-b7bf-48d9-86ff-219c7a8f4b13",
      "name": "Every morning",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -620,
        0
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "triggerAtHour": 6
            }
          ]
        }
      }
    },
    {
      "id": "95d03583-0355-418b-99e2-854be0433337",
      "name": "Settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -400,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "e7dc4871-4814-4bc8-9415-4030637a5e50",
              "name": "searchQuery",
              "value": "comedy New York",
              "type": "string"
            },
            {
              "id": "5ee15daf-e012-49a4-9552-a4bacda57681",
              "name": "maxEvents",
              "value": "={{ 50 }}",
              "type": "number"
            },
            {
              "id": "44a9dc11-8cee-44a6-ab85-a9ee1b920882",
              "name": "startUrl",
              "value": "",
              "type": "string"
            }
          ]
        }
      }
    },
    {
      "id": "5a57980b-d147-4dea-ba98-d95bacc0ebd1",
      "name": "Scrape Facebook events",
      "type": "@apify/n8n-nodes-apify.apify",
      "typeVersion": 1,
      "position": [
        -180,
        0
      ],
      "parameters": {
        "actorId": {
          "__rl": true,
          "mode": "list",
          "value": "y5HaZ3g9DfQ8Ko71a",
          "cachedResultUrl": "https://console.apify.com/actors/y5HaZ3g9DfQ8Ko71a/input",
          "cachedResultName": "Facebook Events Scraper (alfalfa/facebook-events-scraper)"
        },
        "operation": "Run actor",
        "timeout": {},
        "customBody": "={{ JSON.stringify({ searchQueries: $json.searchQuery ? [$json.searchQuery] : [], startUrls: $json.startUrl ? [$json.startUrl] : [], maxEvents: $json.maxEvents }) }}"
      }
    },
    {
      "id": "0cfe4e87-126a-47fb-a9fb-cab8d8da5d7b",
      "name": "Get event results",
      "type": "@apify/n8n-nodes-apify.apify",
      "typeVersion": 1,
      "position": [
        40,
        0
      ],
      "parameters": {
        "resource": "Datasets",
        "operation": "Get items",
        "datasetId": "={{ $json.defaultDatasetId }}",
        "limit": {}
      }
    },
    {
      "id": "88f79693-ba52-43a3-b3c7-ad9e6ad3931c",
      "name": "Expand recurring dates",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        260,
        0
      ],
      "parameters": {
        "jsCode": "// Use Facebook event IDs (including IDs of recurring dates) as Google Calendar IDs.\n// Google's event ID alphabet is 0-9 and a-v; \"fb\" + a numeric Facebook ID is valid.\nconst out = [];\nconst seen = new Set();\nconst facebookId = (id, url) => {\n  if (/^\\d+$/.test(String(id ?? ''))) return String(id);\n  return String(url ?? '').match(/\\/events\\/(\\d+)/)?.[1] ?? null;\n};\nfor (const { json: e } of $input.all()) {\n  const dates = (Array.isArray(e.childEvents) && e.childEvents.length)\n    ? e.childEvents\n    : [e];\n  for (const d of dates) {\n    const id = facebookId(d.id, d.url) || (d === e ? facebookId(e.id, e.url) : null);\n    const startMs = Date.parse(d.utcStartDate);\n    if (!id || !Number.isFinite(startMs)) continue;\n    const calendarId = `fb${id}`;\n    if (seen.has(calendarId)) continue;\n    seen.add(calendarId);\n    const endMs = Date.parse(d.utcEndDate);\n    const end = Number.isFinite(endMs) && endMs > startMs\n      ? d.utcEndDate : new Date(startMs + 2 * 3600 * 1000).toISOString();\n    const cancelled = e.isCanceled || d.isCanceled;\n    out.push({ json: {\n      calendarId,\n      summary: cancelled ? `CANCELLED: ${e.name || 'Facebook event'}` : (e.name || 'Facebook event'),\n      start: d.utcStartDate,\n      end,\n      location: e.address || e.location?.name || '',\n      description: [e.description, d.url || e.url].filter(Boolean).join('\\n\\n'),\n    }});\n  }\n}\nreturn out;\n"
      }
    },
    {
      "id": "e97bb85c-9fe8-4879-9730-a101680ef61e",
      "name": "Create calendar event",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.3,
      "position": [
        480,
        0
      ],
      "parameters": {
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "primary",
          "cachedResultName": "Primary"
        },
        "start": "={{ $json.start }}",
        "end": "={{ $json.end }}",
        "additionalFields": {
          "summary": "={{ $json.summary }}",
          "location": "={{ $json.location }}",
          "description": "={{ $json.description }}",
          "id": "={{ $json.calendarId }}"
        }
      },
      "continueOnFail": true
    },
    {
      "id": "32e2f686-d996-46f9-aa2d-a84c20c04139",
      "name": "Refresh calendar event",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.3,
      "position": [
        700,
        0
      ],
      "parameters": {
        "resource": "event",
        "operation": "update",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "primary",
          "cachedResultName": "Primary"
        },
        "eventId": "={{ $('Expand recurring dates').item.json.calendarId }}",
        "updateFields": {
          "start": "={{ $('Expand recurring dates').item.json.start }}",
          "end": "={{ $('Expand recurring dates').item.json.end }}",
          "summary": "={{ $('Expand recurring dates').item.json.summary }}",
          "location": "={{ $('Expand recurring dates').item.json.location }}",
          "description": "={{ $('Expand recurring dates').item.json.description }}"
        }
      }
    }
  ],
  "connections": {
    "Every morning": {
      "main": [
        [
          {
            "node": "Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings": {
      "main": [
        [
          {
            "node": "Scrape Facebook events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Facebook events": {
      "main": [
        [
          {
            "node": "Get event results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get event results": {
      "main": [
        [
          {
            "node": "Expand recurring dates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Expand recurring dates": {
      "main": [
        [
          {
            "node": "Create calendar event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create calendar event": {
      "main": [
        [
          {
            "node": "Refresh calendar event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  }
}
