Migration ACF Pro vers Modern Fields sur WordPress FSE – retour d’expérience


ACF Pro, c’est l’outil de référence pour les champs custom WordPress depuis des années. Mais sur un projet Starter Kit FSE moderne – stack Bedrock + DDEV, thème Timber/Twig, blocs Gutenberg custom – il commençait à peser. UI parallèle découplée de l’éditeur, dépendance payante, roadmap qui n’a pas suivi l’évolution du core WordPress. J’ai migré vers Modern Fields – avec le privilège d’être parmi les premiers beta testeurs. Voici ce que ça a vraiment impliqué.

Pourquoi Modern Fields

Modern Fields (disponible aussi sur WordPress.org) s’intègre directement dans l’inspector de l’éditeur Gutenberg, sans interface parallèle. Les groupes de champs sont des posts Gutenberg en base – donc versionnables, importables programmatiquement. L’API est plus propre, la roadmap est alignée avec WordPress core.

La différence de stockage est la première chose à comprendre : ACF stockait les valeurs en attribut data dans le bloc. Modern Fields utilise un attribut modernFields. Pas grand chose à l’œil, mais ça implique un script de migration sur tous les posts de contenu existants – dans mon cas, 7 posts de démo, mais sur un vrai site en production, c’est à anticiper sérieusement.

Ce que la migration a couvert

17 blocs custom à migrer. Chaque bloc avait un fichier acf.php avec acf_add_local_field_group(). Chaque fichier a été remplacé par un mf.php co-localisé qui enregistre le groupe Modern Fields programmatiquement via un helper PHP maison (GroupSync + BlockSerializer).

Types de champs migrés : text, textarea, select, toggle, number, repeater, relation, conditional. Rien d’exotique, mais chaque type a ses petites subtilités dans l’API MF.

Côté classes PHP, la majorité des remplacements sont mécaniques – les appels get_field() ACF remplacés par l’API MF native, un fichier à la fois. J’en ai profité pour passer quelques coups de balai au passage : découplage de récupération de données globales, suppression du dual-mode ACF/WP-standard dans le moteur de rendu Twig – soit −150 lignes de code mort.

Un cas sans équivalent direct dans l’API MF : la lecture programmatique des choices d’un champ select. Remplacé par une requête SQL sur les données existantes en base, avec deux hooks WordPress pour court-circuiter ou post-traiter le résultat selon le contexte. Pas élégant, mais robuste et découplé de toute dépendance à un plugin tiers.

Le piège WordPress 7.0 : autoRegister et les inner blocks

WordPress 7.0 introduit supports.autoRegister: true dans block.json. Un bloc avec ce flag est automatiquement enregistré côté JS par le core avec un edit basé sur ServerSideRender – sans écrire de JavaScript. Exactement ce qu’il faut pour les blocs dynamiques PHP + Twig.

Sauf que ça a introduit une régression sérieuse : les inner blocks étaient perdus à chaque sauvegarde. Le diagnostic a pris un moment. WordPress enregistre automatiquement ces blocs avec save: () => null. Dans le sérialiseur Gutenberg, save() retournant null produit une chaîne vide – ce qui génère un commentaire auto-fermant <!-- wp:juz/tabs /--> sans contenu. Les inner blocks ne sont jamais écrits en base.

La correction : un filtre JS blocks.registerBlockType qui détecte les blocs avec supports.jsx = true dans leurs métadonnées bootstrappées côté JS, et remplace edit par InnerBlocks et save par InnerBlocks.Content. Ce filtre est enqueué inconditionnellement sur enqueue_block_editor_assets – sans dépendre d’une liste PHP côté serveur, l’ancienne approche fragile.

// Filtre JS - correction inner blocks avec autoRegister
wp.hooks.addFilter(
  'blocks.registerBlockType',
  'juz/fix-inner-blocks',
  ( settings, name ) => {
    if ( settings?.supports?.jsx ) {
      return {
        ...settings,
        edit: ( { clientId } ) => wp.element.createElement( wp.blockEditor.InnerBlocks ),
        save: () => wp.element.createElement( wp.blockEditor.InnerBlocks.Content ),
      };
    }
    return settings;
  }
);

Nettoyage final

Phase finale : supprimer la compatibilité ACF qui avait servi de filet de sécurité pendant la migration. ModernFieldsAcfCompat.php – classe morte, supprimée. Mode dual ACF/WP dans RenderWithTwig – supprimé, −150 lignes. Mise à jour de toutes les références acf.phpmf.php dans les outils internes. Résultat : zéro appel acf_* / get_field ACF dans le code source.

PHPCS, PHPStan, ESLint, Stylelint, Prettier, GrumPHP au vert tout au long.

Ce que je retiens

La migration en elle-même est mécanique – 17 blocs, c’est du travail mais pas de la sorcellerie. Les deux points qui méritent attention sur n’importe quel projet :

  • Le stockage différent – prévoir un script de migration des attributs en base, tester sur une copie avant tout.
  • Le piège autoRegister + inner blocks sur WordPress 7.0 – si vous avez des blocs avec inner blocks et que vous passez à autoRegister, vérifiez que vos inner blocks survivent à une sauvegarde.

Modern Fields Pro est en beta (v0.9.1 au moment de cette migration). C’est à prendre en compte avant de l’embarquer sur un projet client en production – mais sur un Starter Kit, c’est exactement le bon endroit pour tester.


Un grand merci à Maxime BERNARD-JACQUET, développeur de Modern Fields, pour ce plugin et pour m’avoir accordé le privilège d’être parmi les premiers beta testeurs. 🙏

Stack : WordPress 7.0 · Bedrock · DDEV · Timber/Twig · Modern Fields 1.0.4 · Modern Fields Pro 0.9.1 (beta) · PHP 8.3 · PHPCS · PHPStan · ESLint · GrumPHP