---
title: "Plugin Grav IndexNow : soumettre automatiquement ses pages aux moteurs de recherche"
description: "Création d'un plugin Grav from scratch pour soumettre automatiquement les pages modifiées à IndexNow (Bing, Yandex) via le hook onAdminAfterSave."
url: "https://www.arleo.eu/posts/grav-plugin-indexnow/"
language: "fr"
datePublished: "2026-04-17T12:00:00+02:00"
dateModified: "2026-05-15T23:51:25+02:00"
tags: ["grav","seo","indexnow"]
categories: ["plugins"]
contentSignal: "ai-train=no, search=yes, ai-input=yes"
---

# Plugin Grav IndexNow : soumettre automatiquement ses pages aux moteurs de recherche
Création d'un plugin Grav from scratch pour soumettre automatiquement les pages modifiées à IndexNow (Bing, Yandex) via le hook onAdminAfterSave.



## ⚡ En bref

Grav ne dispose d'aucun plugin IndexNow dans son catalogue officiel. Ce plugin maison comble ce manque en soumettant automatiquement les URLs modifiées à `api.indexnow.org` à chaque sauvegarde — que ce soit via l'admin Grav ou via le plugin MCP — sans intervention manuelle, sans cron, sans dépendance externe.

Le code est disponible sur GitHub :

