Un client a un jour envoyé une capture d’écran d’un article WordPress où la moitié du contenu manquait à l’affichage, alors que tout semblait normal dans l’éditeur. Le coupable n’était ni un plugin ni un thème, mais un export de contenu qui avait tronqué un commentaire HTML au milieu d’un bloc. Pour comprendre ce genre d’incident, il faut d’abord comprendre ce qu’est réellement un bloc une fois enregistré en base de données : ni du JSON, ni du HTML classique, mais un format hybride que peu de développeurs prennent le temps d’observer de près.
Ce format hybride, ce sont les delimiters de bloc. Chaque bloc Gutenberg, qu’il soit un simple paragraphe ou un composant complexe avec des dizaines d’attributs, est stocké dans le champ post_content sous la forme d’un commentaire HTML d’ouverture, du contenu, puis d’un commentaire de fermeture. Comprendre cette anatomie change la façon dont on débogue un contenu cassé, dont on écrit un script de migration, ou dont on lit un export WXR sans se perdre.
L’anatomie d’un delimiter de bloc
Un bloc de paragraphe simple ressemble, une fois sérialisé, à ceci :
<!-- wp:paragraph -->
<p>Bonjour tout le monde.</p>
<!-- /wp:paragraph -->
Le commentaire d’ouverture porte le nom du bloc, préfixé par l’espace de nom de l’extension qui le fournit. Les blocs natifs utilisent le préfixe core, ce qui donne wp:paragraph plutôt que wp:core/paragraph : c’est la seule exception au schéma namespace/nom-du-bloc. Un bloc personnalisé enregistré sous monagence/temoignage apparaîtra donc sous la forme <!-- wp:monagence/temoignage -->.
Où vivent les attributs non sourcés
Certains attributs sont directement lus depuis le HTML rendu grâce à une propriété source dans block.json (comme html ou attribute). D’autres, en revanche, n’ont aucune correspondance visible dans le balisage : ce sont les booléens de mise en page, les identifiants internes ou les réglages qui n’ont pas de représentation HTML naturelle. Pour ceux-là, le parser stocke un objet JSON directement à l’intérieur du commentaire d’ouverture, juste après le nom du bloc.

Voici un bloc de type « couverture » avec un attribut de superposition non sourcé :
<!-- wp:cover {"overlayColor":"contrast","dimRatio":60} -->
<div class="wp-block-cover">
<span class="wp-block-cover__background has-contrast-background-color"></span>
<div class="wp-block-cover__inner-container">
<p>Texte affiché sur l'image</p>
</div>
</div>
<!-- /wp:cover -->
Ce JSON est produit automatiquement par la fonction serialize() du module @wordpress/blocks, qui compare chaque attribut déclaré à sa valeur par défaut et n’inscrit dans le commentaire que ce qui diverge. C’est une optimisation utile à connaître : un attribut resté à sa valeur par défaut n’apparaîtra jamais dans le contenu stocké, même s’il existe bien dans la définition du bloc.
Le rôle du parser côté PHP
Côté serveur, la fonction parse_blocks() lit le contenu brut d’un article et reconstruit un tableau de blocs structuré, chacun avec son nom, ses attributs déjà décodés depuis le JSON, son contenu HTML interne et la liste de ses éventuels blocs enfants. C’est ce tableau que WordPress parcourt ensuite pour appeler render_block() sur chaque entrée, en particulier pour les blocs dynamiques dont le rendu dépend d’un render_callback.
- Un bloc sans callback dynamique est simplement réinjecté tel quel dans le flux de sortie.
- Un bloc dont le nom ne correspond à aucune définition enregistrée devient un bloc de type
core/freeform, du HTML brut sans structure. - Un commentaire de fermeture manquant ou mal formé casse le parsing de tout le reste du contenu qui suit.
Pourquoi un export ou une modification manuelle peut tout casser
Le point le plus fragile de ce système, c’est justement sa dépendance à un format texte que rien n’empêche de modifier à la main. Un script de recherche-remplacement un peu trop agressif sur la table wp_posts, une exportation qui échappe mal les guillemets du JSON inclus dans le commentaire, ou un copier-coller depuis un éditeur de texte qui reformate les apostrophes : chacun de ces cas peut rendre un delimiter illisible pour le parser.
Avant toute modification en base de données sur du contenu de blocs, faites toujours un test sur une copie : un JSON mal fermé dans un commentaire ne provoque pas d’erreur PHP visible, il provoque un rendu silencieusement incomplet.
Un cas fréquent est celui d’un attribut contenant lui-même des guillemets doubles, par exemple un texte alternatif d’image. La sérialisation échappe ces guillemets en " à l’intérieur du JSON du commentaire, et toute manipulation qui ne respecte pas cet échappement produit un bloc que l’éditeur affichera comme invalide dès l’ouverture suivante.
Inspecter la sérialisation en pratique
Pour observer ce format sans passer par la base de données, l’éditeur de blocs propose directement un mode « Éditeur de code » accessible depuis le menu des options (les trois points verticaux en haut à droite), qui affiche le contenu exactement tel qu’il sera stocké. C’est l’outil le plus simple pour vérifier qu’un attribut est bien pris en compte avant de creuser plus loin dans une extension personnalisée.
Depuis WP-CLI, il est aussi possible d’inspecter directement la valeur stockée :
wp post get 42 --field=post_content
Cette commande renvoie le contenu brut, delimiters compris, et permet de confirmer en quelques secondes si un attribut suspect est bien présent dans le commentaire ou s’il a été perdu en cours de route.
Ce qu’il faut retenir
La sérialisation en commentaires HTML est un choix d’architecture qui permet à WordPress de rester compatible avec du contenu non structuré tout en offrant une structure exploitable aux blocs. Elle explique à la fois la robustesse du système face à des extensions désactivées et sa fragilité face à des manipulations de contenu mal maîtrisées. Retenir l’existence de parse_blocks() et du mode éditeur de code suffit, dans la grande majorité des cas de débogage, à localiser rapidement l’origine d’un contenu qui se comporte mal.