Skip to content

Extending

The service provider registers one container binding per core interface. Replace any of them with $this->app->bind() / singleton() / extend() in your own provider (register it after the package, or bind in register() — the package resolves everything lazily).

Bindings

Abstract Default Replace it to
IndexNowKit\Config ConfigFactory::create(config('indexnow')) — (edit the config)
IndexNowKit\Key\KeyProviderInterface StaticKeyProvider::fromConfig() keys from a database (multi-tenant); honour $host in isKnownKey()
IndexNowKit\Http\TransportInterface LazyTransport over a discovered PSR-18 client your own HTTP stack (or set http.client)
IndexNowKit\Url\UrlNormalizerInterface UrlNormalizer canonical form: strip tracking parameters, trailing-slash policy
IndexNowKit\Throttle\ThrottleInterface TokenBucket a shared rate limiter
IndexNowKit\ClientInterface Client
IndexNowKit\Debounce\DebounceStoreInterface by debounce.store another window store
IndexNowKit\SubmitterInterface Submitter wrap with RetryingSubmitter, add listeners for metrics
IndexNowKit\Collector\CollectorInterface (scoped) Collector a durable outbox, a per-tenant buffer
IndexNowKit\Attribute\AttributeReaderInterface / RuleRegistry RuleRegistry over AttributeReader your own rule source
IndexNowKit\Url\RouteUrlResolverInterface / LaravelRouteUrlResolver the router bridge another URL scheme
IndexNowKit\Url\ResolverLocatorInterface ContainerResolverLocator
IndexNowKit\Url\UrlResolverInterface AttributeUrlResolver replace the whole "object → URLs" step
IndexNowKit\Url\GuardedUrlResolver, ObjectChangeHandler the facade's
IndexNowKit\Dispatch\DispatcherInterface by dispatch another delivery (an outbox table, a bus)
IndexNowKit\IndexNowKit the core facade
IndexNowKit\Key\KeyFileResponder over the key provider
IndexNowKit\Check\CheckerInterface Checker with the tagged checks
IndexNowKit\Sitemap\SitemapSourceInterface SitemapReader filter, rewrite or replace the sitemap source (bound only with indexnowkit/sitemap installed)
IndexNowKit\Laravel\IndexNowManager facade root
IndexNowKit\Laravel\Eloquent\IndexNowObserver (singleton) the observer
IndexNowKit\Laravel\Eloquent\RouteBindingFieldsInterface LaravelRouteUrlResolver which model field a {post:slug} parameter binds to (drives rename detection) when routes come from elsewhere
IndexNowKit\Console\SubjectLoaderInterface Console\ModelLoader model lookup (tenant scoping, another id format)
IndexNowKit\Console\ResultFormatterInterface ResultRenderer command output (your JSON envelope or table style)
IndexNowKit\Adapter\SubmitterFactoryInterface SubmitterFactory what --force / --dry-run submit through
IndexNowKit\Console\Vocabulary, Console\*Runner Laravel words; the command bodies reuse a runner from your own command (a tenant loop over SubmitSubjectsRunner)
indexnowkit.logger Log::channel(logging.channel) a PSR-3 logger of your own (tests: ArrayLogger)

Custom resolvers

#[IndexNow(resolver: ProductUrlResolver::class)]
class Product extends Model { use IndexNowable; }

final class ProductUrlResolver implements UrlResolverInterface
{
    public function __construct(private readonly UrlGenerator $urls) {}   // constructor dependencies are injected

    public function resolve(object $subject, Event $event): array
    {
        return [$this->urls->route('products.show', ['product' => $subject], true)];
    }
}

The class is built by the container; a binding id works the same (resolver: 'shop.product_urls'). An unknown id is a logged configuration error, not an exception.

Rules registered at runtime

IndexNowKit::observe(Vendor\Package\Post::class, [new IndexNow(route: 'posts.show', params: ['post' => 'self'])], new IndexNowDefaults(when: 'published'));
IndexNowKit::observe(Vendor\Package\Page::class);                       // the class carries its own attributes, only the observer is missing
IndexNowKit::rules()->registerFor(Node::class, fn (Node $node): ?RuleSet => $node->type === 'page' ? RuleCompiler::compile(...) : null);

Extra checks

Anything implementing IndexNowKit\Check\CheckInterface and tagged indexnowkit.check is printed by indexnow:check:

$this->app->singleton(CdnPurgeCheck::class);
$this->app->tag([CdnPurgeCheck::class], IndexNowKitServiceProvider::CHECK_TAG);

Add lines to the report; never throw — a failing check is an error line.

Submission results

Every Result (engine, host, status, reason, HTTP code, URL count) is dispatched as a Laravel event, the object itself: Event::listen(Result::class, fn (Result $result) => ...) receives it, Telescope's events watcher lists it, Event::fake() catches it in tests. The submitter and the command submitters (--force, --dry-run, the sitemap command) publish through the same PSR-14 bridge (IndexNowKitServiceProvider::EVENTS). The lower-level IndexNowKit::kit()->submitter->addListener(fn (Result $result) => ...) still works and fires first.

php artisan about has an IndexNow section: core version, enabled/dry-run, environment, base URL, the masked key, engines, dispatch, debounce — the first things a support request needs. php artisan indexnow:config --json is the full effective configuration.

Reading model attributes

#[IndexNow] accessors on Eloquent models go through Eloquent\EloquentSubjectReader, registered with the core's ParamExtractor::registerReader(). It claims attributes, casts, accessors and relations (methods with a declared Relation return type, or already loaded); anything else falls to the core DSL (methods, properties). An accessor that matches nothing is a ConfigurationException logged at error, not a silent null.

What is the core's

IndexNowObserver keeps only what is Eloquent's (the change set from getChanges()/getOriginal(), the previous state from getRawOriginal(), Connection::afterCommit()); guarding, logging and the URLs of a row about to be deleted are the core's Hook\ObserverHelper. SubmitUrlsJob is Retry\WorkerOutcome plus release()/fail() with the delay the RetryPolicy computes. The signatures of the artisan commands are rendered from Console\Definitions and Sitemap\Console\Definitions (CommandDefinition::laravelSignature()), so php artisan indexnow:submit-model --help matches the bundle and Yii2. A custom command over a core runner can build its signature the same way.