- 🔌 Plugin : [jmrGrav/grav-plugin-indexnow](https://github.com/jmrGrav/grav-plugin-indexnow)

## 🧠 Pourquoi

[IndexNow](https://www.indexnow.org/) est un protocole ouvert permettant de notifier instantanément les moteurs de recherche compatibles (Bing, Yandex) qu'une page a été créée ou modifiée. Sans lui, les moteurs attendent leur prochain passage de crawler — qui peut prendre des heures ou des jours.

Plusieurs raisons ont motivé ce développement :

- **Aucun plugin natif** : contrairement à WordPress ou Shopify, Grav n'a pas de plugin IndexNow dans son catalogue
- **Cloudflare Crawler Hints déjà actif** mais limité aux invalidations de cache — pas aux modifications de contenu
- **Contrôle total** : soumettre explicitement les 3 variantes d'URL (`/route`, `/fr/route`, `/en/route`) à chaque save
- **Traçabilité** : chaque soumission est loggée dans `grav.log` avec le code HTTP retourné

## 🔧 Ce qui a été fait

### 📁 Structure du plugin

Un plugin Grav minimal se compose de 3 fichiers :

```
/var/www/grav/user/plugins/indexnow/
├── indexnow.php          ← logique principale
├── blueprints.yaml       ← définition des champs admin
└── indexnow.yaml         ← valeurs par défaut
```

### 🔍 Trouver le bon hook admin

Le premier obstacle a été d'identifier l'événement Grav déclenché lors d'un save dans l'admin. Le hook `onPageSave` — intuitif mais inexistant — ne fonctionne pas. La recherche dans le code source a révélé le bon candidat :

```bash
grep -rh "fireEvent(" /var/www/grav/system/src/ | sort -u
# → onAdminAfterSave émis par AdminController.php après chaque save réussi
```

| Hook testé | Résultat |
|---|---|
| `onPageSave` | ❌ N'existe pas dans Grav admin |
| `onAdminSave` | ⚠️ Émis avant la validation |
| `onAdminAfterSave` | ✅ Émis après chaque save réussi |

### ⚙️ Code du plugin

```php
public static function getSubscribedEvents(): array
{
    return [
        'onAdminAfterSave' => ['onAdminAfterSave', 0],
        'onMcpAfterSave'   => ['onMcpAfterSave', 0],
    ];
}
```

### 🐛 Bug 1 : `$this->config()` vs `$this->grav['config']`

La méthode `$this->config()` dans un plugin Grav retourne un **array**, pas un objet `Config`. Appeler `->get()` dessus provoque une erreur fatale :

```php
// ❌ Incorrect — retourne un array, pas un objet Config
$config = $this->config();
$key    = $config->get('plugins.indexnow.key');

// ✅ Correct
$config = $this->grav['config'];
$key    = $config->get('plugins.indexnow.key');
```

### 🐛 Bug 2 : le plugin MCP bypasse le hook admin

Après les premiers tests, un problème est apparu : les pages créées ou modifiées via le **plugin MCP** (`create_page`, `update_page`) ne déclenchaient pas `onAdminAfterSave`. Ce hook n'est émis que par `PageObject.php` dans le contexte admin — les appels programmatiques via MCP le bypasse complètement.

**Diagnostic :**

```bash
# Aucun fireEvent dans le plugin MCP
grep -r "fireEvent" /var/www/grav/user/plugins/mcp-server/mcp-server.php
# → (aucun résultat)

# onAdminAfterSave conditionné à isAdminSite()
grep -r "onAdminAfterSave" /var/www/grav/system/src/Grav/Common/Flex/Types/Pages/PageObject.php
# → if ($this->isAdminSite()) { $grav->fireEvent('onAdminAfterSave', ...) }
```

**Solution — event custom `onMcpAfterSave` :**

Plutôt que de réutiliser `onAdminAfterSave` (qui nécessite un objet `PageInterface` pas toujours disponible après un write MCP), un event custom a été ajouté directement dans le plugin MCP après chaque opération réussie :

```php
// Dans mcp-server.php — helper ajouté après toolCreatePage et toolUpdatePage
private function notifyPageSaved(string $route): void
{
    $grav = \Grav\Common\Grav::instance();
    $grav->fireEvent('onMcpAfterSave', new \RocketTheme\Toolbox\Event\Event([
        'route' => $route,
    ]));
}
```

Le plugin IndexNow écoute cet event et extrait la route directement depuis le payload :

```php
public function onMcpAfterSave(Event $event): void
{
    $route = $event['route'] ?? null;
    if (!$route) return;
    if (in_array($route, self::EXCLUDED_ROUTES, true)) return;
    if (str_starts_with($route, '/tag')) return;

    $this->submitToIndexNow($route);
}
```

### ✅ Résultat final — deux contextes couverts

| Contexte | Hook | Statut |
|---|---|---|
| Save via admin Grav | `onAdminAfterSave` | ✅ Actif |
| `create_page` / `update_page` via MCP | `onMcpAfterSave` | ✅ Actif |

### 📋 Fichier de clé IndexNow

Le fichier clé doit être accessible publiquement à la racine du site :

```bash
curl -I https://arleo.eu/bf6faf5563914fe7bb18d429976b182d.txt
# → HTTP/2 200
```

### ✅ Vérification dans les logs

```bash
tail -f /var/www/grav/logs/grav.log | grep -i indexnow
# [IndexNow] ✅ Soumission OK (HTTP 200) pour :
# https://arleo.eu/fr/grav-plugin-indexnow,
# https://arleo.eu/en/grav-plugin-indexnow,
# https://arleo.eu/grav-plugin-indexnow
```

## 🏁 Conclusion

Le plugin couvre désormais les deux contextes de modification : l'admin Grav via `onAdminAfterSave` et le plugin MCP via l'event custom `onMcpAfterSave`. Chaque sauvegarde déclenche une soumission immédiate des 3 variantes d'URL à IndexNow, avec traçabilité complète dans `grav.log`. Le délai d'indexation par Bing passe de plusieurs jours à quelques minutes.

**Pour aller plus loin :**

- 💡 Ajouter une soumission bulk au démarrage du plugin pour indexer toutes les pages existantes en une seule requête
- 💡 Étendre la soumission à d'autres moteurs compatibles IndexNow (Naver, Seznam) via leurs endpoints dédiés



## Tags

- grav
- seo
- indexnow

## Categories

- plugins
