laravel-freelancer-nl/laravel-index-now
Instantly notify search engines about new and updated content in your Laravel app using the IndexNow protocol. Submit URLs to Bing, Yandex, and other IndexNow-enabled engines with a simple, fluent API and automatic integration.
Install and publish the config, then set your IndexNow key:
composer require laravel-freelancer-nl/laravel-index-now
php artisan vendor:publish --tag="index-now-config"
INDEXNOW_KEY=your-8-to-128-char-key
INDEXNOW_KEY_LOCATION=/your-8-to-128-char-key.txt
APP_URL=https://example.com
The key must be 8–128 characters of a-z, A-Z, 0-9 and hyphens, and it must be reachable as plain text at https://your-site.com/{key}.txt with a 200 and no redirect. The package provides a route/command for serving this file — check php artisan list for the index-now namespace and confirm the file resolves on the public domain before submitting anything.
As of v2.1.0 the keyLocation sent to the engines is derived from APP_URL plus INDEXNOW_KEY_LOCATION, falling back to https://<host> when APP_URL is empty. Set APP_URL to the canonical public host before your first submission — a local or http:// value produces a keyLocation the engines will reject.
Look first at config/index-now.php: it holds the key, the keyLocation, the production-environment gate, failed-submit logging, and the list of search engine endpoints (IndexNow aggregate, Bing, Yandex, Seznam, Naver). Then check the facade/service class to see the exact submission signatures your version exposes.
First use case — notify engines that a page just went live:
use LaravelFreelancerNL\LaravelIndexNow\Facades\IndexNow;
$response = IndexNow::submit('https://example.com/blog/hello-world');
Submit, inspect the response code, and confirm the URL shows up in Bing Webmaster Tools within minutes-to-hours. Once that works end-to-end, wire it into a publish flow.
Fire on transition, not on every save. IndexNow is a change signal; spamming it on every updated event burns rate limit and devalues the signal. Use an observer and gate on the actual publish transition:
class PostObserver
{
public function updated(Post $post): void
{
if ($post->wasChanged('published_at') && $post->published_at !== null) {
SubmitToIndexNow::dispatch($post->url());
}
}
}
Always queue the submission. Search engine endpoints are external HTTP calls; never put one in the request cycle of a user-facing save. A thin job keeps retries and timeouts under your control:
class SubmitToIndexNow implements ShouldQueue
{
public $tries = 3;
public $backoff = 60;
public function __construct(public string $url) {}
public function handle(): void
{
IndexNow::submit($this->url);
}
}
Know the production gate. The package ships with INDEXNOW_PRODUCTION_ENV gating submissions to a named environment (default production). That is what stops Tinker sessions and tests from firing real network calls, and it means a staging or preview worker will silently no-op — which is usually what you want, but confusing the first time you test a queue in a non-production env. v2.1.0 fixed INDEXNOW_PRODUCTION_ENV=false so the gate can be disabled outright; set it to false (not an empty string) when you genuinely want submissions from another environment.
Batch for imports, migrations, and bulk edits. When a taxonomy change touches 500 URLs, submit them in one call rather than 500. v2.1.0 fixed the bulk payload to send the correct host and urlList fields, so bulk submission is now trustworthy — earlier versions produced malformed bodies. Still chunk well below the endpoint's 10,000-URL ceiling to avoid 429s on the aggregate endpoint.
Reconcile against your sitemap on a schedule. A nightly job that diffs sitemap.xml against the previous run and submits only additions/updates is the most robust pattern — it catches content created outside the normal publish flow (seeders, migrations, admin imports) and self-heals missed events.
Turn on failed-submit logging in production. INDEXNOW_LOG_FAILED_SUBMITS is distinct from the production gate: the former records non-2xx outcomes, the latter decides whether a request is sent at all. Enable logging on your queue workers so a bad keyLocation or a 422 surfaces in the log instead of vanishing into a swallowed job exception.
Treat the facade as an injectable service in controllers and actions rather than app() inside methods — it makes swapping or mocking trivial during refactors. v2.1.0 fixed the registered IndexNow facade alias; if you previously worked around the broken alias by binding the service class manually, drop that shim and re-test your imports.
.txt body is the key alone — no HTML, no newline padding that changes bytes, no text/html, no 301 to a CDN. Verify with curl -I https://site.com/key.txt and curl https://site.com/key.txt. A single redirect silently fails verification on most engines.keyLocation is built from APP_URL. If APP_URL is unset the package falls back to https://<host>, and if it is set to something non-canonical (a preview domain, http://, a trailing path) the keyLocation sent to the engines will not match where the file actually lives. Keep APP_URL equal to the canonical public origin and keep INDEXNOW_KEY_LOCATION in sync with the actual route that serves the file.https://example.com vs https://www.example.com counts as different hosts — pick one canonical host and submit only that.http://, and #anchors are rejected. Query strings are allowed but strip tracking params (utm_*, session IDs) or you'll submit duplicate variants.400 is usually a malformed body, 403 means the key isn't resolving, 422 means URL(s) don't belong to the verified host, and 429 is rate limiting. Log the body — engines return a human-readable reason that is far more useful than the status.404/410 and submit it; engines will recrawl and drop it. Don't submit removed URLs that still return 200.routes/web.php has a fallback that renders the Vue/React shell for unknown paths, it can intercept /{key}.txt and return HTML. Register the key route or a web-server rewrite before the fallback.INDEXNOW_LOG_FAILED_SUBMITS and wrap submissions in your own Log::info with the response body while bringing up a new site. The most common failure is not the client — it's an unreachable or mislocated key file behind a proxy or WAF, and the second most common is the production-env gate quietly suppressing the call.api.indexnow.org endpoint throttles; if you're submitting in bulk, add a short delay between chunks or use a dedicated engine endpoint (Bing) where you have webmaster credentials. Don't retry on 429 with aggressive backoff — use 60s+ and cap attempts.How can I help you explore Laravel packages today?