How to Auto-Submit Your Website to Search Engines with IndexNow

A step-by-step guide to setting up IndexNow with a Cloudflare Worker. No terminal required.

The problem

When you publish a new page or blog post, search engines find out about it when they next crawl your site. That can take days or weeks.

IndexNow is a protocol supported by Bing, Yandex, Naver, and Seznam that lets you notify these search engines when your content changes. Instead of waiting for a crawler, you push the update directly. Google is not part of the IndexNow protocol and uses a separate Indexing API.

This guide walks through setting up a Cloudflare Worker that runs once a day, checks your sitemap for new URLs, and submits them to IndexNow. The entire setup happens in the Cloudflare dashboard, no terminal needed.

The setup works for any website with a sitemap. If you are on Framer, there are two extra considerations. These are called out in the relevant steps.

What you need

  • Any website with a sitemap at yoursite.com/sitemap.xml

  • A Cloudflare account (free plan is sufficient)

  • Your domain's DNS managed by Cloudflare

If your DNS is not yet on Cloudflare, add your domain and update the nameservers at your registrar first. Worker Routes only work on domains managed through Cloudflare.

Step 1: Generate your IndexNow key

Go to indexnow.org/generatekey and generate a key. Save it. You will need it several times in this guide.

Your key will look like this: YOUR_INDEXNOW_KEY

Step 2: Create a KV Namespace

The Worker uses Cloudflare KV to store a snapshot of your sitemap URLs. On each run it compares the current sitemap against the snapshot and only submits new URLs. This prevents submitting the same URLs to IndexNow every day.

  1. In the Cloudflare dashboard go to Workers & Pages → KV

  2. Click Create a namespace

  3. Name it SITEMAP_KV

  4. Click Add

Step 3: Create the Worker

  1. Go to Workers & Pages → Create

  2. Click Create a Worker

  3. Name it indexnow-yoursite (or any name you prefer)

  4. Click Deploy

  5. Click Edit code

  6. Delete all existing code and paste the following:

