Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Core Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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.

  1. Read the source before the README. With 0 stars and 0 dependents there is no community surface area to fall back on. Start at the 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.
  2. Install and check constraints. 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.
  3. Solve the key file first — it is not optional. IndexNow requires a verification file served from the host root: 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.
  4. Build config from env, then bind a singleton. As of 0.13.1 the config path is explicit: 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.

Implementation Patterns

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.

Gotchas and Tips

  • 403 means the key file is wrong or unreachable, not that your key is malformed. 422 usually means a URL in the batch doesn't belong to the host you're authenticating as. Log the response body — the error text is more specific than the status code.
  • The key file must be at the host root, not /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.
  • Guard against non-production submission. An observer that fires on every 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.1 is a patch that adds a method. Per the maintainer's cascade rule, a ^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.
  • Verify the release dates. The package metadata has previously listed dates (e.g. 2026-09-07) that don't reconcile with a current date. Pin an exact version in composer.json if you need reproducibility across machines and CI.
  • Respect 429. IndexNow endpoints and the search engines behind them throttle. If you see 429s, your drain interval is too tight; back off rather than retrying immediately.
  • Failures are silent by default. Framework-agnostic clients tend to return a result object or false rather than throwing. Check every call site and log the outcome; otherwise a broken key file goes unnoticed for weeks.
  • Extension points to look for: a transport/HTTP abstraction and a key provider. If both exist, you can drop in Laravel's HTTP client (gaining 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.
  • Don't confuse submission with indexing. A 200/202 means "received", not "indexed". Don't build dashboards that treat the response as a ranking or indexation signal.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource