kreativsoehne/sulu-indexnow-bundle

Instantly notifies Bing, Yandex, Seznam and Naver about published, updated and unpublished Sulu content via IndexNow.

Maintainers

Package info

github.com/kreativsoehne/sulu-indexnow-bundle

Type:symfony-bundle

pkg:composer/kreativsoehne/sulu-indexnow-bundle

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 1

v1.0.0 2026-08-06 15:08 UTC

This package is not auto-updated.

Last update: 2026-08-06 15:39:52 UTC


README

Tells Bing, Yandex, Seznam and Naver about your content the moment you publish it, instead of waiting for the next crawl. Pages and articles are submitted automatically; a console command covers the initial bulk submission.

IndexNow is an open protocol. Google does not take part — there, sitemaps and crawling stay the way to go.

Requirements

  • PHP 8.2+
  • Sulu 2.6.23+ on Symfony 6.1+ — everything below is held back by security advisories and cannot be installed without overriding them
  • sulu/article-bundle is optional; articles are picked up automatically when it is installed

Installation

composer require kreativsoehne/sulu-indexnow-bundle

Register the bundle in config/bundles.php (Symfony Flex does this for you):

return [
    // …
    KreativSoehne\SuluIndexNowBundle\SuluIndexNowBundle::class => ['all' => true],
];

Generate a key and the key file that proves you control the domain:

bin/console indexnow:key:generate

This writes public/<key>.txt and prints the configuration to copy:

# config/packages/sulu_index_now.yaml
sulu_index_now:
    key: '8a4f2c1e9b7d4a3f8e6c5b2d1a9f7e3c'

Deploy the key file together with the configuration — without a publicly readable https://<host>/<key>.txt every submission is rejected.

Finally, announce what you already have:

bin/console indexnow:submit --sitemap=https://example.com/sitemap.xml

That's it. From now on every publish is reported automatically.

Configuration

Every option with its default:

sulu_index_now:
    # Master switch. When false nothing is sent, the commands stay available.
    enabled: true

    # The key. Not a secret — it has to be publicly readable.
    key: ''

    # Absolute URL of the key file, if it is not served from the document root.
    key_location: null

    # Submit automatically on publish and unpublish.
    auto_submit: true

    # Restrict submissions to these hosts. Empty means every host Sulu resolves.
    hosts: []

    # Scheme used to build absolute URLs and the key file location.
    scheme: 'https'

    # The default endpoint forwards to every participating search engine.
    endpoint: 'https://api.indexnow.org/indexnow'

    # HTTP timeout in seconds.
    timeout: 10

    # Where indexnow:key:generate writes the key file.
    public_dir: '%kernel.project_dir%/public'

The key is usually worth keeping out of the config file:

sulu_index_now:
    key: '%env(INDEXNOW_KEY)%'

Multiple webspaces

URLs are grouped by host and sent in one request per host, so a multi-webspace installation works out of the box — as long as the same key file is reachable under every host. Use hosts to restrict submissions to the domains you actually want to announce:

sulu_index_now:
    hosts: ['www.example.com', 'shop.example.com']

What gets submitted

Event Submitted
Page published yes, unless noindex or "hide in sitemap" is set
Page unpublished yes, so the URL leaves the index
Article published yes, unless noindex or "hide in sitemap" is set
Article unpublished yes

Articles are submitted for their main webspace and every additional webspace they are routed in.

Commands

# One or more URLs
bin/console indexnow:submit https://example.com/blog/hello-world

# Everything in a sitemap or sitemap index
bin/console indexnow:submit --sitemap=https://example.com/sitemap.xml

# See what would be sent
bin/console indexnow:submit --sitemap=https://example.com/sitemap.xml --dry-run

# Create a new key and key file
bin/console indexnow:key:generate --force

Before submitting, indexnow:submit verifies that the key file is publicly reachable for every host involved and aborts otherwise. Use --skip-key-check if you know better.

How it works

The bundle listens to Sulu's domain events (PagePublishedEvent, PageUnpublishedEvent, and their article counterparts), resolves the absolute URL through the webspace manager, and buffers it. The actual HTTP request goes out on kernel.terminate — after the response has reached the editor. Two consequences worth knowing:

  • Publishing never waits for the search engines.
  • If the endpoint is down, it is logged (channel sulu_index_now) and publishing carries on. Nothing is retried; the next publish or a manual indexnow:submit fixes it.

Submitting your own URLs

Anything routed outside of pages and articles — custom entities with a sulu/route-bundle route, for instance — can be submitted through the same service:

<?php

namespace App\Event;

use KreativSoehne\SuluIndexNowBundle\Submitter\UrlCollector;

class EventPublishedSubscriber
{
    public function __construct(private readonly UrlCollector $collector)
    {
    }

    public function onPublished(): void
    {
        // Sent together with everything else on kernel.terminate.
        $this->collector->add('https://example.com/events/summer-party');
    }
}

Or submit immediately, bypassing the buffer:

<?php

namespace App\Event;

use KreativSoehne\SuluIndexNowBundle\Submitter\IndexNowSubmitterInterface;

class EventPublisher
{
    public function __construct(private readonly IndexNowSubmitterInterface $submitter)
    {
    }

    public function publish(): void
    {
        $this->submitter->submit(['https://example.com/events/summer-party']);
    }
}

Both services are registered under their class name and can be autowired (UrlCollector, IndexNowSubmitterInterface).

Troubleshooting

Nothing is submitted. Check enabled, that key is set, and that the URL's host passes the hosts allowlist. indexnow:submit <url> --dry-run tells you whether a URL survives filtering.

The endpoint answers 403. The key file is missing, unreachable or its content does not match the key. It must contain the key and nothing else.

The endpoint answers 422. The submitted URLs do not belong to the host in the payload, usually a scheme or webspace URL mismatch.

URLs point at the wrong domain. Sulu resolves URLs per environment. Check the <environment type="prod"> URLs in your webspace configuration — that is what the resolver uses.

Development

composer install
composer test   # PHPUnit
composer lint   # php-cs-fixer, PHPStan and Rector, all in --dry-run
composer fix    # applies what Rector and php-cs-fixer suggest

License

MIT — see LICENSE.