WordPress · 9 мин чтения

Как перейти с ACF на Gutenberg: что переносится и чего это стоит

Ко мне регулярно приходят с одним и тем же запросом: сайт держится на ACF Pro последние пять-семь лет, у редакторов в админке десяток полей Flexible Content, в которых без инструкции не разобраться, а разработчик хочет перейти с ACF на Gutenberg, потому что нативные блоки проще поддерживать и не тянут за собой отдельную лицензию плагина. Сразу скажу честно: выкинуть ACF полностью получается не в каждом проекте, и не всегда это вообще нужно. Вопрос не в том, «ACF или Gutenberg», а в том, что конкретно из существующих полей переезжает в блоки, а что остаётся как есть в базе данных.

Что реально переносится при переходе с ACF на блоки Gutenberg

Первое, что стоит держать в голове до старта: значения ACF-полей и так лежат в wp_postmeta - обычный meta_key и meta_value, а для repeater и Flexible Content вдобавок ещё пачка служебных ключей с индексами вроде field_0_title и field_0_text. Эти данные никуда физически не переезжают сами по себе. Переход с ACF на Gutenberg - это не перенос базы, а смена способа, которым содержимое попадает в post_content или остаётся в postmeta.

На типичном сайте на ACF это обычно смесь одиночных текстовых и числовых полей, галерей, полей связи relationship и одной-двух групп Flexible Content под конструктор лендинга внутри страницы - переносятся они по-разному, в зависимости от типа и от того, насколько часто редактор их меняет.

Блоки Gutenberg хранят атрибуты прямо в post_content - JSON внутри HTML-комментария перед разметкой блока. Это принципиально другой способ хранения, чем у ACF, где значение живёт отдельно от текста записи. Из-за этого есть три рабочих сценария, и они дают разную цену и разный объём работ:

  • оставить данные в postmeta, но рендерить их через ACF-блоки - самый дешёвый вариант, при котором ACF как плагин остаётся;
  • оставить данные в postmeta, а в блоках подключить нужные атрибуты к ним через Block Bindings API - ACF можно постепенно убрать из части полей;
  • переписать содержимое post_content так, чтобы значения хранились прямо в атрибутах блока, а ACF-поля и сам плагин отключить полностью.

На практике для интернет-магазина на WooCommerce, где ACF-репитер собирал таблицу характеристик товара, я обычно оставляю данные в postmeta и просто меняю способ вывода - переписывать сотни карточек товаров в блочную разметку экономически не оправдано, если таблица характеристик и так исправно работает.

ACF-блоки и нативные блоки Gutenberg: в чём разница

ACF Pro регистрирует свои блоки штатной функцией WordPress register_block_type(), которой передают путь до папки с файлом block.json, а все настройки самого ACF лежат внутри block.json под ключом acf, где путь до PHP-шаблона называется renderTemplate. Старая функция acf_register_block_type() с версии ACF 6.0 объявлена устаревшей, и документация прямо советует регистрировать блоки через block.json. Технически это всё тот же блок Gutenberg, просто поля для него собираются в привычном интерфейсе ACF, а не в JavaScript, и блок появляется в инспекторе блоков с нужными полями. Это самый быстрый способ получить блочный интерфейс, если field group уже существует - ACF остаётся зависимостью сайта, но редактор работает в блочном редакторе, а не в метабоксах под текстом записи.

Нативный блок Gutenberg без ACF собирается иначе: файл block.json с описанием атрибутов, плюс либо JS-файлы edit.js и save.js для интерактивного редактирования в браузере, либо PHP-шаблон, подключённый через свойство render в block.json - тогда блок рендерится на сервере и писать React не нужно вовсе. Разница в трудозатратах ощутима: блок на ACF я обычно собираю за несколько часов, если поля уже описаны в field group, а нативный блок с собственной логикой редактирования в браузере занимает от одного дня, если нужна кастомная область редактирования, а не просто вывод готового текста.

