{
  "id": "vQoMqhlLAuy1uCJp",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Monitor Core Web Vitals at scale with PageSpeed API & Google Sheets",
  "tags": [],
  "nodes": [
    {
      "id": "f3ded7a8-81ea-435b-88cb-57f8a70067da",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3104,
        -256
      ],
      "parameters": {
        "color": 7,
        "width": 848,
        "height": 304,
        "content": "## Fetch XML sitemap & extract URLs\nIf no file is uploaded, workflow will fetch the XML sitemap defined in the Config node and extract all URLs."
      },
      "typeVersion": 1
    },
    {
      "id": "7539fc6a-3bb0-4ab3-9444-25e787149809",
      "name": "Fetch the XML sitemap",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -3024,
        -144
      ],
      "parameters": {
        "url": "={{ $('Config').item.json.xml_sitemap_url }}",
        "options": {}
      },
      "typeVersion": 4.4
    },
    {
      "id": "c4a37165-700e-44d8-8573-8d094206f590",
      "name": "Convert to JSON",
      "type": "n8n-nodes-base.xml",
      "position": [
        -2832,
        -144
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "54889c3c-42ce-4f6f-be39-a855d5d913f2",
      "name": "Extract URL tags",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -2624,
        -144
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "urlset.url"
      },
      "typeVersion": 1
    },
    {
      "id": "0c795eda-84b7-4287-b6f3-ce28031a94cb",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4256,
        -832
      ],
      "parameters": {
        "color": 7,
        "width": 800,
        "height": 704,
        "content": "## Workflow setup\n\n**Required:**\n1. Insert your PageSpeed API key into **api_key** field in the *Config* node\n2. Insert your XML sitemap address into **xml_sitemap_url** field *(required unless CSV file is used)*\n3. Create and select the default Google Sheet into which report is saved *(new sheets, columns and data will populate automatically on each run).*\n4. Add your email address inside the 'Send audit summary email' node to get the email notification \n\n\n**Optional customisation:**\n- Change the **lighthouse_device** to switch between mobile/desktop audit *(default: desktop)*\n- Change the **analytics_campaign_utm** to keep track of the audit data in your website analytics platform; can also be removed entirely *(default: PageSpeedAudit)*\n- Update the **cwv_pass_thresholds** object if you need looser/stricter pass thresholds *(current defaults were taken from official Lighthouse/CWV documentation which may change in the future)*\n\n\n[Official Lighthouse performance scoring documentation](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring)\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "bbbaa679-56de-46ca-9a9d-dbeae17b1511",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -1872,
        -544
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "84d3e39c-9586-41a1-904a-417b02ba48d4",
      "name": "Upload CSV",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -4608,
        -128
      ],
      "parameters": {
        "options": {},
        "formTitle": "Upload CSV",
        "formFields": {
          "values": [
            {
              "fieldType": "file",
              "fieldLabel": "CSV file:",
              "requiredField": true,
              "acceptFileTypes": ".csv"
            }
          ]
        },
        "formDescription": "Upload a CSV file containing URLs to be audited in .CSV format (comma-delimited)."
      },
      "typeVersion": 2.5
    },
    {
      "id": "e6c0adeb-1899-47ba-beae-f346441d32f4",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2128,
        -768
      ],
      "parameters": {
        "color": 7,
        "width": 1060,
        "height": 896,
        "content": "## Main audit loop\nSplit in batches, test each URL, and save results to spreadsheet. Any errors will be caught and inserted into spreadsheet along with Lighthouse error message for debugging.\n\n**Optional customisation:**\n- Change the time interval in the Wait node if you want to limit performance impact of the audit on your site *(useful for large audits, default: 1 second)*"
      },
      "typeVersion": 1
    },
    {
      "id": "de694c52-2794-4aa2-b0fa-6f0050a3e2ee",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -4608,
        -368
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "d08aa690-091b-4603-b992-fadb086b1cae",
      "name": "Pick default spreadsheet to save the report",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -4016,
        -368
      ],
      "parameters": {
        "title": "=PageSpeed_audit_{{ $json.lighthouse_device }}_{{ $now.format('dd-MM-yyyy_HH:mm') }}",
        "options": {},
        "operation": "create",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "3d8bedbc-dc42-4064-9a70-1cab3cb97e5d",
      "name": "Add column headers to new sheet",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -3808,
        -368
      ],
      "parameters": {
        "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $('Pick default spreadsheet to save the report').item.json.spreadsheetId }}/values/{{ $('Pick default spreadsheet to save the report').item.json.title }}!A1:AB?valueInputOption=RAW",
        "method": "PUT",
        "options": {},
        "jsonBody": "={\n\t\"range\": \"{{ $('Pick default spreadsheet to save the report').item.json.title }}!A1:AB\",\n\t\"majorDimension\": \"ROWS\",\n\t\"values\": [{{ $('Config').item.json.report_column_names.toJsonString() }}]\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleSheetsOAuth2Api"
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "02cf2511-e26c-49ef-86b3-3f6d731c4f83",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3104,
        -656
      ],
      "parameters": {
        "color": 7,
        "width": 464,
        "height": 320,
        "content": "## Extract URLs from the CSV file\nIf CSV file is uploaded manually, it will be processed instead of XML sitemap defined in the Config node."
      },
      "typeVersion": 1
    },
    {
      "id": "68688f68-2dc4-4975-a766-0858722beeaf",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "position": [
        -4208,
        -368
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "f3b9a727-cd59-4166-9dbf-4e4abb54ff0a",
              "name": "api_key",
              "type": "string",
              "value": "> Insert your API key here <"
            },
            {
              "id": "f7b57b1e-20c7-4c8d-b095-1f88d29d60d0",
              "name": "xml_sitemap_url",
              "type": "string",
              "value": "> Insert your XML sitemap URL here <"
            },
            {
              "id": "beb25ef0-f0e3-4d20-b8c8-14a8a0ae07af",
              "name": "lighthouse_category",
              "type": "string",
              "value": "performance"
            },
            {
              "id": "b024103b-8d3d-4366-8276-811c8dfb7f43",
              "name": "lighthouse_device",
              "type": "string",
              "value": "desktop"
            },
            {
              "id": "a27363c6-1904-4a25-9b61-0b8c6c3f762c",
              "name": "analytics_campaign_utm",
              "type": "string",
              "value": "PageSpeedAudit"
            },
            {
              "id": "52e003be-4759-410e-aab8-8f0ca1f7e937",
              "name": "cwv_pass_thresholds",
              "type": "object",
              "value": "{ \t\"LCP_pass_threshold\" : 2500,  \t\"INP_pass_threshold\" : 200,  \t\"CLS_pass_threshold\" : 0.1,  \t\"FCP_pass_threshold\" : 1800,  \t\"TTFB_pass_threshold\" : 800,  \t\"TBT_pass_threshhold\": 200,  \t\"SI_pass_threshold\" : 3400,  }"
            },
            {
              "id": "a7377308-0513-46cf-9af3-51a7c1034322",
              "name": "report_column_names",
              "type": "array",
              "value": "[\"url\", \"HTTP_status_code\", \"download_size_KiB\", \"DOM_size\", \"lh_performance_score\", \"lh_FCP_score\", \"lh_FCP_result\", \"lh_LCP_score\", \"lh_LCP_result\",  \"lh_TBT_score\", \"lh_TBT_result\", \"lh_CLS_score\", \"lh_CLS_result\", \"lh_SI_score\", \"lh_SI_result\", \"cwv_LCP_score\", \"cwv_LCP_result\", \"cwv_INP_score\", \"cwv_INP_result\", \"cwv_CLS_score\", \"cwv_CLS_result\", \"cwv_FCP_score\", \"cwv_FCP_result\", \"cwv_TTFB_score\", \"cwv_TTFB_result\", \"audit_timestamp\", \"warnings_errors\"]"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "75d158f4-7a8f-4f54-8e72-2b183f242c2f",
      "name": "Process CSV file if uploaded, otherwise fetch the XML sitemap",
      "type": "n8n-nodes-base.if",
      "position": [
        -3360,
        -368
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "7b7cb9f5-e69b-4185-9923-433aa9d26e87",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $('Upload CSV').isExecuted }}",
              "rightValue": ""
            }
          ]
        }
      },
      "executeOnce": true,
      "typeVersion": 2.3
    },
    {
      "id": "ff85a5dc-2c67-4b2c-9da1-07ba68923caf",
      "name": "Run PageSpeed audit",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        -1680,
        -480
      ],
      "parameters": {
        "url": "https://www.googleapis.com/pagespeedonline/v5/runPagespeed",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "category",
              "value": "={{ $('Config').item.json.lighthouse_category }}"
            },
            {
              "name": "strategy",
              "value": "={{ $('Config').item.json.lighthouse_device }}"
            },
            {
              "name": "url",
              "value": "={{ $('Loop Over Items').item.json.urls}}"
            },
            {
              "name": "key",
              "value": "={{ $('Config').item.json.api_key }}"
            },
            {
              "name": "utm_campaign",
              "value": "={{ $('Config').item.json.analytics_campaign_utm }}"
            }
          ]
        }
      },
      "notesInFlow": false,
      "retryOnFail": false,
      "typeVersion": 4.4,
      "alwaysOutputData": false
    },
    {
      "id": "763262aa-153b-4191-9e73-f74acffb75c1",
      "name": "Extract URLs from file",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -2816,
        -544
      ],
      "parameters": {
        "include": "=",
        "options": {
          "destinationFieldName": "urls"
        },
        "fieldToSplitOut": "row"
      },
      "typeVersion": 1
    },
    {
      "id": "84edb909-f156-44a9-bd6f-b74e200ccd40",
      "name": "Extract data from file",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        -3024,
        -544
      ],
      "parameters": {
        "options": {
          "delimiter": ",",
          "headerRow": false,
          "readAsString": true
        },
        "binaryPropertyName": "={{ $('Upload CSV').item.binary.CSV_file_ }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "307c902f-c0af-4cfb-9933-5597e99041be",
      "name": "Create audit metadata",
      "type": "n8n-nodes-base.set",
      "position": [
        -3600,
        -368
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "8f8fb1da-a8a7-45c3-bbb1-a574bb0f12fb",
              "name": "audit_meta",
              "type": "object",
              "value": "={ \t\"device_tested\": \"{{ $('Config').item.json.lighthouse_device }}\",\t\"urls_processed\": 0, \t\"errors\": 0, \t\"audit_start_time\": \"{{ $now.toUTC().format('dd-MM-yyyy HH:mm') }}\", \t\"audit_finish_time\": \"\", \t\"final_audit_url\": \"https://docs.google.com/spreadsheets/d/{{ $('Pick default spreadsheet to save the report').item.json.spreadsheetId }}/?gid={{ $('Pick default spreadsheet to save the report').item.json.sheetId }}\", }"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "76256599-28e0-46c5-bba7-15855a04cd79",
      "name": "Increment error count meta",
      "type": "n8n-nodes-base.set",
      "position": [
        -1456,
        -352
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "517d61b3-0113-49ea-83f8-c17ce855bd5f",
              "name": "audit_meta.errors",
              "type": "number",
              "value": "={{ $('Create audit metadata').item.json.audit_meta.errors += 1 }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "1c6d32fb-afd6-4bd4-8d40-e51ed50b7a9d",
      "name": "Insert API audit data into sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -1632,
        -112
      ],
      "parameters": {
        "columns": {
          "value": {
            "url": "={{ $json.lighthouseResult.finalUrl }}",
            "DOM_size": "={{ $json.lighthouseResult.audits['dom-size-insight'].details.debugData.totalElements }}",
            "lh_SI_score": "={{ ($json.lighthouseResult.audits['speed-index'].numericValue / 1000).round(2)}}",
            "lh_CLS_score": "={{ $json.lighthouseResult.audits['cumulative-layout-shift'].numericValue.round(3) }}",
            "lh_FCP_score": "={{ ($json.lighthouseResult.audits['first-contentful-paint'].numericValue / 1000).round(2)}}",
            "lh_LCP_score": "={{ ($json.lighthouseResult.audits['largest-contentful-paint'].numericValue / 1000).round(2)}}",
            "lh_SI_result": "={{ $json.lighthouseResult.audits['speed-index'].numericValue <= $('Config').item.json.cwv_pass_thresholds.SI_pass_threshold ? \"Pass\" : \"Fail\"}}",
            "lh_TBT_score": "={{ $json.lighthouseResult.audits['total-blocking-time'].numericValue.round(0) }}",
            "cwv_CLS_score": "={{ $json.loadingExperience.metrics.CUMULATIVE_LAYOUT_SHIFT_SCORE.percentile }}",
            "cwv_FCP_score": "={{ $json.loadingExperience.metrics.FIRST_CONTENTFUL_PAINT_MS.percentile }}",
            "cwv_INP_score": "={{ $json.loadingExperience.metrics.INTERACTION_TO_NEXT_PAINT.percentile }}",
            "cwv_LCP_score": "={{ $json.loadingExperience.metrics.LARGEST_CONTENTFUL_PAINT_MS.percentile }}",
            "lh_CLS_result": "={{ $json.lighthouseResult.audits['cumulative-layout-shift'].numericValue <= $('Config').item.json.cwv_pass_thresholds.CLS_pass_threshold ? \"Pass\" : \"Fail\"}}",
            "lh_FCP_result": "={{ $json.lighthouseResult.audits['first-contentful-paint'].numericValue <= $('Config').item.json.cwv_pass_thresholds.FCP_pass_threshold ? \"Pass\" : \"Fail\" }}",
            "lh_LCP_result": "={{ $json.lighthouseResult.audits['largest-contentful-paint'].numericValue <= $('Config').item.json.cwv_pass_thresholds.LCP_pass_threshold ? \"Pass\" : \"Fail\"}}",
            "lh_TBT_result": "={{ $json.lighthouseResult.audits['total-blocking-time'].numericValue <= $('Config').item.json.cwv_pass_thresholds.TBT_pass_threshhold ? \"Pass\" : \"Fail\"}}",
            "cwv_CLS_result": "={{ $json.loadingExperience.metrics.CUMULATIVE_LAYOUT_SHIFT_SCORE.percentile <= $('Config').item.json.cwv_pass_thresholds.CLS_pass_threshold ? \"Pass\" : \"Fail\"}}",
            "cwv_FCP_result": "={{ $json.loadingExperience.metrics.FIRST_CONTENTFUL_PAINT_MS.percentile <= $('Config').item.json.cwv_pass_thresholds.FCP_pass_threshold ? \"Pass\" : \"Fail\"}}",
            "cwv_INP_result": "={{ $json.loadingExperience.metrics.INTERACTION_TO_NEXT_PAINT.percentile <= $('Config').item.json.cwv_pass_thresholds.INP_pass_threshold ? \"Pass\" : \"Fail\"}}",
            "cwv_LCP_result": "={{ $json.loadingExperience.metrics.LARGEST_CONTENTFUL_PAINT_MS.percentile <= $('Config').item.json.cwv_pass_thresholds.LCP_pass_threshold ? \"Pass\" : \"Fail\"}}",
            "cwv_TTFB_score": "={{ $json.loadingExperience.metrics.EXPERIMENTAL_TIME_TO_FIRST_BYTE.percentile }}",
            "audit_timestamp": "={{ $now.toUTC().format('dd-MM-yyyy HH:mm') }}",
            "cwv_TTFB_result": "={{ $json.loadingExperience.metrics.EXPERIMENTAL_TIME_TO_FIRST_BYTE.percentile <= $('Config').item.json.cwv_pass_thresholds.TTFB_pass_threshold ? \"Pass\" : \"Fail\"}}",
            "warnings_errors": "={{ $json.lighthouseResult.runWarnings.length > 0 ? $json.lighthouseResult.runWarnings : \"\" }}",
            "HTTP_status_code": "={{ $json.lighthouseResult.audits['network-requests'].details.items[0].statusCode }}",
            "download_size_KiB": "={{ ($json.lighthouseResult.audits['total-byte-weight'].numericValue / 1024).round(1)}}",
            "lh_performance_score": "={{ $json.lighthouseResult.categories.performance.score * 100}}"
          },
          "schema": [
            {
              "id": "url",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "url",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "HTTP_status_code",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "HTTP_status_code",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "download_size_KiB",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "download_size_KiB",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "DOM_size",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "DOM_size",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_performance_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_performance_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_FCP_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_FCP_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_FCP_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_FCP_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_LCP_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_LCP_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_LCP_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_LCP_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_TBT_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_TBT_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_TBT_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_TBT_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_CLS_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_CLS_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_CLS_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_CLS_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_SI_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_SI_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_SI_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_SI_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_LCP_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_LCP_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_LCP_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_LCP_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_INP_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_INP_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_INP_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_INP_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_CLS_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_CLS_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_CLS_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_CLS_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_FCP_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_FCP_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_FCP_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_FCP_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_TTFB_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_TTFB_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_TTFB_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_TTFB_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "audit_timestamp",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "audit_timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "warnings_errors",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "warnings_errors",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {
          "useAppend": true
        },
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Pick default spreadsheet to save the report').item.json.sheetId }}"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Pick default spreadsheet to save the report').item.json.spreadsheetId }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "e5e6eaed-5351-4eba-939f-3656d2878559",
      "name": "Wait between each batch",
      "type": "n8n-nodes-base.wait",
      "position": [
        -2032,
        -96
      ],
      "parameters": {
        "amount": 1
      },
      "typeVersion": 1.1
    },
    {
      "id": "9819d61c-ec29-4021-978d-d279fbe2b35a",
      "name": "Increment urls_processed meta",
      "type": "n8n-nodes-base.set",
      "position": [
        -1392,
        -112
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "c12d35fa-d746-4aa0-b9a8-224adf395488",
              "name": "urls_processed",
              "type": "number",
              "value": "={{ $('Create audit metadata').item.json.audit_meta.urls_processed += 1 }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "943ac022-083a-46fc-994d-b73c899ff98b",
      "name": "Update audit_finish_time meta",
      "type": "n8n-nodes-base.set",
      "position": [
        -1360,
        -560
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "78d94c01-c6ba-4d5b-8698-01c8dd653b93",
              "name": "audit_meta.audit_finish_time",
              "type": "string",
              "value": "={{ $now.toUTC().format('dd-MM-yyyy HH:mm') }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "cbaea255-e164-4f2a-aee5-6913fdbf9630",
      "name": "Insert error data into sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -1264,
        -352
      ],
      "parameters": {
        "columns": {
          "value": {
            "url": "={{ $('Run PageSpeed audit').item.json.urls }}",
            "audit_timestamp": "={{ $now.toUTC().format('dd-MM-yyyy HH:mm') }}",
            "warnings_errors": "={{ $('Run PageSpeed audit').item.json.error.message }}",
            "HTTP_status_code": "={{ $('Run PageSpeed audit').item.json.error.status }}"
          },
          "schema": [
            {
              "id": "url",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "url",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "HTTP_status_code",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "HTTP_status_code",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "download_size_KiB",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "download_size_KiB",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "DOM_size",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "DOM_size",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_performance_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_performance_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_FCP_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_FCP_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_FCP_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_FCP_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_LCP_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_LCP_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_LCP_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_LCP_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_TBT_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_TBT_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_TBT_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_TBT_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_CLS_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_CLS_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_CLS_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_CLS_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_SI_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_SI_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lh_SI_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lh_SI_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_LCP_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_LCP_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_LCP_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_LCP_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_INP_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_INP_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_INP_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_INP_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_CLS_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_CLS_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_CLS_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_CLS_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_FCP_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_FCP_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_FCP_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_FCP_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_TTFB_score",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_TTFB_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cwv_TTFB_result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cwv_TTFB_result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "audit_timestamp",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "audit_timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "warnings_errors",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "warnings_errors",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Pick default spreadsheet to save the report').item.json.sheetId }}"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Pick default spreadsheet to save the report').item.json.spreadsheetId }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "b4ca0c24-dac1-4a0e-b555-34a1de5066bd",
      "name": "Extract URLs",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -2416,
        -144
      ],
      "parameters": {
        "options": {
          "destinationFieldName": "urls"
        },
        "fieldToSplitOut": "loc"
      },
      "typeVersion": 1
    },
    {
      "id": "d645e845-6bfc-4bdb-acb7-fc5d84199407",
      "name": "Schedule Audit",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -4608,
        -592
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ]
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "650c9999-5813-4ba1-b07b-b2d7cdd0d85e",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4720,
        -832
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 864,
        "content": "## Ways to run this workflow:\n- On-demand via 'Upload CSV' node (URLs extracted from .csv file)\n- On a regular schedule via 'Schedule Audit' node (URLs fetched from XML sitemap)\n- Manually via 'Execute workflow' node (URLs fetched from XML sitemap)"
      },
      "typeVersion": 1
    },
    {
      "id": "467017ad-0252-422a-8697-d42bdc213e86",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -992,
        -768
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 432,
        "content": "## Send audit notification email\nOnce the audit finishes, this node builds a simple HTML email and sends it to your pre-defined address. \n\nEmail includes basic audit metadata and a link to the spreadsheet with full report."
      },
      "typeVersion": 1
    },
    {
      "id": "62cbddc7-3681-4bf2-a56f-4327ae38f6c2",
      "name": "Send audit summary email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -832,
        -560
      ],
      "parameters": {
        "sendTo": "> Insert your email here <",
        "message": "=<h2>Your PageSpeed audit has finished</h2>\n\n<h3>Here are the details:</h3>\n<h4>Device tested:</h4>\n<p>{{ $('Create audit metadata').item.json.audit_meta.device_tested }}</p>\n<h4>URLs processed successfully:</h4>\n<p>{{ $('Create audit metadata').item.json.audit_meta.urls_processed }}</p>\n<h4>URLs with errors:</h4>\n<p>{{ $('Create audit metadata').item.json.audit_meta.errors }}</p>\n<h4>Start time (UTC):</h4>\n<p>{{ $('Create audit metadata').item.json.audit_meta.audit_start_time }}</p>\n<h4>Finish time (UTC):</h4>\n<p>{{ $json.audit_meta.audit_finish_time }}</p>\n<br>\n<p>Full audit has been <a href=\"{{ $('Create audit metadata').item.json.audit_meta.final_audit_url }}\">saved here.</a></p>",
        "options": {},
        "subject": "PageSpeed audit complete"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 2.2
    },
    {
      "id": "3ff98180-dd60-4f7d-970e-27a3d85a499a",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5328,
        -1088
      ],
      "parameters": {
        "color": "#D3CD8D",
        "width": 528,
        "height": 1120,
        "content": "## What this workflow does\nThis workflow extracts URL addresses from either a CSV file or an XML sitemap, then uses PageSpeed API to get Lighthouse and Core Web Vitals metrics for each URL.\n\n\n### The final report contains the following columns (in order):\n\n- url\n- HTTP_status_code *(as returned by Lighthouse)*\n- download_size_KiB *(main document size in Kibibytes)*\n- DOM_size *(total number of DOM elements)*\n- lh_performance_score *(overall Lighthouse performance score,  0-100)*\n- lh_FCP_score *(notable CWV metric)*\n- lh_FCP_result *(Pass/Fail)*\n- lh_LCP_score *(main CWV metric)*\n- lh_LCP_result *(Pass/Fail)*\n- lh_TBT_score\n- lh_TBT_result *(Pass/Fail)*\n- lh_CLS_score *(main CWV metric)*\n- lh_CLS_result *(Pass/Fail)*\n- lh_SI_score\n- lh_SI_result *(Pass/Fail)*\n- cwv_LCP_score *(main CWV metric)*\n- cwv_LCP_result *(Pass/Fail)*\n- cwv_INP_score *(main CWV metric)*\n- cwv_INP_result *(Pass/Fail)*\n- cwv_CLS_score *(main CWV metric)*\n- cwv_CLS_result *(Pass/Fail)*\n- cwv_FCP_score *(notable CWV metric)*\n- cwv_FCP_result *(Pass/Fail)*\n- cwv_TTFB_score *(notable CWV metric)*\n- cwv_TTFB_result *(Pass/Fail)*\n- audit_timestamp *(in UTC timezone)*\n- warnings_errors *(warnings/error messages, if returned by Lighthouse)*\n\n\n**For brevity, the following acronyms are used in column names:**\n\n- **\"lh_\"** indicates Lighthouse lab data\n- **\"cwv_\"** indicates Core Web Vitals field data from CrUX (if available for a given URL)\n- **\"_score\"** indicates columns containing actual numeric values for each metric\n- **\"_result\"** indicates columns validating Pass/Fail results\n- **\"FCP\"** stands for First Contentful Paint\n- **\"LCP\"** stands for Largest Contentful Paint\n- **\"TBT\"** stands for Total Blocking Time\n- **\"CLS\"** stands for Cumulative Layout Shift\n- **\"SI\"** stands for Speed Index\n- **\"INP\"** stands for Interaction to Next Paint\n- **\"TTFB\"** stands for Time To First Byte\n"
      },
      "typeVersion": 1
    },
    {
      "id": "27877f97-f6d8-4c08-9a91-1c20ad37da9a",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4720,
        80
      ],
      "parameters": {
        "color": 5,
        "width": 384,
        "height": 208,
        "content": "## CSV file setup: \nThe CSV file should contain only a single column (no headers) with URLs separated by commas.\n\n**Format example:**\n\nhttps://firsturl.com,\nhttps://secondurl.com,\nhttps://thirdurl.com,\n..."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "d0c5d687-b697-4925-a884-c8f3eb6e6679",
  "connections": {
    "Config": {
      "main": [
        [
          {
            "node": "Pick default spreadsheet to save the report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload CSV": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract URLs": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Audit": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert to JSON": {
      "main": [
        [
          {
            "node": "Extract URL tags",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [
          {
            "node": "Update audit_finish_time meta",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Run PageSpeed audit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract URL tags": {
      "main": [
        [
          {
            "node": "Extract URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run PageSpeed audit": {
      "main": [
        [
          {
            "node": "Insert API audit data into sheet",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Increment error count meta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create audit metadata": {
      "main": [
        [
          {
            "node": "Process CSV file if uploaded, otherwise fetch the XML sitemap",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch the XML sitemap": {
      "main": [
        [
          {
            "node": "Convert to JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract URLs from file": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract data from file": {
      "main": [
        [
          {
            "node": "Extract URLs from file",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait between each batch": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Increment error count meta": {
      "main": [
        [
          {
            "node": "Insert error data into sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert error data into sheet": {
      "main": [
        [
          {
            "node": "Wait between each batch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Increment urls_processed meta": {
      "main": [
        [
          {
            "node": "Wait between each batch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update audit_finish_time meta": {
      "main": [
        [
          {
            "node": "Send audit summary email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add column headers to new sheet": {
      "main": [
        [
          {
            "node": "Create audit metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert API audit data into sheet": {
      "main": [
        [
          {
            "node": "Increment urls_processed meta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pick default spreadsheet to save the report": {
      "main": [
        [
          {
            "node": "Add column headers to new sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process CSV file if uploaded, otherwise fetch the XML sitemap": {
      "main": [
        [
          {
            "node": "Extract data from file",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Fetch the XML sitemap",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}