const SITEMAP_URL = "https://yoursite.com/sitemap.xml";
const INDEXNOW_HOST = "yoursite.com";
const KV_KEY = "last_urls";
<p>export default {<br>async fetch(request, env) {<br>const url = new URL(request.url);</p>
<pre><code>// Serve IndexNow key file
if (url.pathname === `/${env.INDEXNOW_KEY}.txt`) {
  return new Response(env.INDEXNOW_KEY, {
    headers: { &quot;Content-Type&quot;: &quot;text/plain&quot; }
  });
}

// Manual trigger for testing
const result = await run(env);
return new Response(JSON.stringify(result, null, 2), {
  headers: { &quot;Content-Type&quot;: &quot;application/json&quot; }
});
</code></pre>
<p>},</p>
<p>async scheduled(event, env) {<br>await run(env);<br>}<br>};</p>
<p>async function run(env) {<br>const sitemapRes = await fetch(SITEMAP_URL);<br>if (!sitemapRes.ok) {<br>return { error: <code>Sitemap fetch failed: ${sitemapRes.status}</code> };<br>}<br>const xml = await sitemapRes.text();</p>
<p>const currentUrls = [...xml.matchAll(/<loc>(.*?)</loc>/g)]<br>.map(m => m[1].trim())<br>.filter(url => url.startsWith("https://"));</p>
<p>if (currentUrls.length === 0) {<br>return { error: "No URLs found in sitemap" };<br>}</p>
<p>const stored = await env.SITEMAP_KV.get(KV_KEY);<br>const previousUrls = stored ? JSON.parse(stored) : [];</p>
<p>const previousSet = new Set(previousUrls);<br>const newUrls = currentUrls.filter(url => !previousSet.has(url));</p>
<p>let submitResult = null;<br>if (newUrls.length > 0) {<br>const res = await fetch("<a href="https://api.indexnow.org/indexnow" data-framer-link="Link:{"url":"https://api.indexnow.org/indexnow","type":"url"}">https://api.indexnow.org/indexnow</a>", {<br>method: "POST",<br>headers: { "Content-Type": "application/json" },<br>body: JSON.stringify({<br>host: INDEXNOW_HOST,<br>key: env.INDEXNOW_KEY,<br>keyLocation: <code>https://${INDEXNOW_HOST}/${env.INDEXNOW_KEY}.txt</code>,<br>urlList: newUrls<br>})<br>});<br>submitResult = { status: res.status, urls: newUrls };<br>}</p>
<p>await env.SITEMAP_KV.put(KV_KEY, JSON.stringify(currentUrls));</p>


Replace yoursite.com in the first two lines with your actual domain.

Note: The regex parser works with flat sitemaps. If your site uses a sitemap index file (a sitemap that points to child sitemaps), the Worker will not follow the child URLs. Most Framer sites generate a single flat sitemap. If yours does not, you will need to add a step that fetches and parses each child sitemap.

  1. Click Save and Deploy

Step 4: Bind the KV Namespace

  1. Go to your Worker → Settings → Bindings → Add binding

  2. Select KV Namespace

  3. Variable name: SITEMAP_KV

  4. KV Namespace: select SITEMAP_KV

  5. Click Add Binding

Step 5: Add the IndexNow key as a secret

  1. Go to Settings → Variables and Secrets → Add variable

  2. Type: Secret

  3. Variable name: INDEXNOW_KEY

  4. Value: paste your IndexNow key from Step 1

  5. Click Deploy

Step 6: Set up DNS and the Worker Route

IndexNow requires a verification file accessible at yoursite.com/YOUR_KEY.txt. The Worker already handles serving this file. You just need to route requests through it.

DNS setup

Worker Routes only intercept traffic that passes through Cloudflare's proxy. You need at least one proxied (orange cloud) DNS record on your domain.

Most sites: Your root A record is already proxied by default in Cloudflare. Check your DNS records and confirm the orange cloud is on for yourdomain.com.

Framer sites: Framer requires root A records to be DNS only for its domain verification to work. This means Cloudflare cannot intercept requests on your root domain. The fix is a proxied wildcard record:

  • Type: A

  • Name: *

  • Content: your Framer site IP (visible in Framer → Custom Domain → DNS records)

  • Proxy: orange cloud (proxied)

Check if this wildcard already exists in your Cloudflare DNS. If you migrated from another provider it may already be there. Framer rejects SSL connections for unclaimed subdomains, so no content can be served under your domain by anyone else.

Worker Route

  1. Go to Cloudflare → your domain → Workers Routes → Add route

  2. Route: yoursite.com/YOUR_KEY*

  3. Worker: select your Worker

  4. Save

Verify it works by opening https://yoursite.com/YOUR_KEY.txt in your browser. It should return just the key string as plain text.

Step 7: Add the cron trigger

  1. Go to Worker → Settings → Triggers → Cron Triggers → Add

  2. Add: 0 12 * * * (runs daily at noon UTC)

  3. Save

For sites that publish multiple times per day, add a second trigger at 0 8 * * *. The free plan allows up to 3 cron triggers per Worker.

Step 8: Test

  1. Go to Worker → Observability → Begin log stream

  2. Enable the workers.dev domain under Domains & Routes if it shows as Inactive

  3. In another tab open your Worker's workers.dev URL

  4. Check the logs. You should see a JSON response like this:

{
  "timestamp": "2026-04-01T12:17:12.135Z",
  "totalUrls": 13,
  "newUrls": 13,
  "submitted": {
    "status": 202,
    "urls": [
      "https://yoursite.com/",
      "https://yoursite.com/about",
      "..."
    ]
  }
}

Status 202 means IndexNow accepted the submission. Run it a second time. You should see "submitted": "nothing new", confirming the diff is working correctly.

After testing, consider disabling the workers.dev domain again under Domains & Routes. Anyone who visits the URL triggers a full sitemap check, which is harmless but unnecessary.

How it works after setup

Every day at noon UTC the Worker:

  1. Fetches your sitemap

  2. Compares it against the KV snapshot from the previous run

  3. Submits only new URLs to IndexNow

  4. Updates the KV snapshot

On first run it submits everything. From the second run onward it only submits pages added since the last run. If nothing is new, no request is sent to IndexNow at all.

Cost

Everything in this guide runs on Cloudflare's free plan:

  • Workers: 100,000 requests/day. One daily cron run uses 365/year.

  • KV: 1GB storage, 100,000 reads/day. A sitemap snapshot is a few KB.

  • Cron triggers: 3 per Worker on the free plan.

Total cost: zero.

What this covers

IndexNow notifies Bing, Yandex, Naver, and Seznam. Google is not part of the IndexNow protocol. For most content and business sites, cutting Bing and Yandex indexing time from days or weeks to hours is worth the 20 minutes this setup takes.