{
  "name": "Sync workflow schedules between Google Sheets and Google Calendar",
  "nodes": [
    {
      "id": "9ae22e9d-0b4b-476c-9138-819eda9e0635",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1728,
        512
      ]
    },
    {
      "id": "3dcf261e-1442-470a-b828-8feb7ec668dd",
      "name": "Code: parsing",
      "type": "n8n-nodes-base.code",
      "position": [
        -1152,
        512
      ]
    },
    {
      "id": "807c9b17-1158-4da4-be3e-8de4751009d6",
      "name": "Create an event",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        1360,
        512
      ]
    },
    {
      "id": "f977bd11-3bc3-48f4-bba2-66531a6a5a90",
      "name": "Code: RRULE",
      "type": "n8n-nodes-base.code",
      "position": [
        1040,
        512
      ]
    },
    {
      "id": "7766b246-1694-43f4-8640-884dacb379e1",
      "name": "Delete an event",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        -1136,
        1040
      ]
    },
    {
      "id": "eada40f6-f268-434b-b3f2-2233bc0ae636",
      "name": "Get many events2",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        -1424,
        1040
      ]
    },
    {
      "id": "b91a529d-f08e-4b17-86f5-a3972baff2ab",
      "name": "Remove Duplicates",
      "type": "n8n-nodes-base.removeDuplicates",
      "position": [
        720,
        512
      ]
    },
    {
      "id": "f4a38c2c-da29-47bb-a0b2-3bb2d5b5f396",
      "name": "Sheets:Lookup-ExistOnCalendar",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -528,
        512
      ]
    },
    {
      "id": "5c8aae74-3d10-4432-bf1c-371fd65f1718",
      "name": "Sheets:OnCalendar=YES",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2016,
        512
      ]
    },
    {
      "id": "3618a0a7-5b23-4f5a-94a5-9c57999af221",
      "name": "Get many workflows",
      "type": "n8n-nodes-base.n8n",
      "position": [
        -1472,
        512
      ]
    },
    {
      "id": "78b8189a-02c8-4598-b3ef-279a0765fe7c",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1744,
        1040
      ]
    },
    {
      "id": "7b47f7cf-a72b-44ce-a30e-4ce2695a6de9",
      "name": "Code: detect changes",
      "type": "n8n-nodes-base.code",
      "position": [
        112,
        512
      ]
    },
    {
      "id": "d6caddd9-77d7-42ee-b9c3-ac6ed24a6bdb",
      "name": "Switch",
      "type": "n8n-nodes-base.switch",
      "position": [
        416,
        496
      ]
    },
    {
      "id": "98859d16-bf8b-46ad-b2a1-739c1e7e304d",
      "name": "Code: save current values",
      "type": "n8n-nodes-base.code",
      "position": [
        -848,
        512
      ]
    },
    {
      "id": "3d5e5933-7d47-4a01-816e-d6197025821f",
      "name": "Code manual merge",
      "type": "n8n-nodes-base.code",
      "position": [
        -208,
        512
      ]
    },
    {
      "id": "ef89bad9-1015-4fc3-8bcc-6ae0755255e3",
      "name": "Code: post-create",
      "type": "n8n-nodes-base.code",
      "position": [
        1696,
        512
      ]
    },
    {
      "id": "d57f09f3-25bb-4b9a-8e98-9374cb7be397",
      "name": "Code: split eventIds for delete",
      "type": "n8n-nodes-base.code",
      "position": [
        768,
        1040
      ]
    },
    {
      "id": "f22523dc-c631-4daa-8bd1-40a3de59d8b3",
      "name": "Delete old event",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        1056,
        1040
      ]
    },
    {
      "id": "ea296d83-c150-4241-81a9-228c5b3f2db5",
      "name": "__title",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1824,
        48
      ],
      "parameters": {
        "width": 4104,
        "height": 80,
        "content": "## 🕒 n8n – Workflow Scheduling Extraction\nScans all active n8n workflows every **30 min** · syncs schedules as recurring events on Google Calendar `YOUR_GOOGLE_ACCOUNT@gmail.com` · state store: Google"
      }
    },
    {
      "id": "f4b3437c-a404-460a-aa80-abfbfbe3e6c3",
      "name": "__sec_fetch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1824,
        144
      ],
      "parameters": {
        "width": 584,
        "height": 544,
        "content": "### A · Fetch\n\nRetrieves all n8n workflows via the n8n REST API. Both active and inactive workflows are fetched; filtering to scheduleTrigger-only happens in **Code: parsing**."
      }
    },
    {
      "id": "e5a7b6f2-3ea5-45f1-98d0-1947f109854e",
      "name": "__sec_parse",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1216,
        144
      ],
      "parameters": {
        "width": 908,
        "height": 544,
        "content": "### B · Parse & Lookup\n\nNormalises each workflow's trigger configuration into a human-readable schedule string. Then reads the current Sheets state (schedule, On Calendar, Calendar_EventID) for every "
      }
    },
    {
      "id": "9f5e07d5-b3f7-4535-bcb0-18282622262a",
      "name": "__sec_detect",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -272,
        144
      ],
      "parameters": {
        "width": 880,
        "height": 544,
        "content": "### C · Change Detection\n\nMerges live workflow data with Sheets state. Compares `current_schedule` (live) vs `schedule` (Sheets) and assigns an **action**: `create` (new) · `update` (changed) · `skip`"
      }
    },
    {
      "id": "4d76bdd7-e4b0-4322-a8b9-e41596259524",
      "name": "__sec_create",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        144
      ],
      "parameters": {
        "width": 1640,
        "height": 528,
        "content": "### D · Create / Update Path\n\nBuilds a Google Calendar recurring event payload (RRULE) and creates the event. On **update** this branch runs in parallel with the Delete branch (E). After creation, wri"
      }
    },
    {
      "id": "297bdd17-8445-4781-99ed-814945170bdb",
      "name": "__sec_delete",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        704
      ],
      "parameters": {
        "width": 700,
        "height": 496,
        "content": "### E · Delete Old Event\n\nRuns in parallel with branch D on **update**. Deletes the previous Calendar event(s) identified by Calendar_EventID from Sheets. `continueOnFail = true` — HTTP 410 (already g"
      }
    },
    {
      "id": "3f1980e5-cc38-4362-9a4f-4f7295193e77",
      "name": "__sec_webhook",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1824,
        736
      ],
      "parameters": {
        "width": 940,
        "height": 470,
        "content": "### F · Webhook Sub-flow  *(disconnected – manual trigger)*\n\nStandalone maintenance endpoint. Accepts an HTTP POST with event IDs, fetches the matching Calendar events, and hard-deletes them. Not conn"
      }
    },
    {
      "id": "eec1eecc-2a8f-4cfa-9ea9-e49372c00922",
      "name": "__node_schedule_trigger",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1808,
        256
      ],
      "parameters": {
        "width": 260,
        "height": 380,
        "content": "**Schedule Trigger**\n\nFires every 30 min (cron `*/30 * * * *`).\nAlso supports manual execution from the n8n UI.\nEntry point for the entire sync pipeline."
      }
    },
    {
      "id": "07b02e91-7f2b-496a-b2db-ec4d36833299",
      "name": "__node_get_many_workflows",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1536,
        256
      ],
      "parameters": {
        "width": 260,
        "height": 412,
        "content": "**Get many workflows**\n\nCalls `GET /api/v1/workflows` on the local n8n instance.\nReturns all workflows (active + inactive).\nNo filter applied here — filtering is done in Code: parsing."
      }
    },
    {
      "id": "d6c8e7a1-7073-4074-8c61-6e3b3fff0e79",
      "name": "__node_code_parsing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1216,
        256
      ],
      "parameters": {
        "width": 260,
        "height": 412,
        "content": "**Code: parsing**\n\n- Filters to workflows that have a `scheduleTrigger` node\n- Excludes system / internal workflows by name\n- Normalises trigger config → human schedule string\n- Extracts: WorkflowID, "
      }
    },
    {
      "id": "abe5abb3-7c89-4f77-b7a9-23d51ef51daf",
      "name": "__node_code_save",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -912,
        256
      ],
      "parameters": {
        "width": 260,
        "height": 412,
        "content": "**Code: save current values**\n\nSnapshots all parsed fields into `current_*` prefixed copies\n(e.g. `current_schedule`, `current_freq`, …).\nPreserves the live state before it is overwritten by the Sheet"
      }
    },
    {
      "id": "e7c98db5-8183-4a2d-aa4d-a95f0f6be0d4",
      "name": "__node_sheets_lookup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -592,
        256
      ],
      "parameters": {
        "width": 260,
        "height": 412,
        "content": "**Sheets: Lookup ExistOnCalendar**\n\nReads every row in the \"n8n Scheduling\" tab of the spreadsheet.\nReturns per-workflow: `schedule`, `On Calendar`, `Calendar_EventID`, `calendarLastSync`.\nUses a Goog"
      }
    },
    {
      "id": "f3b6e523-8268-4cd6-9a86-fab7a0b3b663",
      "name": "__node_code_merge",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -256,
        256
      ],
      "parameters": {
        "width": 260,
        "height": 412,
        "content": "**Code manual merge**\n\nJoins live workflow items with Sheets rows on WorkflowID.\nPropagates from Sheets: `schedule`, `On Calendar`, `Calendar_EventID`.\n`current_*` fields are kept intact from Code: sa"
      }
    },
    {
      "id": "b2edab53-4877-4dae-9f95-a7a144be4b41",
      "name": "__node_code_detect",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        48,
        256
      ],
      "parameters": {
        "width": 260,
        "height": 396,
        "content": "**Code: detect changes**\n\nCompares `current_schedule` (live) vs `schedule` (Sheets).\nUses only the human string — not granular fields — to avoid false positives from stale rows.\n- Empty saved schedule"
      }
    },
    {
      "id": "0135024e-1f68-4378-a436-fbe7e4c755bc",
      "name": "__node_switch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        336,
        256
      ],
      "parameters": {
        "width": 260,
        "height": 412,
        "content": "**Switch**\n\nRoutes items by the `action` field:\n- `out0` → **create**  (new workflow, no Calendar event yet)\n- `out1` → **update**  (schedule changed; two parallel branches: D + E)\n- `out2` → **skip**"
      }
    },
    {
      "id": "b0fc2d09-58c9-4093-9587-c04b19c302c1",
      "name": "__node_remove_dupes",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        656,
        256
      ],
      "parameters": {
        "width": 260,
        "height": 380,
        "content": "**Remove Duplicates**\n\nDeduplicates by WorkflowID before event creation.\nDefensive node: prevents accidental double-creation if a workflow\nappears multiple times in the current batch (e.g. after a man"
      }
    },
    {
      "id": "30577841-cce6-48e4-b9c8-9064856eeac5",
      "name": "__node_code_rrule",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        960,
        256
      ],
      "parameters": {
        "width": 260,
        "height": 380,
        "content": "**Code: RRULE**\n\nConverts `current_schedule` string → Google Calendar event payload with RRULE recurrence.\nSupported types: daily · weekly (with or without specific days) · monthly · hourly.\nHourly → "
      }
    },
    {
      "id": "afb8e9c5-a0be-4c7c-9649-77d97d68a22f",
      "name": "__node_create_event",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1280,
        256
      ],
      "parameters": {
        "width": 260,
        "height": 396,
        "content": "**Create an event**\n\nGoogle Calendar node. Creates a recurring event on `YOUR_GOOGLE_ACCOUNT@gmail.com`.\nUses `repeatFrequency` + `repeatUntil = null` for infinite recurrence.\nReturns the full GCal ev"
      }
    },
    {
      "id": "8009e9f3-59ef-47aa-af81-00344237954b",
      "name": "__node_code_post_create",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1616,
        256
      ],
      "parameters": {
        "width": 260,
        "height": 412,
        "content": "**Code: post-create**\n\nAggregates created event IDs by WorkflowID (handles batch creates).\nAdds `On Calendar = YES`, `Calendar_EventID`, `calendarLastSync` (ISO timestamp).\nPrepares the exact row stru"
      }
    },
    {
      "id": "0da373d3-2a4d-4fed-9d54-4541e73c0899",
      "name": "__node_sheets_yes",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1952,
        256
      ],
      "parameters": {
        "width": 260,
        "height": 412,
        "content": "**Sheets: OnCalendar=YES**\n\n`appendOrUpdate` operation matching on the **WorkflowID** column.\nWrites all schedule fields + `On Calendar=YES` + `Calendar_EventID` + `calendarLastSync`.\nService Account "
      }
    },
    {
      "id": "d693564a-7a09-4880-bf53-e760331b118d",
      "name": "__node_code_split",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        688,
        816
      ],
      "parameters": {
        "width": 260,
        "height": 366,
        "content": "**Code: split eventIds for delete**\n\nSplits the `Calendar_EventID` field (comma-separated string) into one item per ID.\nRequired because a workflow can accumulate multiple event IDs across runs.\nFeeds"
      }
    },
    {
      "id": "57e6e365-7fd8-40b5-b948-a20a635dd9c0",
      "name": "__node_delete_old",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        992,
        816
      ],
      "parameters": {
        "width": 260,
        "height": 382,
        "content": "**Delete old event**\n\nDeletes the previous Calendar event(s) when a schedule update occurs.\n`continueOnFail = true` — HTTP 410 \"Resource has been deleted\" is tolerated and does not halt execution.\nTer"
      }
    },
    {
      "id": "32cdfa17-1447-4dc3-9e38-0e42dd21fadb",
      "name": "__node_webhook",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1808,
        848
      ],
      "parameters": {
        "width": 260,
        "height": 350,
        "content": "**Webhook**\n\nHTTP POST trigger for manual Calendar cleanup.\nNot connected to the main 30-min pipeline.\nAccepts event IDs in the request body."
      }
    },
    {
      "id": "09035062-7137-45c7-a4b4-d3ffca205ba2",
      "name": "__node_get_events2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1504,
        848
      ],
      "parameters": {
        "width": 260,
        "height": 350,
        "content": "**Get many events2**\n\nRetrieves Calendar events by ID or search query.\nFeeds the event list into Delete an event."
      }
    },
    {
      "id": "0b136e75-9d51-4280-a375-08b2ac28dbae",
      "name": "__node_delete_event",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1184,
        848
      ],
      "parameters": {
        "width": 260,
        "height": 350,
        "content": "**Delete an event**\n\nHard-deletes Calendar events by ID.\nUsed only in the manual webhook-triggered maintenance flow."
      }
    },
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "name": "__overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2544,
        48
      ],
      "parameters": {
        "width": 680,
        "height": 1288,
        "content": "## n8n Workflow Scheduling Extraction\n\nReads all n8n workflows every 30 min via REST API, extracts their schedules, compares against a Google Sheets state store, and syncs Google Calendar with recurri"
      }
    }
  ],
  "connections": {
    "Switch": {
      "main": [
        [
          {
            "node": "Remove Duplicates",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Remove Duplicates",
            "type": "main",
            "index": 0
          },
          {
            "node": "Code: split eventIds for delete",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "Get many events2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: RRULE": {
      "main": [
        [
          {
            "node": "Create an event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: parsing": {
      "main": [
        [
          {
            "node": "Code: save current values",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create an event": {
      "main": [
        [
          {
            "node": "Code: post-create",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get many events2": {
      "main": [
        [
          {
            "node": "Delete an event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Get many workflows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code manual merge": {
      "main": [
        [
          {
            "node": "Code: detect changes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: post-create": {
      "main": [
        [
          {
            "node": "Sheets:OnCalendar=YES",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Duplicates": {
      "main": [
        [
          {
            "node": "Code: RRULE",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get many workflows": {
      "main": [
        [
          {
            "node": "Code: parsing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: detect changes": {
      "main": [
        [
          {
            "node": "Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: save current values": {
      "main": [
        [
          {
            "node": "Sheets:Lookup-ExistOnCalendar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets:Lookup-ExistOnCalendar": {
      "main": [
        [
          {
            "node": "Code manual merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: split eventIds for delete": {
      "main": [
        [
          {
            "node": "Delete old event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}