{
  "id": "3qFkempg015uG0dB",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Find internal linking opportunities using SerAPI and Google Sheets",
  "tags": [],
  "nodes": [
    {
      "id": "8c2aa4ad-94d0-4c52-b6bb-bee296a4420b",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        480,
        144
      ],
      "parameters": {
        "options": {},
        "batchSize": 5
      },
      "typeVersion": 3
    },
    {
      "id": "7fccd9bf-8f71-4adf-97e4-2eff330d757f",
      "name": "Get search results using SerpAPI",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        832,
        160
      ],
      "parameters": {
        "url": "https://serpapi.com/search",
        "options": {},
        "sendQuery": true,
        "authentication": "predefinedCredentialType",
        "queryParameters": {
          "parameters": [
            {
              "name": "q",
              "value": "={{ $json.Keyword }} site:{{ $json.Domain }} -inurl:{{ $json.URL }}"
            }
          ]
        },
        "nodeCredentialType": "serpApi"
      },
      "credentials": {
        "serpApi": {
          "name": "<your credential>"
        },
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "420bdbbe-a304-4bfe-8cd8-54ab467a6d71",
      "name": "Extract links from JSON",
      "type": "n8n-nodes-base.set",
      "position": [
        1184,
        160
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "eae05e00-7188-4cca-994f-0cdf091e5618",
              "name": "URL",
              "type": "string",
              "value": "={{ $('Loop Over Items').item.json.URL }}"
            },
            {
              "id": "1e7647fb-a137-4971-8f8c-5f0709d0f92f",
              "name": "organic_results",
              "type": "array",
              "value": "={{ $json.organic_results.map(item => item.link) }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "44ca48a2-fae4-4de4-a5d7-e4d16960b914",
      "name": "Add internal URL to Google Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1536,
        160
      ],
      "parameters": {
        "columns": {
          "value": {
            "URL": "={{ $json.URL }}",
            "Internal link 1": "={{ $json.organic_results[0] ? $json.organic_results[0] : \"N/A\"}}",
            "Internal link 2": "={{ $json.organic_results[1] ? $json.organic_results[1] : \"N/A\"}}",
            "Internal link 3": "={{ $json.organic_results[2] ? $json.organic_results[2] : \"N/A\"}}",
            "Internal link 4": "={{ $json.organic_results[3] ? $json.organic_results[3] : \"N/A\"}}",
            "Internal link 5": "={{ $json.organic_results[4] ? $json.organic_results[4] : \"N/A\"}}",
            "Internal link 6": "={{ $json.organic_results[5] ? $json.organic_results[5] : \"N/A\"}}",
            "Internal link 7": "={{ $json.organic_results[6] ? $json.organic_results[6] : \"N/A\"}}",
            "Internal link 8": "={{ $json.organic_results[7] ? $json.organic_results[7] : \"N/A\"}}",
            "Internal link 9": "={{ $json.organic_results[8] ? $json.organic_results[8] : \"N/A\"}}",
            "Internal link 10": "={{ $json.organic_results[9] ? $json.organic_results[9] : \"N/A\"}}"
          },
          "schema": [
            {
              "id": "Domain",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Domain",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "URL",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Keyword",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Keyword",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Internal link 1",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Internal link 1",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Internal link 2",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Internal link 2",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Internal link 3",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Internal link 3",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Internal link 4",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Internal link 4",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Internal link 5",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Internal link 5",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Internal link 6",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Internal link 6",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Internal link 7",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Internal link 7",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Internal link 8",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Internal link 8",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Internal link 9",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Internal link 9",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Internal link 10",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Internal link 10",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "URL"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1U0whmy1d7wPoWTNTa1u68QrLTE1NqYgyyFXxARWo_1M/edit#gid=0",
          "cachedResultName": "Internal links"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1U0whmy1d7wPoWTNTa1u68QrLTE1NqYgyyFXxARWo_1M",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1U0whmy1d7wPoWTNTa1u68QrLTE1NqYgyyFXxARWo_1M/edit?usp=drivesdk",
          "cachedResultName": "Find internal links using SerpAPI - N8N demo"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "172e75f7-9698-4c87-819e-edf94ea20612",
      "name": "Get URLs and keywords",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        128,
        144
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupColumn": "Internal link 1"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1U0whmy1d7wPoWTNTa1u68QrLTE1NqYgyyFXxARWo_1M/edit#gid=0",
          "cachedResultName": "Internal links"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1U0whmy1d7wPoWTNTa1u68QrLTE1NqYgyyFXxARWo_1M",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1U0whmy1d7wPoWTNTa1u68QrLTE1NqYgyyFXxARWo_1M/edit?usp=drivesdk",
          "cachedResultName": "Find internal links using SerpAPI - N8N demo"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "1d1a37d8-8b2b-4399-9abe-13dff9cc1ad2",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16,
        -320
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 704,
        "content": "## Get URLs and keywords\nIn this node, we're getting the different URLs that we want suggestions for, and the keywords/topics those pages are targeting.\n\nWe're filtering out everything that already has a value in the `internal link 1` column so we don't override rows we already processed in a previous run\n"
      },
      "typeVersion": 1
    },
    {
      "id": "2f549918-a4ca-4a99-b60a-94b903c7f994",
      "name": "Manual trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -112,
        144
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "36b666a5-613b-43bc-a940-b5bb546bdbfa",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        -320
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 704,
        "content": "## Loop over URLs\nLoop over each URL to get the results. Here we're doing it in batches of 5\n\n:warning: Note :warning:\nDon't set the batch size too high or you'll start hitting rate limits for some of the APIs, especially Google Sheets. \n\nIf you have a small number of URLs to process you can leave it at five. If you have a larger set of URLs consider setting the batch size lower, or adding a wait node at the end.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "3dc9bb5d-03c4-423b-96d0-120badad6810",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        720,
        -320
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 704,
        "content": "## Get search results using SerpAPI\nGet the search results from SerpAPI.\n\nWe're searching for the target keyword, while doing a `site:<domain>` search so we only get results from our site.\n\nWe use `-inurl:<url>` to exclude the URL we are currently working with. This gives us results similar to the URL we have that we can building internal links from.\n\n:warning: Note :warning:\nIf you have to process a large set of URLs it's worth [setting up a programmable search engine](https://drlee.io/build-your-own-google-create-a-custom-search-engine-with-trusted-sources-c1c113e845cc) instead of using SerAPI\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "75af678f-5af2-4aa7-8f00-d6fc84ee18e6",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1072,
        -320
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 704,
        "content": "## Extract organic links from JSON\nSerpAPI gives us a bunch of data we don't need for this use case so we just filter for the organic results. specifically, we filter the URLs it gives us\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "72d62473-fab0-4db5-92f8-4cac1ab9acb0",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1424,
        -320
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 704,
        "content": "## Update Google Sheet\nHere we're adding the URLs to the different column in our sheet.\n\nNote that we're using an inline `if` statement so, if there's no URL present, it just adds 'N/A' to our Google Sheet. Not all URLs will have 10 internal link opportunities\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "e229c5a9-44c9-44c5-af81-ca2859283831",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -592,
        -320
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 704,
        "content": "## Overview\n\nUse this workflow to spot internal linking ideas on your site and improve your search performance. It takes your target URLs and keywords, finds related pages, and suggests where to add links. Strong internal linking helps search engines understand your site.\n\n## How it works\n- You provide a list of target URLs and the keywords you want to rank for\n- The workflow uses the SERP API to search your site for related pages, skipping the target URL\n- It filters the results and pulls relevant URLs\n- It writes the suggestions to a Google Sheet, and adds \u201cN/A\u201d if no good matches are found\n\n## Setup steps\n1. Turn on the Google Sheets API and create a sheet with your domain, target URLs, and keywords\n2. Create a SERP API account and get an API key\n3. Optional: Set up a Google Programmable Search Engine if you prefer not to use the SERP API\n4. Add your SERP API key and Google Sheets credentials to n8n.\n5. Run the workflow to generate internal link suggestions in your Google Sheet"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "5d62c2bc-3ff0-4b0c-8af5-76d8fe285226",
  "connections": {
    "Manual trigger": {
      "main": [
        [
          {
            "node": "Get URLs and keywords",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [],
        [
          {
            "node": "Get search results using SerpAPI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get URLs and keywords": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract links from JSON": {
      "main": [
        [
          {
            "node": "Add internal URL to Google Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add internal URL to Google Sheet": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get search results using SerpAPI": {
      "main": [
        [
          {
            "node": "Extract links from JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}