To use IndexNow on a Next.js or Laravel site, host a key file at your domain’s root, then send a POST request to an IndexNow endpoint with the URLs that changed whenever content is published, updated or deleted. One request can carry up to 10,000 URLs, and participating search engines, including Bing, Yandex, Seznam and Naver, share the notification with each other. Google does not use IndexNow. The code below shows both frameworks, with the error codes to handle.
How the protocol works
- Create a key8 to 128 characters: letters, numbers, dashes.
- Host the key filehttps://www.example.com/{key}.txt containing the key.
- Send changed URLsPOST to api.indexnow.org or a search engine’s endpoint.
- Handle the response200 or 202 means received.
A notification tells search engines that a URL changed. It does not guarantee crawling or indexing. More background in our IndexNow guide.
The request
POST https://api.indexnow.org/indexnow
Content-Type: application/json; charset=utf-8
{
"host": "www.example.com",
"key": "your-key-here",
"keyLocation": "https://www.example.com/your-key-here.txt",
"urlList": [
"https://www.example.com/services/geo/",
"https://www.example.com/blog/new-post/"
]
}
| Status | Meaning |
|---|---|
| 200 or 202 | Received |
| 400 | Invalid request format |
| 403 | Key not valid, or the key file was not found |
| 422 | URLs do not belong to the host, or the key does not match |
| 429 | Too many requests; slow down |
Next.js (App Router)
Place the key file in the public folder, for example public/your-key-here.txt, containing only the key. Then add a small server-side helper and call it wherever content changes, such as the webhook your CMS calls on publish:
// lib/indexnow.js (server only) export async function submitToIndexNow(urls) { const host = "www.example.com"; const key = process.env.INDEXNOW_KEY; const res = await fetch("https://api.indexnow.org/indexnow", { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8" }, body: JSON.stringify({ host, key, keyLocation: `https://${host}/${key}.txt`, urlList: urls.slice(0, 10000), }), }); if (![200, 202].includes(res.status)) { console.error("IndexNow failed", res.status); } return res.status; } // app/api/revalidate/route.js: called by the CMS on publish import { revalidatePath } from "next/cache"; import { submitToIndexNow } from "@/lib/indexnow"; export async function POST(request) { if (request.headers.get("x-webhook-secret") !== process.env.WEBHOOK_SECRET) { return new Response("Unauthorized", { status: 401 }); } const { path } = await request.json(); revalidatePath(path); await submitToIndexNow([`https://www.example.com${path}`]); return Response.json({ ok: true }); }
Protect the webhook with a secret, as above, so nobody else can trigger submissions.
Laravel
Serve the key file from the public directory, then submit URLs from a queued job triggered by a model observer, so publishing does not wait for the request:
// app/Jobs/SubmitToIndexNow.php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; class SubmitToIndexNow implements ShouldQueue { use Queueable; public function __construct(public array $urls) {} public function handle(): void { $host = parse_url(config('app.url'), PHP_URL_HOST); $key = config('services.indexnow.key'); $response = Http::asJson()->post('https://api.indexnow.org/indexnow', [ 'host' => $host, 'key' => $key, 'keyLocation' => "https://{$host}/{$key}.txt", 'urlList' => array_slice($this->urls, 0, 10000), ]); if (! in_array($response->status(), [200, 202])) { Log::warning('IndexNow failed', ['status' => $response->status()]); } } } // app/Observers/PostObserver.php public function saved(Post $post): void { if ($post->isPublished() && $post->wasChanged(['title', 'body', 'slug'])) { SubmitToIndexNow::dispatch([route('blog.show', $post)]); } }
Checking wasChanged on the content fields avoids submitting URLs when only a view counter or a timestamp changed.
Good practice
- Submit a URL only when its content changes. Repeatedly submitting unchanged URLs wastes the signal.
- Also submit URLs you delete, so search engines drop them sooner.
- Batch bulk changes, such as a site-wide update, into one request of up to 10,000 URLs.
- Keep an accurate XML sitemap as well. Google does not use IndexNow and relies on sitemaps and crawling.
- Check submissions in Bing Webmaster Tools, which shows URLs received through IndexNow.
Frequently asked questions
Which endpoint should I use?
api.indexnow.org forwards to participating search engines. You can also post to a specific engine’s endpoint; it shares the notification with the others.
Is IndexNow worth it if Google ignores it?
For AI visibility, yes: Bing’s index feeds Microsoft Copilot and is one of the sources other assistants can draw on.
We use WordPress. Do we need code?
No. Microsoft’s IndexNow plugin for WordPress does this automatically.
Sources
- IndexNow protocol documentation, indexnow.org
- Bing Webmaster Tools, IndexNow documentation
- Next.js documentation, route handlers and revalidatePath
- Laravel documentation, HTTP client, queues and observers