← Назад в блог

Сущности WordPress, свои поля и роль functions.php: нормальная модель данных без плагинов

Произвольные поля (post meta) для Custom Post Type не сохраняются или пустые после обновления. Решение через register_post_meta, add_meta_box и save_post с nonce — код, диагностика и типичные ошибки.

Сущности WordPress, свои поля и роль functions.php: нормальная модель данных без плагинов

Требования

  • WordPress 6.x
  • Базовое понимание PHP
  • Доступ к functions.php темы

Сущности WordPress, свои поля и роль functions.php

Запрос wordpress custom fields functions php или произвольные поля не сохраняются часто приводит к ситуации: вы завели Custom Post Type (CPT), добавили метабокс с полем в админке, при сохранении записи значение не попадает в базу — get_post_meta($post_id, 'my_key', true) возвращает пустую строку. Ниже — одна конкретная проблема: почему meta для своего типа записи не пишется в wp_postmeta и как это исправить кодом в теме без плагинов (register_post_meta, nonce, приоритет хука). Если у вас ACF и поле не сохраняется, отдельный сценарий разобран в статье ACF поле не сохраняется в WordPress.


В чём проблема

Симптомы:

  • В админке у записи CPT есть метабокс с полем, вы вводите значение и нажимаете «Обновить».
  • После перезагрузки страницы редактирования поле пустое.
  • В базе в таблице wp_postmeta для этого post_id нет строки с нужным meta_key или значение не обновилось.
  • В шаблоне или в коде get_post_meta($post_id, '_repo_url', true) возвращает пусто.

Пример типичной ошибки в коде: сохранение без проверки nonce и без отсечения autosave/revision — в итоге meta перезаписывается пустым при автосохранении или ревизии:

// ❌ Плохо: срабатывает на автосохранении и ревизиях, перетирает meta
add_action('save_post', function ($post_id) {
    update_post_meta($post_id, '_repo_url', $_POST['project_repo_url'] ?? '');
});

Почему возникает:

  1. Не проверяется nonce — запрос может быть подделан; кроме того, без nonce форма могла не отправить поле.
  2. Хук save_post срабатывает на автосохранении и ревизиях — WordPress передаёт в хук ID автосохранения или ревизии; запись в meta идёт «не в тот» пост или перезаписывается пустым $_POST.
  3. Ключ meta не зарегистрирован через register_post_meta — в современных версиях WordPress для корректной работы REST и админки meta для CPT лучше регистрировать.
  4. Логика в functions.php без разделения — десятки хуков и полей в одном файле приводят к конфликтам приоритетов и случайному перезаписыванию.

Итог: одна статья = одна задача — сделать так, чтобы произвольное поле для CPT стабильно сохранялось и читалось, с путём к файлам и проверкой результата.

Как быстро проверить: откройте запись CPT в админке, введите значение в своё поле и нажмите «Обновить». Перезагрузите страницу редактирования. Если поле пустое — проблема в сохранении (хук, nonce или autosave). Если в админке значение есть, а в шаблоне пусто — проверьте ключ в get_post_meta() и то, что вы читаете тот же post_id (в цикле используйте get_the_ID()).


Рабочее решение

Используем один CPT (например, project) и одно метаполе (URL репозитория). Код разнесён по файлам темы, чтобы не превращать functions.php в свалку.

Стек: WordPress 6.x, PHP 7.4+, тема с доступом к functions.php и папке inc/.

Путь к файлам:

theme/
├── functions.php
├── inc/
│   ├── cpt.php
│   ├── meta.php

1) Регистрация CPT

Файл: inc/cpt.php

<?php
add_action('init', function () {
    register_post_type('project', [
        'label'       => 'Проекты',
        'public'      => true,
        'supports'    => ['title', 'editor'],
        'has_archive' => true,
        'rewrite'     => ['slug' => 'projects'],
    ]);
});

2) Регистрация meta и метабокс

Файл: inc/meta.php

Регистрируем meta для типа project через register_post_meta, чтобы ключ был известен ядру (REST, админка). Ключ с подчёркиванием в начале (_repo_url) скрывает поле из блока «Произвольные поля» в редакторе.

<?php
add_action('init', function () {
    register_post_meta('project', '_repo_url', [
        'type'              => 'string',
        'single'            => true,
        'sanitize_callback' => 'esc_url_raw',
        'auth_callback'     => function ($allowed, $meta_key, $post_id) {
            return current_user_can('edit_post', $post_id);
        },
    ]);
});

add_action('add_meta_boxes', function () {
    add_meta_box(
        'project_repo',
        'Репозиторий',
        'render_project_repo_box',
        'project'
    );
});

function render_project_repo_box($post) {
    wp_nonce_field('project_repo_save', 'project_repo_nonce');
    $value = get_post_meta($post->ID, '_repo_url', true);
    ?>
    <input
        type="url"
        name="project_repo_url"
        value="<?php echo esc_attr($value); ?>"
        style="width:100%;"
    />
    <?php
}

3) Сохранение с проверкой nonce и без autosave/revision

Тот же файл: inc/meta.php

add_action('save_post_project', function ($post_id) {
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }
    if (wp_is_post_revision($post_id)) {
        return;
    }
    if (!isset($_POST['project_repo_nonce'])
        || !wp_verify_nonce($_POST['project_repo_nonce'], 'project_repo_save')) {
        return;
    }
    if (!current_user_can('edit_post', $post_id)) {
        return;
    }
    if (!isset($_POST['project_repo_url'])) {
        return;
    }

    update_post_meta(
        $post_id,
        '_repo_url',
        esc_url_raw($_POST['project_repo_url'])
    );
});

