Сущности WordPress, свои поля и роль functions.php: нормальная модель данных без плагинов
Произвольные поля (post meta) для Custom Post Type не сохраняются или пустые после обновления. Решение через register_post_meta, add_meta_box и save_post с nonce — код, диагностика и типичные ошибки.
Требования
- 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'] ?? '');
});
Почему возникает:
- Не проверяется nonce — запрос может быть подделан; кроме того, без nonce форма могла не отправить поле.
- Хук
save_postсрабатывает на автосохранении и ревизиях — WordPress передаёт в хук ID автосохранения или ревизии; запись в meta идёт «не в тот» пост или перезаписывается пустым$_POST. - Ключ meta не зарегистрирован через
register_post_meta— в современных версиях WordPress для корректной работы REST и админки meta для CPT лучше регистрировать. - Логика в
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) означает «вернуть одно значение»; без него вернётся массив (нужно, если по одному ключу хранится несколько значений).
Проверка результата
В админке
- Открыть запись типа «Проекты».
- Ввести URL в поле «Репозиторий», нажать «Обновить».
- Обновить страницу редактирования — значение должно остаться.
Через 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.



Комментарии