Flexible Content и повторяющиеся поля - самая дорогая часть переноса

Простые текстовые и числовые поля переносятся почти без раздумий - один ACF-field превращается в один атрибут блока или в одну привязку через Block Bindings API. Сложности начинаются с repeater и особенно с Flexible Content, где на одной странице может быть восемь-девять разных макетов: баннер, три колонки, слайдер, форма заявки, цитата, и у каждого свой набор полей.

Каждый layout из Flexible Content при переезде на блоки становится отдельным кастомным блоком: свой block.json, свой рендер, своя логика в редакторе. Если на сайте девять layouts, это девять блоков, которые нужно спроектировать, написать и протестировать по отдельности, а не один общий модуль. Именно на этом этапе бюджет миграции обычно утяжеляется - в моей практике клиент закладывает перенос полей, а по факту заказывает перепроектирование блочной структуры всей страницы.

Repeater внутри блока чаще всего превращается в InnerBlocks с ограниченным списком разрешённых дочерних блоков - повторяющийся набор полей заменяется повторяющимся набором вложенных блоков одного типа. Для редактора это удобнее: можно менять порядок перетаскиванием, добавлять и убирать элементы прямо в превью страницы. Но это означает, что старый контент, введённый через repeater, нужно программно пересобрать в такую структуру, а не просто скопировать значения полей один в один.

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

Block Bindings API как способ не отказываться от ACF резко

В WordPress 6.5 появилась Block Bindings API - штатный механизм, который подключает атрибут блока к внешнему источнику данных, включая произвольные поля записи, без написания кастомного блока. Работает это через свойство metadata.bindings прямо в разметке блока:

<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"tour_price"}}}}} -->
<p>Плейсхолдер</p>
<!-- /wp:paragraph -->

Источник core/post-meta идёт в комплекте с ядром WordPress и умеет подставлять значение произвольного поля записи в текст абзаца или заголовка, в URL и alt картинки, в текст и ссылку кнопки. Одной привязки при этом мало: этот источник видит только то поле, которое зарегистрировано через register_meta со show_in_rest в значении true, а ключи, начинающиеся с подчёркивания, недоступны как защищённые. ACF свои поля так не регистрирует, поэтому регистрацию приходится дописывать руками:

add_action( 'init', function () {
    register_meta( 'post', 'tour_price', array(
        'show_in_rest' => true,
        'single'       => true,
        'type'         => 'string',
    ) );
} );

После этого для простых одиночных ACF-полей Block Bindings действительно заменяет кастомный блок: подключили атрибут к ключу поля, и редактор видит обычный блок «Абзац», просто с данными из старого поля под капотом.

Ограничение честное: Block Bindings поддерживает только определённый набор атрибутов у определённого набора блоков и не умеет работать с повторяющимися структурами - Flexible Content и repeater через неё не заменить, для них всё равно придётся собирать полноценный блок. Для сайта из предыдущего раздела с девятью layouts это не решение, а для лендинга, где ACF используется под десяток одиночных текстовых и графических полей, вполне достаточно - и ACF там реально можно снести совсем.

Три сценария перехода и от чего зависит цена

Сценарий Что происходит с ACF Объём разработки
ACF-блоки (block.json + register_block_type) плагин остаётся, поля работают как раньше минимальный - обёртка существующей field group в блок
Block Bindings API для одиночных полей ACF можно отключить для этих полей, для остальных остаётся среднее - разметка привязок и чистка старых метабоксов
Полный переход на нативные блоки с переносом Flexible Content ACF отключается полностью максимальный - блок под каждый layout плюс скрипт миграции контента

