Произвольные поля WordPress хранят данные записи: цену, подзаголовок или ссылку на источник. Метабокс — форма в редакторе, через которую эти данные заполняют.
Создадим блок «Источник» с названием и ссылкой: зарегистрируем поля, настроим сохранение и выведем их в шаблоне. Затем разберём доступ через REST API и альтернативный вариант на Carbon Fields.
Как хранятся произвольные поля
Поля лежат в таблице wp_postmeta: номер записи, ключ и значение. Работают с ними три функции:
get_post_meta( $post_id, $key, true )— прочитать одно значение. С третьим аргументомfalseвернётся массив всех значений с этим ключом.update_post_meta( $post_id, $key, $value )— записать значение. Если ключа нет, он будет создан.delete_post_meta( $post_id, $key )— удалить.
Ключи, которые начинаются с подчёркивания, WordPress считает служебными и не показывает в стандартной панели «Произвольные поля». Для полей с собственным метабоксом это то, что нужно: иначе одно значение окажется в редакторе дважды.
1. Создание метабокса через add_meta_box()
Пример — блок «Источник» с названием и ссылкой в боковой колонке редактора записи. Сначала регистрация и разметка формы в functions.php:
function mytheme_add_source_metabox() {
add_meta_box(
'mytheme_source', // ID блока
'Источник', // заголовок
'mytheme_render_source_metabox', // функция вывода
'post', // тип записи
'side' // колонка: normal, side или advanced
);
}
add_action( 'add_meta_boxes', 'mytheme_add_source_metabox' );
function mytheme_render_source_metabox( $post ) {
$title = get_post_meta( $post->ID, '_mytheme_source_title', true );
$url = get_post_meta( $post->ID, '_mytheme_source_url', true );
wp_nonce_field( 'mytheme_save_source', 'mytheme_source_nonce' );
?>
<p>
<label for="mytheme-source-title">Название</label>
<input type="text" id="mytheme-source-title" name="mytheme_source_title"
value="<?php echo esc_attr( $title ); ?>" class="widefat">
</p>
<p>
<label for="mytheme-source-url">Ссылка</label>
<input type="url" id="mytheme-source-url" name="mytheme_source_url"
value="<?php echo esc_attr( $url ); ?>" class="widefat">
</p>
<?php
}
wp_nonce_field() добавляет скрытый токен для проверки запроса. В WordPress nonce не является строго одноразовым: он действует ограниченное время и помогает защитить форму от CSRF. Право пользователя менять запись проверяется отдельно.
Значения в HTML-атрибутах экранируются через esc_attr(), чтобы кавычки и другие символы не повредили разметку.
2. Сохранение полей: nonce, права и очистка
function mytheme_save_source_metabox( $post_id ) {
$nonce = isset( $_POST['mytheme_source_nonce'] )
? sanitize_text_field( wp_unslash( $_POST['mytheme_source_nonce'] ) )
: '';
if ( ! wp_verify_nonce( $nonce, 'mytheme_save_source' ) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
$fields = array(
'_mytheme_source_title' => sanitize_text_field( wp_unslash( $_POST['mytheme_source_title'] ?? '' ) ),
'_mytheme_source_url' => esc_url_raw( wp_unslash( $_POST['mytheme_source_url'] ?? '' ) ),
);
foreach ( $fields as $key => $value ) {
if ( '' === $value ) {
delete_post_meta( $post_id, $key );
} else {
update_post_meta( $post_id, $key, $value );
}
}
}
add_action( 'save_post_post', 'mytheme_save_source_metabox' );
- Хук
save_post_postсрабатывает только для записей типаpost. Для страниц нуженsave_post_page, для своего типа —save_post_{тип}. Общийsave_postвызывается и для ревизий, и для всех типов сразу. - Проверка nonce помогает отклонить поддельную отправку формы. Проверка
DOING_AUTOSAVEпропускает автосохранение: оно не должно затирать значения метабокса. current_user_can( 'edit_post', $post_id )проверяет право редактировать именно эту запись, а не записи вообще.- Данные из
$_POSTприходят с экранированными кавычками:wp_unslash()снимает экранирование, затем значение очищается по типу. Текст — черезsanitize_text_field(), ссылка — черезesc_url_raw(), число — черезabsint(). - Пустое поле удаляет ключ, чтобы в базе не копились пустые строки.
В блочном редакторе такие метабоксы работают: блок с 'side' появится в боковой панели записи, остальные — под текстом. При нажатии «Сохранить» редактор отдельным запросом отправляет форму метабоксов, и хук срабатывает как в классическом редакторе.
3. Вывод произвольных полей в шаблоне
В single.php внутри цикла, например после текста записи:
<?php
$source_title = get_post_meta( get_the_ID(), '_mytheme_source_title', true );
$source_url = get_post_meta( get_the_ID(), '_mytheme_source_url', true );
if ( $source_url ) :
$label = $source_title ? $source_title : wp_parse_url( $source_url, PHP_URL_HOST );
?>
<p class="post-source">
Источник: <a href="<?php echo esc_url( $source_url ); ?>" rel="nofollow"><?php echo esc_html( $label ); ?></a>
</p>
<?php endif; ?>
Значения экранируются ещё раз при выводе: esc_url() для адреса, esc_html() для текста. Очистка при сохранении не отменяет экранирования в шаблоне — поле могло попасть в базу другим путём, например через импорт.
Регистрация поля для REST API
Если поле нужно в REST API или в собственных блоках редактора, его регистрируют через register_post_meta(). Так WordPress знает тип поля, функцию очистки и кто может его менять:
function mytheme_register_source_meta() {
register_post_meta( 'post', '_mytheme_source_url', array(
'type' => 'string',
'single' => true,
'sanitize_callback' => 'esc_url_raw',
'show_in_rest' => true,
'auth_callback' => function ( $allowed, $meta_key, $post_id ) {
return current_user_can( 'edit_post', $post_id );
},
) );
}
add_action( 'init', 'mytheme_register_source_meta' );
Зарегистрированное поле доступно в объекте meta REST API, если тип записи поддерживает custom-fields. Не включай show_in_rest для секретных данных: регистрация поля может сделать его значение доступным при чтении записи.
В примере auth_callback задаёт проверку права изменять защищённое поле с подчёркиванием. Функция очистки из register_post_meta() применяется и при вызове update_post_meta().
Метабокс на Carbon Fields
Carbon Fields — библиотека, которая строит метабоксы из описания полей. Форму, nonce и сохранение она берёт на себя. Удобна, когда полей много или нужны повторяющиеся группы, картинки и условия показа. Подключается плагином или через Composer.
<?php
use Carbon_Fields\Container;
use Carbon_Fields\Field;
function mytheme_source_fields() {
Container::make( 'post_meta', 'Источник' )
->where( 'post_type', '=', 'post' )
->set_context( 'side' )
->add_fields( array(
Field::make( 'text', 'mytheme_source_title', 'Название' ),
Field::make( 'text', 'mytheme_source_url', 'Ссылка' )
->set_attribute( 'type', 'url' ),
) );
}
add_action( 'carbon_fields_register_fields', 'mytheme_source_fields' );
- Строки
useпишутся в начале PHP-файла, вне функций. Удобно вынести поля в отдельный файл, напримерinc/carbon-fields.php, и подключить его изfunctions.php. - Если библиотека установлена через Composer, а не плагином, её нужно запустить:
\Carbon_Fields\Carbon_Fields::boot();на хукеafter_setup_theme. - Условия
where()ограничивают показ: по типу записи, шаблону страницы (post_template), рубрике. Их можно комбинировать.
Читать значение лучше функцией библиотеки:
<?php
$source_url = carbon_get_post_meta( get_the_ID(), 'mytheme_source_url' );
Для чтения полей Carbon Fields используй carbon_get_post_meta(): так код не зависит от внутреннего способа хранения. Простые значения можно использовать в запросах к wp_postmeta, проверив фактическое имя ключа.
Составные поля complex читай через API библиотеки. Экранирование при выводе остаётся в шаблоне: атрибут type="url" у поля ввода не заменяет проверку данных на сервере.
Когда использовать API WordPress или библиотеку
- Одно-два простых поля — стандартный
add_meta_box. Нет зависимостей, всё под контролем. - Много полей, повторяющиеся блоки, галереи, страницы настроек — Carbon Fields или ACF. Меньше кода и единый интерфейс.
- Картинку записи не нужно хранить в произвольном поле: для этого есть миниатюры записей.