indexnowkit/core
IndexNowKit Core is a Laravel/PHP package that provides essential IndexNow integration, helping your app notify participating search engines about new or updated URLs. It offers a simple core client and API foundation for fast, reliable indexing notifications.
indexnowkit/core is a transport-level client, not a Laravel package — there is no service provider, no facade, no config publish command. Budget the first 20 minutes for wiring rather than reading docs.
Config class and the client/factory entry point, then the exception types and any interfaces (transport, key provider, URL validator) — those interfaces are your integration seam. The release notes cite internal spec sections (spec 19b §3.3, "wave N"), so the changelog is written for the maintainer, not for you; treat src/ as the documentation.composer require indexnowkit/core, then open the package's composer.json to confirm the PHP and Guzzle/PSR-18 requirement. If it pins guzzlehttp/guzzle tightly, check it against the version Laravel already resolves before you commit.https://example.com/{key}.txt whose body is exactly the key. Generate a 32–128 char hex key, put it in config/services.php / .env, and write the file into public/ (or a route returning text/plain). Nothing works until this is live and reachable — including locally, which it won't be, so plan for that.Config::fromEnv() reads INDEXNOW_* variables, and internally it is just fromArray(arrayFromEnv()). In Laravel those env vars are already loaded, so Config::fromEnv() is usually all you need; use arrayFromEnv() only if you want to merge the environment over a config file (this is the path the indexnowkit/cli package takes). Bind it in AppServiceProvider so you control construction and can swap the transport:$this->app->singleton(IndexNowClient::class, function () {
$config = array_merge(
config('services.indexnow', []), // your Laravel-side defaults
Config::arrayFromEnv(), // env wins, only set vars present
);
return new IndexNowClient(Config::fromArray($config));
});
First use case: an artisan command that pulls URLs from sitemap.xml and submits them once, manually, so you can see a real 200/202 response and a real error before anything is automated. Do not start with model observers.
Submit from a queued job, not inline. IndexNow is a third-party HTTP call; a slow or failing endpoint should never sit in a web request. One job per batch, $tries = 3, backoff() returning [30, 120, 600].
Batch aggressively. IndexNow accepts up to 10,000 URLs per request. The natural workflow is: collect URLs into a buffer (cache, DB table, or a pending_indexnow_urls table), then have a scheduled command (everyFiveMinutes) drain the buffer into a single submission. This also gives you deduplication for free.
Keep configuration as data, not constructor args. Since Config::fromArray() is the canonical entry point, keep your IndexNow settings in a plain array (config/services.php merged with arrayFromEnv()), and construct the client from that. It means the same array can be logged on failure (minus the key), reused by the artisan command, and round-tripped through toArray() when you need to see the effective defaults.
Debounce at the application layer, not the HTTP layer. The package's debounce is useful for suppressing rapid repeat submissions of the same URL, but on a queue worker it usually means sleeping or scheduling a delayed job — which stalls the worker. Prefer: an observer/cache-push writes the URL with a timestamp, and the scheduled drain job only picks up rows older than N minutes. Predictable, restart-safe, and visible in your DB.
Pick one retry layer. The package retries; Laravel's queue retries too. Stacked retries multiply (3×3 = 9 requests) and burn your rate budget. Either disable package retries and let the queue own it, or keep package retries for transient 5xx/429 and set the queue job to $tries = 1 with explicit failure logging.
Key handling per host. If you run multi-tenant or multi-domain, every host needs its own key file, and all URLs in one request must belong to the same host. Group by host before submitting. With arrayFromEnv() this gets awkward — it reads a single INDEXNOW_* set — so for multi-host, keep the per-host keys in your own config array and call Config::fromArray() per host rather than relying on env.
Testing. Http::fake() will not intercept the package if it uses Guzzle/cURL directly. Check whether the client accepts a PSR-18 client in its constructor — if it does, inject a mock handler and assert on the request body. If it doesn't, wrap the client behind your own IndexNow interface and fake that in tests. This is the single highest-value thing you can add on day one, because it also makes vendor swaps painless. Config construction is now cheap to test in isolation: Config::fromArray(Config::arrayFromEnv($env)) should equal Config::fromEnv($env) — a useful invariant to assert in your own wiring test.
Deployment hook. A deploy post-hook that submits changed sitemap URLs is a better fit than model observers for content sites; use observers only where individual records matter (marketplace listings, etc.). The indexnowkit/cli sibling package reads config via arrayFromEnv(), which makes it a reasonable fit for a shell step in your deploy script if you'd rather not write the artisan command.
/public/{key}.txt from the outside's perspective, and served as text/plain. Nginx try_files rules, Cloudflare caching, and .txt-blocking WAF rules are the three usual culprits.arrayFromEnv() returns only set variables, as strings. It is deliberately not a full config: toArray() carries every default, arrayFromEnv() carries none. fromArray() coerces them, so the types land correctly — but if you array_merge() it yourself and read the result before constructing Config, you'll be handling strings ("true", "10000") and missing keys. Merge, then let Config::fromArray() normalise; don't branch on the raw merged array.save() will spam the endpoint from your local machine and CI seeders. Wrap in if (app()->isProduction()) or a dedicated INDEXNOW_ENABLED flag, default false. Note that arrayFromEnv() will happily pick up an INDEXNOW_ENABLED set in a stray .env on a developer machine.^0.13 constraint pulls this in silently on composer update, and so will every future addition released as a patch. If you pin with a caret, review src/ diffs on update rather than assuming patch releases are behaviour-neutral — with 0 dependents, nobody else is stress-testing upgrades for you.composer.json if you need reproducibility across machines and CI.false rather than throwing. Check every call site and log the outcome; otherwise a broken key file goes unnoticed for weeks.Http::fake, retry, and middleware for free) and move the key to a secrets manager. If neither exists, write an adapter class in app/Services — do not call the vendor client directly from jobs or observers.How can I help you explore Laravel packages today?