У меня разработка сайта на WordPress с переносом на блочный редактор идёт как обычный проект на WordPress - от 60 000 ₽, и в эту сумму входит аудит существующих field group и первая итерация блоков. Если у сайта девять layouts во Flexible Content и интернет-магазин на WooCommerce поверх, реалистичная оценка выше базовой планки - точную цифру называю после аудита, потому что до разбора конкретных полей и вложенности layouts это гадание, а не расчёт. Сам аудит и оценку проекта я оформляю как консультацию - от 3 000 ₽, и по её итогам отдаю смету по этапам, а не одну цифру на весь проект.

После переноса на блочный редактор большинство правок сводится к точечным доработкам вёрстки блоков, а не к правке PHP руками. Такое сопровождение у меня закрывает техподдержка - от 15 000 ₽ в месяц: слежу, чтобы обновления WordPress не ломали кастомные блоки, и донастраиваю мелочи, которые вылезают уже после переезда на новый редактор.

Как перенести контент без потери данных: пошаговый план

  1. Провести аудит: выгрузить список field group и отметить тип каждого поля - текст, число, картинка, repeater, Flexible Content, relationship.
  2. Разделить поля на кандидатов для Block Bindings API (одиночные простые значения) и кандидатов для кастомных блоков (repeater, Flexible Content, галереи).
  3. Спроектировать блоки: атрибуты, список разрешённых дочерних блоков для InnerBlocks, PHP-шаблон рендера.
  4. Написать миграционный скрипт, который проходит по записям через WP_Query, читает значения через get_field(), собирает блочную разметку строкой и обновляет запись через wp_update_post(). Такие скрипты я запускаю через wp eval-file в WP-CLI - так проще откатиться, если что-то пошло не так, и виден вывод по каждой записи.
  5. Прогнать миграцию на копии базы и сверить визуально десяток случайных записей до и после.
  6. Показать редакторам новый интерфейс блоков и только после этого выкатывать изменения на продакшен.
  7. Отключить field group для перенесённых полей, а сам плагин ACF - только если все поля на сайте закрыты блоками или Block Bindings.

Последний пункт часто пропускают: отключают ACF, пока где-то в футере или в опциях темы остаётся поле, которое всё ещё на нём завязано, и сайт отваливается кусками. Перед тем как деактивировать ACF насовсем, я всегда проверяю зависимости плагина поиском по теме на get_field и the_field.

WordPress и Block Bindings API регулярно обновляются, поэтому перед переходом с ACF я сверяюсь с актуальной документацией.

Частые вопросы

Обязательно ли полностью удалять ACF после перехода на блоки Gutenberg?

Нет. Если часть полей закрыта ACF-блоками или Block Bindings, а часть всё ещё выводится классическим способом, плагин можно оставить работать дальше - он не мешает нативным блокам на той же странице. Убирать ACF целиком имеет смысл, только когда ни одно поле на сайте больше от него не зависит, иначе это лишний риск ради экономии на одном плагине.

Что произойдёт со старыми записями, если удалить field group сразу?

Значения полей останутся в базе - ACF при удалении field group из интерфейса не трогает postmeta, если явно не выбрать удаление данных. Но если код темы или блоков всё ещё вызывает get_field() для несуществующей группы, вывод может стать пустым или непредсказуемым, поэтому я сначала переношу вывод на блоки и только потом убираю field group, а не наоборот.

Можно ли обойтись ACF-блоками и не переписывать всё на нативный Gutenberg?

Да, и для многих проектов это разумный компромисс: редакторы получают блочный интерфейс, а поля остаются в привычном ACF без переписывания на JavaScript. Минус в том, что плагин остаётся обязательной зависимостью сайта, а его лицензия и продление никуда не деваются.

Сколько времени занимает переход с ACF на блоки Gutenberg?

Зависит от количества layouts во Flexible Content и от того, нужен ли перенос уже существующего контента или только новый интерфейс для будущих записей. Сайт с парой repeater и без миграции старых записей я делаю за несколько дней, а магазин на WooCommerce с девятью layouts и переносом всей истории товаров занимает уже несколько недель - точный срок называю после аудита field group.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно