Awesome n8n templates: 280+ free workflows, organised by use case
A curated, continuously updated GitHub collection of free n8n templates spanning marketing, sales, content and operations automation.
The Automation Wing
An importable n8n flow that watches your sitemap, spots new or changed URLs and pings IndexNow so Bing and Yandex hear about a page the moment it ships.
Google finds out about a new page from Search Console or a crawl. Bing and Yandex, and everything else that speaks the protocol, can find out the moment you tell them, because IndexNow is a single API call, not a queue you wait in. One ping covers every participating engine at once. Most sites never send it, not because it is hard, but because nobody wired it up. This flow wires it up.
It polls your sitemap on a schedule, works out which URLs are new or have a changed lastmod since the last run, and pings the shared IndexNow endpoint with just those URLs. It reads the sitemap and it notifies an external API; it changes nothing on the site itself.
https://yourdomain.com/{key}.txt. Generate a key (any hex string will do) and publish the file before your first run, or every ping will fail verification.{
"name": "IndexNow Submitter",
"nodes": [
{
"parameters": {
"rule": { "interval": [ { "field": "hours", "hoursInterval": 1 } ] }
},
"id": "1",
"name": "Every Hour",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [220, 300]
},
{
"parameters": {
"values": {
"string": [
{ "name": "SITEMAP_URL", "value": "https://www.example.com/sitemap.xml" },
{ "name": "HOST", "value": "www.example.com" },
{ "name": "INDEXNOW_KEY", "value": "REPLACE_WITH_YOUR_KEY" },
{ "name": "KEY_LOCATION", "value": "https://www.example.com/REPLACE_WITH_YOUR_KEY.txt" }
]
}
},
"id": "2",
"name": "Config",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [440, 300]
},
{
"parameters": { "url": "={{$json.SITEMAP_URL}}", "options": {} },
"id": "3",
"name": "Get Sitemap",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [660, 300]
},
{
"parameters": { "mode": "xmlToJson", "options": {} },
"id": "4",
"name": "Parse XML",
"type": "n8n-nodes-base.xml",
"typeVersion": 1,
"position": [880, 300]
},
{
"parameters": {
"jsCode": "const urlset = $input.first().json.urlset || {};\nconst raw = urlset.url;\nconst entries = Array.isArray(raw) ? raw : (raw ? [raw] : []);\nreturn entries.map(e => ({ json: { loc: e.loc, lastmod: e.lastmod || '' } }));"
},
"id": "5",
"name": "Flatten URLs",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1100, 300]
},
{
"parameters": {
"jsCode": "const staticData = $getWorkflowStaticData('global');\nconst seen = staticData.seen || {};\nconst current = $input.all().map(i => i.json);\nconst changed = current.filter(c => seen[c.loc] !== c.lastmod);\ncurrent.forEach(c => { seen[c.loc] = c.lastmod; });\nstaticData.seen = seen;\nreturn [{ json: { urls: changed.map(c => c.loc), count: changed.length } }];"
},
"id": "6",
"name": "Diff Against Last Seen",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1320, 300]
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": true, "typeValidation": "strict" },
"conditions": [
{
"leftValue": "={{$json.count}}",
"rightValue": 0,
"operator": { "type": "number", "operation": "gt" }
}
],
"combinator": "and"
}
},
"id": "7",
"name": "Has New URLs?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [1540, 300]
},
{
"parameters": {
"url": "https://api.indexnow.org/indexnow",
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { host: $('Config').first().json.HOST, key: $('Config').first().json.INDEXNOW_KEY, keyLocation: $('Config').first().json.KEY_LOCATION, urlList: $json.urls } }}",
"options": {}
},
"id": "8",
"name": "Ping IndexNow",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1760, 220],
"executeOnce": true
},
{
"parameters": {},
"id": "9",
"name": "Log Result",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1980, 220]
}
],
"connections": {
"Every Hour": { "main": [[{ "node": "Config", "type": "main", "index": 0 }]] },
"Config": { "main": [[{ "node": "Get Sitemap", "type": "main", "index": 0 }]] },
"Get Sitemap": { "main": [[{ "node": "Parse XML", "type": "main", "index": 0 }]] },
"Parse XML": { "main": [[{ "node": "Flatten URLs", "type": "main", "index": 0 }]] },
"Flatten URLs": { "main": [[{ "node": "Diff Against Last Seen", "type": "main", "index": 0 }]] },
"Diff Against Last Seen": { "main": [[{ "node": "Has New URLs?", "type": "main", "index": 0 }]] },
"Has New URLs?": { "main": [[{ "node": "Ping IndexNow", "type": "main", "index": 0 }], []] },
"Ping IndexNow": { "main": [[{ "node": "Log Result", "type": "main", "index": 0 }]] }
},
"active": false,
"settings": { "executionOrder": "v1" }
}
SITEMAP_URL, HOST, INDEXNOW_KEY and KEY_LOCATION to your own values."Ping IndexNow" is set to run once per execution, not once per URL, because the whole point is a single call carrying the full urlList array; IndexNow accepts up to 10,000 URLs in one request. A 200 response means the engines have been notified, not that they have crawled or indexed anything yet, IndexNow is a discovery signal, not a guarantee. If "Has New URLs?" routes to the empty branch, nothing changed since the last run and no call is made, which is the expected steady state most hours.
One honest limitation: this only catches what the sitemap reflects. A page that never makes it into the sitemap, or a CMS that regenerates the sitemap on a delay, will not trigger a ping until the sitemap itself updates. If your own publish flow can call the IndexNow endpoint directly at the moment of publish, that is faster than this sweep; treat this flow as the safety net that catches everything else.
More Like This
A curated, continuously updated GitHub collection of free n8n templates spanning marketing, sales, content and operations automation.
An importable n8n flow that reads Meta account insights daily, compares yesterday against the trailing week and posts to Slack when spend swings beyond your threshold.
A weekly n8n workflow that pulls the last 28 days of Search Console clicks against the 28 before, writes the biggest query winners and losers to a Google Sheet, and emails the top movers.