{
  "name": "Automate app store rank tracking on Google Play into Google Sheets",
  "nodes": [
    {
      "id": "a2d5e649-0001-4b01-c001-000000000001",
      "name": "Every Monday morning",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        0,
        520
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "a2d5e649-0002-4b02-c002-000000000002",
      "name": "Set the keywords to track",
      "type": "n8n-nodes-base.set",
      "position": [
        260,
        520
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a2d5e649-0011-4b11-c011-+1234567890",
              "name": "keywords",
              "type": "array",
              "value": "[\"habit tracker\", \"daily planner\"]"
            },
            {
              "id": "a2d5e649-0012-4b12-c012-+1234567890",
              "name": "yourPackageName",
              "type": "string",
              "value": "org.isoron.uhabits"
            },
            {
              "id": "a2d5e649-0013-4b13-c013-+1234567890",
              "name": "appsPerKeyword",
              "type": "number",
              "value": "20"
            },
            {
              "id": "a2d5e649-0014-4b14-c014-+1234567890",
              "name": "country",
              "type": "string",
              "value": "us"
            },
            {
              "id": "a2d5e649-0015-4b15-c015-+1234567890",
              "name": "language",
              "type": "string",
              "value": "en"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a2d5e649-0003-4b03-c003-000000000003",
      "name": "One search per keyword",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        520,
        520
      ],
      "parameters": {
        "include": "allOtherFields",
        "options": {},
        "fieldToSplitOut": "keywords"
      },
      "typeVersion": 1
    },
    {
      "id": "a2d5e649-0004-4b04-c004-000000000004",
      "name": "Search the Play Store",
      "type": "@apify/n8n-nodes-apify.apify",
      "position": [
        780,
        520
      ],
      "parameters": {
        "memory": 1024,
        "actorId": "johnvc~google-play-api",
        "resource": "Actors",
        "operation": "Run actor and get dataset",
        "customBody": "={{ JSON.stringify({ search_mode: 'search', query: $json.keywords, max_results: $json.appsPerKeyword, country: $json.country, language: $json.language }) }}"
      },
      "typeVersion": 1
    },
    {
      "id": "a2d5e649-0005-4b05-c005-000000000005",
      "name": "Keep rows that are real apps",
      "type": "n8n-nodes-base.filter",
      "position": [
        1040,
        520
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "a2d5e649-0041-4b41-c041-+1234567890",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.resultType }}",
              "rightValue": "app"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "a2d5e649-0006-4b06-c006-000000000006",
      "name": "One row per ranked app",
      "type": "n8n-nodes-base.code",
      "position": [
        1300,
        520
      ],
      "parameters": {
        "jsCode": "// One row per ranked app, with your own app flagged so the sheet reads as a\n// leaderboard rather than a list. Play search returns apps in rank order, so\n// position is the ranking for that keyword on that day.\nconst cfg = $('Set the keywords to track').first().json;\nconst yours = String(cfg.yourPackageName || '').trim().toLowerCase();\nconst weekOf = $now.toFormat('yyyy-MM-dd');\n\nconst clip = (s, n) => {\n  const v = String(s || '').replace(/\\s+/g, ' ').trim();\n  return v.length > n ? v.slice(0, n - 3) + '...' : v;\n};\n\nreturn $input.all().map((item) => {\n  const a = item.json;\n  const pkg = String(a.productId || '');\n  return {\n    json: {\n      'Week of': weekOf,\n      Keyword: a.query || '',\n      Rank: a.position ?? '',\n      App: clip(a.title, 120),\n      'Is your app': yours !== '' && pkg.toLowerCase() === yours ? 'YES' : '',\n      Developer: clip(a.developer, 120),\n      Category: a.category || '',\n      Rating: typeof a.rating === 'number' ? a.rating : '',\n      Downloads: a.downloads || '',\n      'Package name': pkg,\n      'Play listing': a.url || '',\n    },\n  };\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "a2d5e649-0007-4b07-c007-000000000007",
      "name": "Append rankings to sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1560,
        520
      ],
      "parameters": {
        "columns": {
          "value": {},
          "mappingMode": "autoMapInputData",
          "matchingColumns": []
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "a2d5e649-0090-4b90-c090-000000000090",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -620,
        40
      ],
      "parameters": {
        "width": 520,
        "height": 1240,
        "content": "## Automate app store rank tracking on Google Play into Google Sheets\n\nNo ASO tool subscription and no per-seat licence. Every week this searches the Play Store for the keywords you care about and appends one row per ranked app to your sheet: position, app, developer, category, rating, download band and the package name, with your own app flagged YES.\n\nBecause it records the whole ranked list and not just your position, the sheet doubles as a competitor watch: you can see exactly who moved above you and what their rating looks like.\n\n**Who's it for**\nAndroid developers watching keyword rankings after a listing change, ASO consultants reporting to clients, and anyone who wants the raw positions rather than a dashboard they rent.\n\n**How it works**\n1. A schedule trigger fires once a week\n2. One Set node holds your keywords, your package name, and the storefront\n3. The Apify node runs one Play Store search per keyword\n4. A Code node turns each result into a row and flags your own app\n\n**Setup**\n1. Create a free Apify account and add your API token to the Apify credential\n2. Connect your Google account in the Sheets node and pick a spreadsheet\n3. Replace the example keywords, and set your package name, the one after id= in your Play Store URL\n\n**Good to know**\nUnder a tenth of a cent per app returned, so three keywords at 20 apps each is about 5 cents a week.\nPlay rankings are per country, so change the storefront and you are tracking a different market entirely.\nActor: https://apify.com/johnvc/google-play-api?fpr=9n7kx3&fp_sid=n8n"
      },
      "typeVersion": 1
    },
    {
      "id": "a2d5e649-0091-4b91-c091-000000000091",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -40,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 240,
        "content": "### 1. Schedule\nRuns every Monday morning. Change the cadence, or swap in a manual trigger while you test."
      },
      "typeVersion": 1
    },
    {
      "id": "a2d5e649-0092-4b92-c092-000000000092",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        220,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 240,
        "content": "### 2. Your input\nKeywords, your package name, and the storefront country. This is the only node you edit."
      },
      "typeVersion": 1
    },
    {
      "id": "a2d5e649-0093-4b93-c093-000000000093",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 240,
        "content": "### 3. Fan out\nTurns your keyword list into one Play Store search each."
      },
      "typeVersion": 1
    },
    {
      "id": "a2d5e649-0094-4b94-c094-000000000094",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        740,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 240,
        "content": "### 4. Search\nRuns the Google Play API actor for one keyword. Add your Apify credential here."
      },
      "typeVersion": 1
    },
    {
      "id": "a2d5e649-0095-4b95-c095-000000000095",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1000,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 240,
        "content": "### 5. Clean\nKeeps only real app rows, so an upstream error never reaches the sheet."
      },
      "typeVersion": 1
    },
    {
      "id": "a2d5e649-0096-4b96-c096-000000000096",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1260,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 240,
        "content": "### 6. Shape\nOne row per ranked app. Your own package name is flagged YES."
      },
      "typeVersion": 1
    },
    {
      "id": "a2d5e649-0097-4b97-c097-000000000097",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1520,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 240,
        "content": "### 7. Deliver\nPick your spreadsheet and tab. Columns map automatically."
      },
      "typeVersion": 1
    },
    {
      "id": "a2d5e649-0080-4b80-c080-000000000080",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        220,
        860
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 240,
        "content": "### Where do I find my package name?\nIt is the value after `id=` in your Play Store URL, for example `com.spotify.music`. Paste it into the config node and your own rows get flagged YES so you can filter the sheet down to your rank history."
      },
      "typeVersion": 1
    },
    {
      "id": "a2d5e649-0081-4b81-c081-000000000081",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        660,
        860
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 240,
        "content": "### Rankings are per storefront\nPlay results differ by country, so `us` and `de` are two different rank histories. Duplicate the workflow per market rather than mixing them in one sheet, or add the country to the row so you can pivot on it."
      },
      "typeVersion": 1
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Every Monday morning": {
      "main": [
        [
          {
            "node": "Set the keywords to track",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search the Play Store": {
      "main": [
        [
          {
            "node": "Keep rows that are real apps",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "One row per ranked app": {
      "main": [
        [
          {
            "node": "Append rankings to sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "One search per keyword": {
      "main": [
        [
          {
            "node": "Search the Play Store",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set the keywords to track": {
      "main": [
        [
          {
            "node": "One search per keyword",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Keep rows that are real apps": {
      "main": [
        [
          {
            "node": "One row per ranked app",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}