Skip to content

Configuration

config/indexnow.php (publish it with php artisan vendor:publish --tag=indexnow-config; without the file every key below uses its default). Keys shared with every indexnowkit adapter are described in the core configuration reference; this page lists all of them briefly and the Laravel-only blocks in full.

Values come from env(), so they are validated at runtime: an invalid value never throws from a save or from terminating. It is logged once at critical (indexnow: invalid configuration, IndexNow is disabled until it is fixed: ...), IndexNow runs disabled, and php artisan indexnow:check prints the exact error. Unknown keys (typos) are logged at warning.

Core keys

Key Default Meaning
enabled INDEXNOW_ENABLED, true kill switch; false = nothing is sent, changes are logged at debug
key INDEXNOW_KEY the IndexNow key, [A-Za-z0-9-]{8,128}
previous_key INDEXNOW_PREVIOUS_KEY the key before a rotation: its file is still served, nothing is submitted under it
key_location INDEXNOW_KEY_LOCATION full URL of the key file when it is not https://<host>/<key>.txt
base_url INDEXNOW_BASE_URL, then APP_URL origin for URLs generated outside HTTP requests (artisan, workers); required with dispatch: queue
hosts [] host => key or host => ['key', 'key_location', 'base_url', 'engines', 'previous_key']
strict_hosts INDEXNOW_STRICT_HOSTS, false skip URLs of hosts outside base_url / hosts instead of sending them under the default key
environment app()->environment() feeds the non-production dry-run safety net
production_environments ['prod', 'production'] environments where a missing key is an error, not dry-run
max_url_length 2048 longer URLs are invalid_url
engines INDEXNOW_ENGINES, api api, yandex, bing, naver, seznam, yep, internetarchive, amazon, an endpoint URL or an alias
engine_aliases [] alias => endpoint URL
locale_hosts [] locale => host for rules with locales and no host
dispatch INDEXNOW_DISPATCH, queue queue, sync, none (see below)
dry_run INDEXNOW_DRY_RUN, false log the request instead of sending it
batch.max_urls 10000 URLs per request (protocol maximum)
debounce.per_url 600 seconds a URL is not resubmitted; 0 = off
debounce.key_prefix indexnowkit_ cache key prefix of the shared window
throttle.max_requests_per_minute 60 token bucket per process
http.timeout 10 seconds
http.user_agent null override the indexnowkit-php/x.y.z agent
retry.* 3 / 60 / 2.0 / 3600 / 5 max_attempts, base_delay, multiplier, max_delay, server_error_delay of SubmitUrlsJob
resolver.max_via_depth / max_via_fanout 3 / 100 limits of via:
collector.max_urls / detect_leaks 0 / true early flush threshold; warn at shutdown about unflushed URLs
logging.max_urls / forbidden_escalation / max_body / levels 20 / 5 / 300 / [] log line shaping; see the core operations guide

Laravel keys

dispatch

Value Behaviour
queue (default) each flushed batch becomes a SubmitUrlsJob on queue.connection / queue.queue, delayed by queue.delay seconds; retries follow retry.*. QUEUE_CONNECTION=sync runs it inline.
sync the batch is sent from app()->terminating(), after the response; 429/5xx are not retried
none URLs are collected but never sent (drain IndexNowKit::kit()->collector yourself)
'queue' => [
    'connection' => env('INDEXNOW_QUEUE_CONNECTION'),   // null = queue.default
    'queue' => env('INDEXNOW_QUEUE'),                   // null = the connection's default queue
    'delay' => 0,                                       // seconds before the first attempt
],

Details, Horizon and failure handling: queue.md.

debounce.store

Value Store
cache (default) Cache::store() — the default cache store, shared by web requests and workers
a store name (redis, array) that store
memory per process; fine for CLI scripts and tests, warned about by indexnow:check
none no debounce

The window is best effort and fails open: an unusable store logs a warning and the URLs are still sent.

http.client

null discovers a PSR-18 client (Guzzle in a default Laravel application, otherwise php-http/discovery). A class name or container binding of a Psr\Http\Client\ClientInterface uses that client (proxy, extra headers, retries of your own). The transport is built on first use, so a request that submits nothing never touches it.

key_file

'key_file' => [
    'enabled' => true,             // false = the route is not registered (serve the file yourself or set key_location)
    'path' => '/{key}.txt',        // must contain {key}
    'host' => null,                // restrict the route to a host (Route::domain())
    'cache_max_age' => 300,        // Cache-Control max-age; short on purpose: a cached old file means 403 after a rotation
    'route_name' => 'indexnow.key_file',
    'middleware' => [],            // no "web" group by default: no session, no CSRF, no cookies
],

The controller serves only a key that belongs to the requested host (404 otherwise) and adds Vary: Host when a hosts map is configured. serve_key_file (core name) is accepted as an alias of key_file.enabled. The route is compatible with route:cache.

router

'router' => [
    'locales' => [],               // locales generated for rules with locales: 'all' (empty = current only)
    'locale_parameter' => 'locale',// added to the route parameters only when the route declares {locale}
    'set_app_locale' => true,      // App::setLocale() while generating a locale's URL (localized slugs), restored after
],

Inside an HTTP request URLs keep the host Laravel generated them on; in the console (artisan, queue workers) they are rebased onto base_url; a rule with host: uses hosts.<host>.base_url, else https://<host>. Routes with their own Route::domain() keep it. Details: multi-domain.md.

eloquent

eloquent.enabled: false keeps the observer registered but inert: models using IndexNowable submit nothing, manual submission still works. indexnow:check says so.

logging.channel

Log channel name (config/logging.php); null = the default channel. Every service of the package logs there, with messages prefixed indexnow:.

sitemap

Needs indexnowkit/sitemap (composer require indexnowkit/sitemap); without the package the block is ignored and indexnow:check says so.

'sitemap' => [
    'enabled' => true,             // false = no indexnow:sitemap command
    'url' => null,                 // default argument (null = <base_url>/sitemap.xml)
    'max_depth' => 3,              // nested sitemap indexes
    'max_sitemaps' => 1000,
    'max_bytes' => 52428800,       // per document
    'allow_foreign_hosts' => false,// follow index entries on another origin (CDN)
    'spool' => 'auto',             // auto | disk | memory: where a document is kept while parsing
    'spool_dir' => null,           // temp directory (null = sys_get_temp_dir())
    'fetch_retries' => 2,          // after a network failure or 5xx
],

Details: sitemap.md.

Environment variables

INDEXNOW_KEY, INDEXNOW_PREVIOUS_KEY, INDEXNOW_KEY_LOCATION, INDEXNOW_BASE_URL, INDEXNOW_ENABLED, INDEXNOW_STRICT_HOSTS, INDEXNOW_ENGINES (comma-separated), INDEXNOW_DISPATCH, INDEXNOW_DRY_RUN, INDEXNOW_QUEUE_CONNECTION, INDEXNOW_QUEUE, INDEXNOW_DEBOUNCE_STORE, INDEXNOW_LOG_CHANNEL are read by the shipped config file. Anything else goes through your published copy.

Startup checks

php artisan indexnow:check validates the configuration (exact error on failure), fetches every key file over HTTP, and prints the Laravel wiring: the queue connection SubmitUrlsJob goes to (or that sync retries nothing), whether the debounce cache store is usable, where sitemap documents are spooled, and whether observers are active. --live adds a real probe request per engine.