indexnowkit/console
IndexNowKit Console provides Artisan commands for submitting URLs to IndexNow-compatible search engines from Laravel. Run single or bulk submissions, manage API keys, and integrate search indexing into deployments or scheduled tasks.
The 0.5.0 line is a re-packaging of the IndexNow toolkit: the command bodies that each adapter used to carry (about 900 lines across eleven near-identical classes) now live here as Console\Command\*, and adapters register them. For Laravel that means laravel 0.15.0 + indexnowkit/core ^0.13 + this package, pinned together — sitemap and history already pin ^0.5, so bump the whole set in one composer update.
First steps:
composer require indexnowkit/laravel (0.15.0) — it pulls this package and core ^0.13.php artisan vendor:publish for the indexnow tag) and set INDEXNOW_KEY; generate one with php artisan indexnow:key:generate --write-env.config/indexnow.php first, then the adapter's ConfigSourceInterface implementation and Adapter\ConfigFactory::build() — those two tell you exactly what indexnow:check and indexnow:config will see.php artisan indexnow:submit to ping with your sitemap, or register a model and run php artisan indexnow:submit-model "App\Models\Post".Artisan no longer needs hand-written command classes: Illuminate\Console\Application::resolve() accepts any symfony/console command, so $this->commands() in a service provider is enough to expose SubmitCommand, CheckCommand, ConfigCommand, etc.
Register, don't reimplement. Add the package commands in a service provider:
public function boot(): void
{
$this->commands([
\IndexNowKit\Console\Command\SubmitCommand::class,
\IndexNowKit\Console\Command\CheckCommand::class,
\IndexNowKit\Console\Command\ConfigCommand::class,
// ...
]);
}
Everything that varies per adapter arrives by constructor: the runner, the Vocabulary, and Check\SampleOptions (with the sampler already inside). SubmitSubjectsCommand has no #[AsCommand] — its name comes from Vocabulary::$submitSubjects, so let the adapter/vocabulary wire it rather than naming it yourself.
Keep the Laravel argument name. Laravel passes 'model' as the class-argument name, which is why SubmitSubjectsCommand and ExplainCommand take string $classArgument = 'class'. This keeps existing call sites working:
Artisan::call('indexnow:submit-model', ['model' => Post::class]);
Don't rename the argument when wrapping; it's positional on the CLI but named in Artisan::call().
Programmatic submission from jobs/observers. Reach for the kit/runner, not the commands. Dispatch on model save via an observer, debounce with ShouldBeUnique, and use SubmitSubjectsOptions::DEFAULT_LIMIT (1000) as your --limit/batch fallback rather than a literal.
Sampling. SubjectSampler(SubjectLoaderInterface, IndexNowKit) gives you up to PER_CLASS = 3 subjects per class (or the one matching an id) through their rules. Put it in Check\SampleOptions::$sampler — don't grow another copy of the 38-line loop.
Custom ORM loaders. Extend AbstractSubjectLoader and implement only findOne(class, id, event) and findMany(class, limit, event). You inherit ClassNameResolver (marker class or predicate over namespaces), the single guard text, the found/missing loop, and max(1, $limit).
Config plumbing. If you expose config to other packages/tools, implement Console\ConfigSourceInterface (raw(), build(), packages()) rather than passing three constructor args around. Remember methods are only added on a major (pre-1.0: a listed "Changed"), so implement it deliberately.
Optional-package stubs. SitemapNotInstalledCommand, HistoryNotInstalledCommand, StatusNotInstalledCommand give a one-sentence message and exit 1 instead of "command not found". Register them (one class per registry key) so users get a useful error, and don't try to merge them into a single class — console registries key by class name.
The breaking change. CommandDefinition::laravelSignature() is gone. Its only consumer was the old artisan command classes in laravel 0.15.0. If you had your own command doing Definitions::check()->applyTo($this), migrate to a Symfony\Component\Console\Command\Command registered via $this->commands([...]), or vendor a copy of the 32-line renderer from 0.4.
Upgrade the family together. sitemap and history build their commands on these classes and pin ^0.5; a partial bump gives you mismatched signatures. Bump laravel, symfony bundle, and yii3 adapters in the same PR.
Secrets leaked via indexnow:config, now fixed. Before 0.5.0, masking only covered installed optional-package blocks — so history.pdo.dsn (with password) printed in the table, in --json, and in the "configuration does not build" JSON. If anyone pasted that output into a bug report or a ticket between 0.4.0 and 0.5.0, treat the DSN as compromised and rotate it. Going forward, check ConfigRunner::adapterOnly() → maskedBlock().
Env-file permissions. key:generate --write-env now creates the file with 0600 before writing the key. An already-existing wider-permission file is not narrowed (that's your application's call) but the command now prints a warning naming the file and the chmod. Run it.
Key rotation escaping. 0.5.0 fixes the case where a previous key containing $ or \ was injected unescaped into preg_replace()'s replacement — an inherited INDEXNOW_KEY=$0 produced corrupted INDEXNOW_PREVIOUS_KEY. After any rotation on an older version, inspect the env file; on 0.5.0 it's safe.
Debugging config failures. If indexnow:check says the config doesn't build, run indexnow:config --json — but keep in mind the JSON is what the bug-report prompt pastes, so never post it without re-checking masked blocks.
Extension points to prefer over forking: ConfigSourceInterface (config surface), AbstractSubjectLoader (ORM access), SubjectSampler (sampling rules), and the NotInstalledCommand subclasses (missing-optional UX). Each replaces a per-adapter copy that used to drift.
How can I help you explore Laravel packages today?