N
n8n Store
Workflow Market
Weekly GSC Pulse Check - Generic

Weekly GSC Pulse Check - Generic

Description

Categories

📢 Marketing

Nodes Used

n8n-nodes-base.ifn8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.code
PriceGratuit
Views0
Last Updated11/28/2025
workflow.json
{
  "id": "83yfuiWClSX51Ebj",
  "meta": {
    "instanceId": "bd3424651820da96219b3bf8bf1cdfabcd2b0cc2dbde58159a106ccfa63cca09",
    "templateCredsSetupCompleted": true
  },
  "name": "Weekly GSC Pulse Check - Generic",
  "tags": [
    {
      "id": "10",
      "name": "SEO",
      "createdAt": "2022-11-25T12:57:02.999Z",
      "updatedAt": "2022-11-25T12:57:02.999Z"
    },
    {
      "id": "dO9obbaAMg8UNnbo",
      "name": "Scheduled",
      "createdAt": "2025-07-10T15:12:41.643Z",
      "updatedAt": "2025-07-10T15:12:41.643Z"
    }
  ],
  "nodes": [
    {
      "id": "fdcaa6bc-9f69-45db-9d87-6fde0f2266a6",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        860,
        1580
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 15 * * 1"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "cb54e593-9335-4252-a69f-5541ab17b32b",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        1180,
        1580
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "8ab18755-9c4f-40d8-a0c0-f16ab3b7d940",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.label }}",
              "rightValue": "priorWeek"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "56e3f13b-c094-45d4-9832-75a1621c44eb",
      "name": "priorWeek",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Connect GSC account",
      "position": [
        1360,
        1500
      ],
      "parameters": {
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"startDate\": \"{{ $json.startDate }}\",\n  \"endDate\": \"{{ $json.endDate }}\",\n  \"dimensions\": [\"page\", \"query\"],\n  \"rowLimit\": 2500,\n  \"dataState\": \"all\"\n}\n",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleOAuth2Api"
      },
      "credentials": {
        "googleOAuth2Api": {
          "id": "vGLK8wkbx5dOMive",
          "name": "My Google Search Console Account"
        }
      },
      "notesInFlow": true,
      "typeVersion": 4.2
    },
    {
      "id": "0b192fcb-02fb-4220-b6fe-2132c0632129",
      "name": "lastWeek",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Connect GSC account",
      "position": [
        1360,
        1660
      ],
      "parameters": {
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"startDate\": \"{{ $json.startDate }}\",\n  \"endDate\": \"{{ $json.endDate }}\",\n  \"dimensions\": [\"page\", \"query\"],\n  \"rowLimit\": 2500,\n  \"dataState\": \"all\"\n}\n",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleOAuth2Api"
      },
      "credentials": {
        "googleOAuth2Api": {
          "id": "vGLK8wkbx5dOMive",
          "name": "My Google Search Console Account"
        }
      },
      "notesInFlow": true,
      "typeVersion": 4.2
    },
    {
      "id": "8221a2d9-c93d-4675-8090-b4559dcce4f4",
      "name": "label",
      "type": "n8n-nodes-base.code",
      "position": [
        1500,
        1500
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  return {\n    json: {\n      week: \"priorWeek\",  // or \"lastWeek\" in the other branch\n      ...item.json        // preserve everything from GSC response\n    }\n  };\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "8e242ef0-3aa0-43dd-8472-9dfadb5b868d",
      "name": "label1",
      "type": "n8n-nodes-base.code",
      "position": [
        1500,
        1660
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  return {\n    json: {\n      week: \"lastWeek\",  // or \"priorWeek\" in the other branch\n      ...item.json        // preserve everything from GSC response\n    }\n  };\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "134f84ca-370c-4d17-8711-728a3402ddc6",
      "name": "Flatten",
      "type": "n8n-nodes-base.code",
      "position": [
        1640,
        1500
      ],
      "parameters": {
        "jsCode": "const weekLabel = $json.week;\n\nif (!items[0].json.rows) {\n  return [];\n}\n\nreturn items[0].json.rows.map(row => {\n  return {\n    json: {\n      week: weekLabel,\n      page: row.keys[0] || null,\n      query: row.keys[1] || null,\n      clicks: row.clicks,\n      impressions: row.impressions,\n      ctr: row.ctr,\n      position: row.position\n    }\n  };\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e6ca5fa7-2c85-4125-9b3b-11534b89edbe",
      "name": "Flatten1",
      "type": "n8n-nodes-base.code",
      "position": [
        1640,
        1660
      ],
      "parameters": {
        "jsCode": "const weekLabel = $json.week;\n\nif (!items[0].json.rows) {\n  return [];\n}\n\nreturn items[0].json.rows.map(row => {\n  return {\n    json: {\n      week: weekLabel,\n      page: row.keys[0] || null,\n      query: row.keys[1] || null,\n      clicks: row.clicks,\n      impressions: row.impressions,\n      ctr: row.ctr,\n      position: row.position\n    }\n  };\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "4105dc33-c7b0-4675-8e91-dbf0a130d6c7",
      "name": "Define Weeks",
      "type": "n8n-nodes-base.code",
      "notes": "Defines what the weeks are",
      "position": [
        1020,
        1580
      ],
      "parameters": {
        "jsCode": "function formatDate(date) {\n  return date.toISOString().split('T')[0];\n}\n\nconst today = new Date();\n\n// Prior week (14 to 8 days ago)\nconst priorStart = new Date(today);\npriorStart.setDate(today.getDate() - 14);\n\nconst priorEnd = new Date(today);\npriorEnd.setDate(today.getDate() - 8);\n\n// Last week (7 to 1 days ago)\nconst lastStart = new Date(today);\nlastStart.setDate(today.getDate() - 7);\n\nconst lastEnd = new Date(today);\nlastEnd.setDate(today.getDate() - 1);\n\nreturn [\n  {\n    json: {\n      label: \"priorWeek\",\n      startDate: formatDate(priorStart),\n      endDate: formatDate(priorEnd)\n    }\n  },\n  {\n    json: {\n      label: \"lastWeek\",\n      startDate: formatDate(lastStart),\n      endDate: formatDate(lastEnd)\n    }\n  }\n];\n"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "3006c533-6749-4661-a19e-0d35b91029b9",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        1820,
        1580
      ],
      "parameters": {},
      "typeVersion": 3
    },
    {
      "id": "eb343549-1c73-4ba0-9b97-fc19ea51a72c",
      "name": "Merge Weeks",
      "type": "n8n-nodes-base.code",
      "position": [
        1960,
        1580
      ],
      "parameters": {
        "jsCode": "// Split all items by week\nconst prior = [];\nconst last = [];\n\nfor (const item of items) {\n  if (item.json.week === \"priorWeek\") {\n    prior.push(item.json);\n  } else if (item.json.week === \"lastWeek\") {\n    last.push(item.json);\n  }\n}\n\n// Index prior week rows by page+query\nconst priorMap = new Map();\nfor (const row of prior) {\n  const key = `${row.page}|||${row.query}`;\n  priorMap.set(key, row);\n}\n\n// Match last week rows and compute deltas + formatted % changes\nconst results = [];\n\nfor (const row of last) {\n  const key = `${row.page}|||${row.query}`;\n  const previous = priorMap.get(key);\n  if (!previous) continue;\n\n  const clicksDelta = row.clicks - previous.clicks;\n  const ctrDeltaRaw = row.ctr - previous.ctr;\n  const impressionsDelta = row.impressions - previous.impressions;\n  const positionDelta = row.position - previous.position;\n\n  const percentChangeClicks = previous.clicks !== 0 ? `${((clicksDelta / previous.clicks) * 100).toFixed(1)}%` : null;\n  const percentChangeCTR = previous.ctr !== 0 ? `${((ctrDeltaRaw / previous.ctr) * 100).toFixed(1)}%` : null;\n  const percentChangeImpressions = previous.impressions !== 0 ? `${((impressionsDelta / previous.impressions) * 100).toFixed(1)}%` : null;\n  const percentChangePosition = previous.position !== 0 ? `${((positionDelta / previous.position) * 100).toFixed(1)}%` : null;\n\n  const ctrLastWeek = `${(row.ctr * 100).toFixed(1)}%`;\n  const ctrPriorWeek = `${(previous.ctr * 100).toFixed(1)}%`;\n  const deltaCTR = `${(ctrDeltaRaw * 100).toFixed(1)}%`;\n\n  results.push({\n    json: {\n      page: row.page,\n      query: row.query,\n\n      deltaClicks: clicksDelta,\n      percentChangeClicks,\n      clicksLastWeek: row.clicks,\n      clicksPriorWeek: previous.clicks,\n\n      deltaCTR,\n      percentChangeCTR,\n      ctrLastWeek,\n      ctrPriorWeek,\n\n      deltaImpressions: impressionsDelta,\n      percentChangeImpressions,\n      impressionsLastWeek: row.impressions,\n      impressionsPriorWeek: previous.impressions,\n\n      deltaPosition: positionDelta,\n      percentChangePosition,\n      positionLastWeek: row.position,\n      positionPriorWeek: previous.position\n    }\n  });\n}\n\nreturn results;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "5e8378a4-ecda-435d-ab92-796c008c7d00",
      "name": "Tag Brand / Recipes / Nonbrand",
      "type": "n8n-nodes-base.code",
      "notes": "Create KW segments",
      "position": [
        2100,
        1580
      ],
      "parameters": {
        "jsCode": "// Step 1: Map the items and add properties\nconst mappedItems = items.map(item => {\n  const query = (item.json.query || \"\").toLowerCase();\n  const page = (item.json.page || \"\").toLowerCase();\n\n  // Update with your own brand terms\"\n  const isBrand = query.includes(\"BRAND TERM 1\", \"BRAND TERM 2\", \"ETC.\");\n  // Remove if you don't want to also segment brand terms by page group, or update with your own group, or add more as needed\n  const isRecipes = page.includes(\"/recipes\");\n\n  // Tag segment type — this is optional now that isBrand + isRecipes can coexist\n  let segment = \"nonbrand\";\n  if (isBrand && isRecipes) {\n    segment = \"brand+recipes\";\n  } else if (isBrand) {\n    segment = \"brand\";\n  } else if (isRecipes) {\n    segment = \"recipes\";\n  }\n\n  return {\n    json: {\n      ...item.json,\n      isBrand,\n      isRecipes,\n      segment\n    }\n  };\n});\n\n// Step 2: Sort by deltaClicks\nconst sortedItems = mappedItems.sort((a, b) => {\n  const deltaClicksA = a.json.deltaClicks || 0;\n  const deltaClicksB = b.json.deltaClicks || 0;\n\n  // Sorting in descending order (highest deltaClicks first)\n  return deltaClicksB - deltaClicksA;\n});\n\n// Return the sorted items\nreturn sortedItems;\n"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "137aa32d-2dd0-4f18-9843-20afb836f682",
      "name": "Top Movers Filter",
      "type": "n8n-nodes-base.code",
      "position": [
        2520,
        1080
      ],
      "parameters": {
        "jsCode": "const flagged = [];\n\nfor (const item of items) {\n  const delta = item.json.deltaClicks || 0;\n  const pct = parseFloat(item.json.percentChangeClicks?.replace('%', '') || 0);\n\n  if (Math.abs(delta) >= 200 && Math.abs(pct) >= 30) {\n    const direction = delta > 0 ? '📈 UP' : '📉 DOWN';\n    const line = `• ${direction} ${item.json.query} → ${delta > 0 ? '+' : ''}${delta} clicks (${item.json.percentChangeClicks})\\nPage: ${item.json.page}`;\n    \n    flagged.push(line);\n  }\n}\n\nif (flagged.length === 0) {\n  return []; // No alerts to send\n}\n\nreturn [\n  {\n    json: {\n      text: `*🚨 Big WoW Movers Alert - BRAND:*\\n\\n${flagged.join('\\n\\n')}`\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "68d959f5-c70e-44a7-b74e-ea8e3c0046cc",
      "name": "Top 25 Filter",
      "type": "n8n-nodes-base.code",
      "position": [
        2660,
        1220
      ],
      "parameters": {
        "jsCode": "// Split into up and down movers\nconst up = [];\nconst down = [];\n\nfor (const item of items) {\n  const delta = item.json.deltaClicks || 0;\n  if (delta > 0) up.push(item);\n  else if (delta < 0) down.push(item);\n}\n\n// Sort by absolute deltaClicks descending\nup.sort((a, b) => Math.abs(b.json.deltaClicks) - Math.abs(a.json.deltaClicks));\ndown.sort((a, b) => Math.abs(b.json.deltaClicks) - Math.abs(a.json.deltaClicks));\n\n// Take top 25 from each\nconst topUp = up.slice(0, 25);\nconst topDown = down.slice(0, 25);\n\n// HTML row formatter\nconst formatRow = (item, emoji) => {\n  const q = item.json.query;\n  const p = item.json.page;\n  const delta = item.json.deltaClicks;\n  const pct = item.json.percentChangeClicks;\n  return `\n    <tr>\n      <td>${emoji}</td>\n      <td>${q}</td>\n      <td><a href=\"${p}\">${p}</a></td>\n      <td>${delta > 0 ? '+' : ''}${delta}</td>\n      <td>${pct}</td>\n    </tr>\n  `;\n};\n\n// Build table\nconst header = `\n  <tr>\n    <th>📊</th>\n    <th>Query</th>\n    <th>Page</th>\n    <th>Delta Clicks</th>\n    <th>% Change</th>\n  </tr>\n`;\n\nconst bodyRows = [\n  ...topUp.map(item => formatRow(item, '📈')),\n  ...topDown.map(item => formatRow(item, '📉'))\n];\n\nconst html = `\n  <h2>Top Weekly Movers</h2>\n  <p>Sorted by largest absolute click change</p>\n  <table border=\"1\" cellpadding=\"4\" cellspacing=\"0\">\n    ${header}\n    ${bodyRows.join('\\n')}\n  </table>\n`;\n\nreturn [\n  {\n    json: {\n      subject: `Top WoW SEO Movers – BRAND (${new Date().toISOString().split('T')[0]})`,\n      body: html\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "95054b7e-7d37-46e4-b4bd-b1a719438cf2",
      "name": "Top WoW Movers Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        2660,
        1080
      ],
      "webhookId": "af07d03e-7798-453a-a538-390bad76cd7a",
      "parameters": {
        "text": "={{$json.text}}",
        "user": {
          "__rl": true,
          "mode": "id",
          "value": ""
        },
        "select": "user",
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "id": "db118b11-8d3b-4df1-aa82-9c4477eea3f4",
          "name": "Slack Account n8n-bot (YOUR.ACCOUNT)"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "2fa5b713-2346-4552-98c7-24a6d3997e14",
      "name": "Code",
      "type": "n8n-nodes-base.code",
      "position": [
        3080,
        1600
      ],
      "parameters": {
        "jsCode": "const titledTables = items.map((item, index) => {\n  const label = [\n    \"Brand KWs\",\n    \"Brand+Recipes KWs\",\n    \"Recipes KWs\",\n    \"Nonbrand KWs\"\n  ][index] || `Segment ${index + 1}`;\n\n  return `<h1>${label}</h1>\\n${item.json.body || ''}`;\n});\n\nreturn [\n  {\n    json: {\n      subject: `Top Weekly SEO Movers – ${new Date().toISOString().split('T')[0]}`,\n      body: titledTables.join('<br><br>')\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "2273a8d5-df75-46a1-94b6-ee6228e066a2",
      "name": "Top WoW Movers Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        3220,
        1600
      ],
      "webhookId": "58867038-205d-482e-9c43-7a4c0400740b",
      "parameters": {
        "message": "={{ $json.body }}",
        "options": {},
        "subject": "={{ $json.subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "020fc4ae-9c23-4719-8484-ebde4daead63",
          "name": "Gmail Account 01 (YOUR.ACCOUNT)"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "192d4640-1955-44cf-9f21-cd8292d32d9c",
      "name": "Merge4",
      "type": "n8n-nodes-base.merge",
      "position": [
        2940,
        1580
      ],
      "parameters": {
        "numberInputs": 4
      },
      "typeVersion": 3
    },
    {
      "id": "f71cf77a-24a9-42a5-bd2c-7c3fdf6974b7",
      "name": "Top WoW Movers Alert1",
      "type": "n8n-nodes-base.slack",
      "position": [
        2660,
        1380
      ],
      "webhookId": "93b21702-2b47-4eff-a8f1-b7913c6e028d",
      "parameters": {
        "text": "={{$json.text}}",
        "user": {
          "__rl": true,
          "mode": "id",
          "value": ""
        },
        "select": "user",
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "id": "db118b11-8d3b-4df1-aa82-9c4477eea3f4",
          "name": "Slack Account n8n-bot (YOUR.ACCOUNT)"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "84905892-f99e-4934-9377-942d16fb98bc",
      "name": "Top 25 Filter1",
      "type": "n8n-nodes-base.code",
      "position": [
        2660,
        1520
      ],
      "parameters": {
        "jsCode": "// Split into up and down movers\nconst up = [];\nconst down = [];\n\nfor (const item of items) {\n  const delta = item.json.deltaClicks || 0;\n  if (delta > 0) up.push(item);\n  else if (delta < 0) down.push(item);\n}\n\n// Sort by absolute deltaClicks descending\nup.sort((a, b) => Math.abs(b.json.deltaClicks) - Math.abs(a.json.deltaClicks));\ndown.sort((a, b) => Math.abs(b.json.deltaClicks) - Math.abs(a.json.deltaClicks));\n\n// Take top 25 from each\nconst topUp = up.slice(0, 25);\nconst topDown = down.slice(0, 25);\n\n// HTML row formatter\nconst formatRow = (item, emoji) => {\n  const q = item.json.query;\n  const p = item.json.page;\n  const delta = item.json.deltaClicks;\n  const pct = item.json.percentChangeClicks;\n  return `\n    <tr>\n      <td>${emoji}</td>\n      <td>${q}</td>\n      <td><a href=\"${p}\">${p}</a></td>\n      <td>${delta > 0 ? '+' : ''}${delta}</td>\n      <td>${pct}</td>\n    </tr>\n  `;\n};\n\n// Build table\nconst header = `\n  <tr>\n    <th>📊</th>\n    <th>Query</th>\n    <th>Page</th>\n    <th>Delta Clicks</th>\n    <th>% Change</th>\n  </tr>\n`;\n\nconst bodyRows = [\n  ...topUp.map(item => formatRow(item, '📈')),\n  ...topDown.map(item => formatRow(item, '📉'))\n];\n\nconst html = `\n  <h2>Top Weekly Movers</h2>\n  <p>Sorted by largest absolute click change</p>\n  <table border=\"1\" cellpadding=\"4\" cellspacing=\"0\">\n    ${header}\n    ${bodyRows.join('\\n')}\n  </table>\n`;\n\nreturn [\n  {\n    json: {\n      subject: `Top WoW SEO Movers – BRAND + RECIPES (${new Date().toISOString().split('T')[0]})`,\n      body: html\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "0dc0cca9-85b7-4ddb-9e0b-35e504c24212",
      "name": "Top Movers Filter1",
      "type": "n8n-nodes-base.code",
      "position": [
        2520,
        1380
      ],
      "parameters": {
        "jsCode": "const flagged = [];\n\nfor (const item of items) {\n  const delta = item.json.deltaClicks || 0;\n  const pct = parseFloat(item.json.percentChangeClicks?.replace('%', '') || 0);\n\n  if (Math.abs(delta) >= 200 && Math.abs(pct) >= 30) {\n    const direction = delta > 0 ? '📈 UP' : '📉 DOWN';\n    const line = `• ${direction} ${item.json.query} → ${delta > 0 ? '+' : ''}${delta} clicks (${item.json.percentChangeClicks})\\nPage: ${item.json.page}`;\n    \n    flagged.push(line);\n  }\n}\n\nif (flagged.length === 0) {\n  return []; // No alerts to send\n}\n\nreturn [\n  {\n    json: {\n      text: `*🚨 Big WoW Movers Alert - BRAND + RECIPES:*\\n\\n${flagged.join('\\n\\n')}`\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "285477c6-4b90-4fd4-bb1d-b795f57e2a47",
      "name": "Top WoW Movers Alert3",
      "type": "n8n-nodes-base.slack",
      "position": [
        2660,
        2120
      ],
      "webhookId": "e55e1af7-0a9c-474a-8690-da870a50a2b4",
      "parameters": {
        "text": "={{$json.text}}",
        "user": {
          "__rl": true,
          "mode": "id",
          "value": ""
        },
        "select": "user",
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "id": "db118b11-8d3b-4df1-aa82-9c4477eea3f4",
          "name": "Slack Account n8n-bot (YOUR.ACCOUNT)"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "645895c3-4c7d-46e8-83d4-59b740cc9879",
      "name": "Top WoW Movers Alert2",
      "type": "n8n-nodes-base.slack",
      "position": [
        2660,
        1820
      ],
      "webhookId": "d1e2d56f-785a-4d6b-9a77-f75715a55ff6",
      "parameters": {
        "text": "={{$json.text}}",
        "user": {
          "__rl": true,
          "mode": "id",
          "value": ""
        },
        "select": "user",
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "id": "db118b11-8d3b-4df1-aa82-9c4477eea3f4",
          "name": "Slack Account n8n-bot (YOUR.ACCOUNT)"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "8366e03b-10af-4917-b702-cd776f9c4f57",
      "name": "Top 25 Filter3",
      "type": "n8n-nodes-base.code",
      "position": [
        2660,
        1980
      ],
      "parameters": {
        "jsCode": "// Split into up and down movers\nconst up = [];\nconst down = [];\n\nfor (const item of items) {\n  const delta = item.json.deltaClicks || 0;\n  if (delta > 0) up.push(item);\n  else if (delta < 0) down.push(item);\n}\n\n// Sort by absolute deltaClicks descending\nup.sort((a, b) => Math.abs(b.json.deltaClicks) - Math.abs(a.json.deltaClicks));\ndown.sort((a, b) => Math.abs(b.json.deltaClicks) - Math.abs(a.json.deltaClicks));\n\n// Take top 25 from each\nconst topUp = up.slice(0, 25);\nconst topDown = down.slice(0, 25);\n\n// HTML row formatter\nconst formatRow = (item, emoji) => {\n  const q = item.json.query;\n  const p = item.json.page;\n  const delta = item.json.deltaClicks;\n  const pct = item.json.percentChangeClicks;\n  return `\n    <tr>\n      <td>${emoji}</td>\n      <td>${q}</td>\n      <td><a href=\"${p}\">${p}</a></td>\n      <td>${delta > 0 ? '+' : ''}${delta}</td>\n      <td>${pct}</td>\n    </tr>\n  `;\n};\n\n// Build table\nconst header = `\n  <tr>\n    <th>📊</th>\n    <th>Query</th>\n    <th>Page</th>\n    <th>Delta Clicks</th>\n    <th>% Change</th>\n  </tr>\n`;\n\nconst bodyRows = [\n  ...topUp.map(item => formatRow(item, '📈')),\n  ...topDown.map(item => formatRow(item, '📉'))\n];\n\nconst html = `\n  <h2>Top Weekly Movers</h2>\n  <p>Sorted by largest absolute click change</p>\n  <table border=\"1\" cellpadding=\"4\" cellspacing=\"0\">\n    ${header}\n    ${bodyRows.join('\\n')}\n  </table>\n`;\n\nreturn [\n  {\n    json: {\n      subject: `Top WoW SEO Movers – NONBRAND (${new Date().toISOString().split('T')[0]})`,\n      body: html\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "57ec0dd1-e52c-4599-a646-b2bb23621391",
      "name": "Top Movers Filter3",
      "type": "n8n-nodes-base.code",
      "position": [
        2520,
        2120
      ],
      "parameters": {
        "jsCode": "const flagged = [];\n\nfor (const item of items) {\n  const delta = item.json.deltaClicks || 0;\n  const pct = parseFloat(item.json.percentChangeClicks?.replace('%', '') || 0);\n\n  if (Math.abs(delta) >= 200 && Math.abs(pct) >= 30) {\n    const direction = delta > 0 ? '📈 UP' : '📉 DOWN';\n    const line = `• ${direction} ${item.json.query} → ${delta > 0 ? '+' : ''}${delta} clicks (${item.json.percentChangeClicks})\\nPage: ${item.json.page}`;\n    \n    flagged.push(line);\n  }\n}\n\nif (flagged.length === 0) {\n  return []; // No alerts to send\n}\n\nreturn [\n  {\n    json: {\n      text: `*🚨 Big WoW Movers Alert - NONBRAND:*\\n\\n${flagged.join('\\n\\n')}`\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "a86b34c7-60b3-4ee9-9431-68530a0e6996",
      "name": "Top 25 Filter2",
      "type": "n8n-nodes-base.code",
      "position": [
        2660,
        1680
      ],
      "parameters": {
        "jsCode": "// Split into up and down movers\nconst up = [];\nconst down = [];\n\nfor (const item of items) {\n  const delta = item.json.deltaClicks || 0;\n  if (delta > 0) up.push(item);\n  else if (delta < 0) down.push(item);\n}\n\n// Sort by absolute deltaClicks descending\nup.sort((a, b) => Math.abs(b.json.deltaClicks) - Math.abs(a.json.deltaClicks));\ndown.sort((a, b) => Math.abs(b.json.deltaClicks) - Math.abs(a.json.deltaClicks));\n\n// Take top 25 from each\nconst topUp = up.slice(0, 25);\nconst topDown = down.slice(0, 25);\n\n// HTML row formatter\nconst formatRow = (item, emoji) => {\n  const q = item.json.query;\n  const p = item.json.page;\n  const delta = item.json.deltaClicks;\n  const pct = item.json.percentChangeClicks;\n  return `\n    <tr>\n      <td>${emoji}</td>\n      <td>${q}</td>\n      <td><a href=\"${p}\">${p}</a></td>\n      <td>${delta > 0 ? '+' : ''}${delta}</td>\n      <td>${pct}</td>\n    </tr>\n  `;\n};\n\n// Build table\nconst header = `\n  <tr>\n    <th>📊</th>\n    <th>Query</th>\n    <th>Page</th>\n    <th>Delta Clicks</th>\n    <th>% Change</th>\n  </tr>\n`;\n\nconst bodyRows = [\n  ...topUp.map(item => formatRow(item, '📈')),\n  ...topDown.map(item => formatRow(item, '📉'))\n];\n\nconst html = `\n  <h2>Top Weekly Movers</h2>\n  <p>Sorted by largest absolute click change</p>\n  <table border=\"1\" cellpadding=\"4\" cellspacing=\"0\">\n    ${header}\n    ${bodyRows.join('\\n')}\n  </table>\n`;\n\nreturn [\n  {\n    json: {\n      subject: `Top WoW SEO Movers – RECIPES (${new Date().toISOString().split('T')[0]})`,\n      body: html\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "3a540e0b-2d24-4ddc-8142-572624da1008",
      "name": "Top Movers Filter2",
      "type": "n8n-nodes-base.code",
      "position": [
        2520,
        1820
      ],
      "parameters": {
        "jsCode": "const flagged = [];\n\nfor (const item of items) {\n  const delta = item.json.deltaClicks || 0;\n  const pct = parseFloat(item.json.percentChangeClicks?.replace('%', '') || 0);\n\n  if (Math.abs(delta) >= 200 && Math.abs(pct) >= 30) {\n    const direction = delta > 0 ? '📈 UP' : '📉 DOWN';\n    const line = `• ${direction} ${item.json.query} → ${delta > 0 ? '+' : ''}${delta} clicks (${item.json.percentChangeClicks})\\nPage: ${item.json.page}`;\n    \n    flagged.push(line);\n  }\n}\n\nif (flagged.length === 0) {\n  return []; // No alerts to send\n}\n\nreturn [\n  {\n    json: {\n      text: `*🚨 Big WoW Movers Alert - RECIPES:*\\n\\n${flagged.join('\\n\\n')}`\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "f509329a-ffe4-419b-a085-a995376f6490",
      "name": "Switch",
      "type": "n8n-nodes-base.switch",
      "position": [
        2260,
        1560
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Brand Flow",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.segment }}",
                    "rightValue": "brand"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Brand+Recipes Flow",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "84a4654e-3d07-4bde-a7cf-aadf9b61301d",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.segment }}",
                    "rightValue": "brand+recipes"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Recipes Flow",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "eee379f6-8678-4f7c-8fb4-68a0be79aa01",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.segment }}",
                    "rightValue": "recipes"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Nonbrand Flow",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "e9624b23-70a7-4e4e-bd4c-72cf716d4fe7",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.segment }}",
                    "rightValue": "nonbrand"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "4c52027f-871e-4c67-b7b7-ee19e3492660",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1320,
        1380
      ],
      "parameters": {
        "width": 180,
        "height": 100,
        "content": "## Connect to GSC account"
      },
      "typeVersion": 1
    },
    {
      "id": "6c7fe161-38db-4198-b426-dd0e0c2307ea",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2600,
        960
      ],
      "parameters": {
        "width": 200,
        "height": 100,
        "content": "## Connect to Slack account"
      },
      "typeVersion": 1
    },
    {
      "id": "b0760a35-69f2-44df-963e-9d11149bf3a4",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3160,
        1440
      ],
      "parameters": {
        "height": 120,
        "content": "## Connect to Gmail account or update to something else"
      },
      "typeVersion": 1
    },
    {
      "id": "773bbb5a-51b4-46ba-b03d-eb663d98fe65",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2040,
        1460
      ],
      "parameters": {
        "width": 200,
        "height": 80,
        "content": "## Create KW segments here"
      },
      "typeVersion": 1
    },
    {
      "id": "f8643470-af97-4e57-a915-0bf034a18c97",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2460,
        2280
      ],
      "parameters": {
        "height": 140,
        "content": "### Current threshold is set to greater than 200 absolute delta clicks (i.e. positive or negative) and 30% absolute change."
      },
      "typeVersion": 1
    },
    {
      "id": "37bc1268-1dd0-49aa-a53e-f789db9541ed",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        180,
        1260
      ],
      "parameters": {
        "width": 620,
        "height": 760,
        "content": "## This workflow tracks week-over-week changes in Google Search Console performance and highlights the top movers across keyword segments like brand, nonbrand, and content categories.\n\nInstead of providing a routine check, it focuses on significant movements by:\n- Sending a Slack alert only if a query crosses a defined movement threshold.\n- Emailing a structured report with the Top 25 increases and Top 25 decreases for clicks, including % changes and linked URLs\n\nIt’s designed to surface the most important shifts, helping SEO teams catch big wins, losses, or anomalies early.\n\n### How it works\n1. Runs weekly (e.g. every Monday) to compare last week’s GSC data to the week prior.\n2. Segments traffic based on query and page (e.g. brand terms, category page URLs, etc.).\n3. Calculates delta and % change for clicks, CTR, impressions, and position.\n4. Filters and flags top movers with large shifts (default: ±200 clicks and ±30%).\n5. Sends Slack alerts only if meaningful changes are detected.\n6. Emails a full HTML table report showing the Top 25 up/down queries per segment.\n\n### Setup steps\n- Requires a connected Google Search Console account.\n- Slack alert is included by default (can be replaced with email, webhook, or other tools).\n- Customize your brand terms and URL filters to match your segments (e.g. recipes, blog, category pages).\n- Typical setup time: 15–25 minutes depending on the number of segments and filters you want.\n\n*Note: “Recipes” is used in the example to show how to segment by content type. You can update this to reflect your own site’s structure.*"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "808147c5-d84d-4f62-aa6f-879e7cb4ed44",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "priorWeek",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "lastWeek",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code": {
      "main": [
        [
          {
            "node": "Top WoW Movers Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Merge Weeks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "label": {
      "main": [
        [
          {
            "node": "Flatten",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge4": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch": {
      "main": [
        [
          {
            "node": "Top Movers Filter",
            "type": "main",
            "index": 0
          },
          {
            "node": "Top 25 Filter",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Top 25 Filter1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Top Movers Filter1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Top 25 Filter2",
            "type": "main",
            "index": 0
          },
          {
            "node": "Top Movers Filter2",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Top 25 Filter3",
            "type": "main",
            "index": 0
          },
          {
            "node": "Top Movers Filter3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "label1": {
      "main": [
        [
          {
            "node": "Flatten1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flatten": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flatten1": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "lastWeek": {
      "main": [
        [
          {
            "node": "label1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "priorWeek": {
      "main": [
        [
          {
            "node": "label",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Weeks": {
      "main": [
        [
          {
            "node": "Tag Brand / Recipes / Nonbrand",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Define Weeks": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Top 25 Filter": {
      "main": [
        [
          {
            "node": "Merge4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Top 25 Filter1": {
      "main": [
        [
          {
            "node": "Merge4",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Top 25 Filter2": {
      "main": [
        [
          {
            "node": "Merge4",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Top 25 Filter3": {
      "main": [
        [
          {
            "node": "Merge4",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Define Weeks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Top Movers Filter": {
      "main": [
        [
          {
            "node": "Top WoW Movers Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Top Movers Filter1": {
      "main": [
        [
          {
            "node": "Top WoW Movers Alert1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Top Movers Filter2": {
      "main": [
        [
          {
            "node": "Top WoW Movers Alert2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Top Movers Filter3": {
      "main": [
        [
          {
            "node": "Top WoW Movers Alert3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Top WoW Movers Alert": {
      "main": [
        []
      ]
    },
    "Tag Brand / Recipes / Nonbrand": {
      "main": [
        [
          {
            "node": "Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

相关工作流