4) Подключение в теме

Файл: functions.php

<?php
require_once __DIR__ . '/inc/cpt.php';
require_once __DIR__ . '/inc/meta.php';

5) Вывод в шаблоне

<?php
$repo_url = get_post_meta(get_the_ID(), '_repo_url', true);
if ($repo_url) {
    printf(
        '<a href="%s" target="_blank" rel="noopener">Репозиторий</a>',
        esc_url($repo_url)
    );
}

Этого достаточно, чтобы поле стабильно сохранялось и отображалось. Дополнительные варианты (OOP, таксономия): сниппет CPT и таксономия (OOP), сниппет метабокс с безопасным сохранением (OOP).

Почему ключ с подчёркиванием: в редакторе WordPress блок «Произвольные поля» выводит все meta записи. Ключи, начинающиеся с _, в этом блоке не показываются — так служебные поля не путаются с теми, что редактор может случайно изменить. Для полей, которые заполняет только ваш код или метабокс, используйте префикс _. Третий параметр true в get_post_meta($id, '_repo_url', true) означает «вернуть одно значение»; без него вернётся массив (нужно, если по одному ключу хранится несколько значений).


Проверка результата

В админке

  1. Открыть запись типа «Проекты».
  2. Ввести URL в поле «Репозиторий», нажать «Обновить».
  3. Обновить страницу редактирования — значение должно остаться.

Через WP-CLI

Подставьте свой post_id (например, 123):

wp post meta get 123 _repo_url

Ожидаемый вывод: строка с URL, например https://github.com/user/repo. Пустой вывод означает, что meta не записалось — тогда смотрите раздел «Типичные ошибки».

В коде (временная проверка)

В шаблоне single для project или в цикле:

<?php
$value = get_post_meta(get_the_ID(), '_repo_url', true);
var_dump($value); // должно вывести сохранённый URL

После проверки var_dump уберите.


Типичные ошибки

1. Хук на save_post без типа поста и без отсечения autosave/revision

Срабатывает на всех типах записей и на автосохранении — meta перетирается. Используйте save_post_project (или свой тип) и в начале обработчика проверяйте DOING_AUTOSAVE и wp_is_post_revision($post_id).

2. Не проверяется nonce

Без wp_verify_nonce и проверки current_user_can('edit_post', $post_id) возможна подмена запроса и запись meta от имени другого поста. Всегда проверяйте nonce и права.

3. Ключ meta без подчёркивания показывается в «Произвольных полях»

Редактор выводит все meta в блоке «Произвольные поля»; ключи с префиксом _ там не показываются. Для служебных полей используйте _repo_url, а не repo_url.

4. Логика в одном большом functions.php

Сотни строк в functions.php усложняют отладку и порядок хуков. Выносите регистрацию CPT и meta в отдельные файлы и подключайте через require_once, как в блоке выше.

5. Читаете meta по другому ключу

Убедитесь, что в get_post_meta($post_id, '_repo_url', true) ключ совпадает с тем, что в update_post_meta. Третий параметр true у get_post_meta возвращает одно значение (строку).

Если после правок meta по-прежнему не сохраняется: убедитесь, что хук именно save_post_project (с суффиксом типа записи), а не общий save_post. Проверьте, что в форме есть wp_nonce_field() с тем же action, что и в wp_verify_nonce(). Временно отключите другие плагины и переключитесь на тему Twenty Twenty-Four — если поле начнёт сохраняться, конфликт в теме или плагине. Для отладки добавьте в начало обработчика save_post_project строку error_log('save_post_project: ' . $post_id); и убедитесь, что хук срабатывает при нажатии «Обновить», а не только при автосохранении.


Где применять

  • Prod — решение предназначено для боевого сайта: регистрация meta, nonce и проверка прав обязательны.
  • Docker / локальный стенд — те же файлы; удобно проверить через WP-CLI и отключить лишние плагины, чтобы исключить конфликт хуков.
  • CI/CD — можно добавить smoke-тест: создание поста типа project, запись meta через WP-CLI или код, чтение обратно и проверка значения.

Резюме: одна проблема — meta для CPT не сохраняется. Решение — регистрация meta через register_post_meta, метабокс с nonce, хук save_post_{post_type} с отсечением autosave и revision и проверкой прав. Структура темы: functions.php только подключает inc/cpt.php и inc/meta.php. Проверка — админка, WP-CLI, при необходимости временный var_dump в шаблоне. Типичные ошибки — общий save_post, отсутствие nonce, ключ без _, один огромный functions.php, несовпадение ключа при чтении. Для нескольких метаполей повторите блок register_post_meta, метабокс и ветку в обработчике save_post_project для каждого ключа. Если CPT зарегистрирован в теме и вы планируете менять тему позже — данные в БД останутся, но тип записей в админке пропадёт, пока не подключите плагин или mu-plugin с той же регистрацией; для долгоживущих проектов регистрацию CPT и meta часто выносят в плагин. В REST API зарегистрированные через register_post_meta поля автоматически доступны для чтения и записи при корректных auth_callback и sanitize_callback.

Дополнительно по теме: WordPress Core Web Vitals 2026: ускорение без магии, Migrations Plugin: управление миграциями БД WordPress.

0 просмотров

Комментарии

Загрузка комментариев...
Пока нет комментариев. Будьте первым!