Skip to content

Sitemaps

composer require indexnowkit/sitemap   # optional: the indexnow:sitemap command

The reader lives in the optional indexnowkit/sitemap package. Without it indexnow:sitemap exists as a stub that prints indexnowkit/sitemap is not installed: composer require indexnowkit/sitemap and exits 1 (a scheduled run gets a sentence, not "command not found"), indexnow:check prints sitemap: not installed (…), the sitemap block of the config is ignored, and nothing else changes or is logged.

php artisan indexnow:sitemap submits every URL of a sitemap, a sitemap index, a text sitemap or a gzipped one, reading it as a stream and sending every batch.max_urls URLs while it is still being read. A million-URL index never has to fit in memory.

php artisan indexnow:sitemap                                   # sitemap.url, else <base_url>/sitemap.xml
php artisan indexnow:sitemap https://www.example.com/news.xml
php artisan indexnow:sitemap storage/app/sitemap.xml          # local file, no web server involved
php artisan indexnow:sitemap --changed-since="1 day"          # only <lastmod> newer than that (entries without lastmod are skipped)
php artisan indexnow:sitemap --dry-run                        # list, send nothing
php artisan indexnow:sitemap --json                           # one row per engine/host/status with url_count and batches

Schedule it once a day as a safety net for changes that bypassed the hooks:

// routes/console.php
Schedule::command('indexnow:sitemap --changed-since="1 day"')->daily();

Options

Option
--changed-since= "1 day", "2 hours", "2026-09-01"
--allow-foreign-hosts follow index entries hosted on another origin (a CDN) for this run; sitemap.allow_foreign_hosts makes it permanent
-f, --force ignore the debounce window
--dry-run print the entries instead of submitting
--json machine-readable summary; with --dry-run a JSON array of URLs

When the sitemap breaks midway (network, 5xx after fetch_retries), the batch read so far is still submitted, the command exits 1 and says how much went out; the re-run is idempotent thanks to the debounce window.

Configuration

'sitemap' => [
    'enabled' => true, 'url' => null, 'max_depth' => 3, 'max_sitemaps' => 1000, 'max_bytes' => 52428800,
    'allow_foreign_hosts' => false, 'spool' => 'auto', 'spool_dir' => null, 'fetch_retries' => 2,
],

spool decides where a document is kept while parsing: auto uses a temp file and falls back to memory when the temp dir is not writable (read-only containers), disk fails instead, memory never touches the disk (bounded by max_bytes). indexnow:check reports the location, or why the temp dir is unusable.

Your own source

The command depends on IndexNowKit\Sitemap\SitemapSourceInterface. Bind another implementation to read from a database, filter entries or rewrite URLs:

$this->app->singleton(SitemapSourceInterface::class, fn ($app) => new FilteringSource($app->make(SitemapReader::class)));

--allow-foreign-hosts only reaches the shipped SitemapReader; a custom source decides on its own.

spatie/laravel-sitemap users: point sitemap.url at the generated file (public/sitemap.xml) or pass the path as the argument; the package reads it locally.

The reader behind the command is the indexnowkit/sitemap package (required by this one); its README documents the sitemap block and the safety rules.