Стандартные поля WordPress — заголовок, контент, миниатюра — покрывают далеко не все задачи. Для каталога товаров нужна цена, для портфолио — ссылка на проект, для рецептов — время приготовления. Кастомные поля (post meta) решают эту задачу. Рассмотрим три подхода: нативные функции WordPress, собственные метабоксы и плагин ACF.
Нативные функции: add, get, update, delete
WordPress хранит кастомные поля в таблице wp_postmeta. Для работы с ними есть четыре базовые функции:
// Добавление мета-поля
add_post_meta( $post_id, '_product_price', 1500 );
// Получение значения
$price = get_post_meta( $post_id, '_product_price', true );
// true — вернуть одно значение (строку)
// false — вернуть массив всех значений с этим ключом
// Обновление (создаст, если не существует)
update_post_meta( $post_id, '_product_price', 2000 );
// Удаление
delete_post_meta( $post_id, '_product_price' );
// Получение ВСЕХ мета-полей записи
$all_meta = get_post_meta( $post_id );
// Вернёт ассоциативный массив: ключ => array( значения )
Префикс _ (нижнее подчёркивание) перед ключом скрывает поле из стандартного блока “Произвольные поля” в редакторе. Используйте его для полей, которые управляются через метабоксы или код.
Разница между add_post_meta и update_post_meta: add может создать несколько записей с одинаковым ключом (если передать четвёртый параметр false), update всегда обновляет существующую или создаёт одну новую.
Создание метабокса в админке
Метабокс — это блок в редакторе записи с вашими полями. Создадим метабокс для товара с ценой и артикулом:
// Регистрация метабокса
add_action( 'add_meta_boxes', 'product_add_meta_boxes' );
function product_add_meta_boxes() {
add_meta_box(
'product_details', // ID
'Характеристики товара', // заголовок
'product_meta_box_callback', // функция вывода
'post', // тип записи (или массив типов)
'normal', // позиция: normal, side, advanced
'high' // приоритет: high, core, default, low
);
}
// Вывод HTML метабокса
function product_meta_box_callback( $post ) {
// Nonce для безопасности
wp_nonce_field( 'product_meta_save', 'product_meta_nonce' );
$price = get_post_meta( $post->ID, '_product_price', true );
$sku = get_post_meta( $post->ID, '_product_sku', true );
$avail = get_post_meta( $post->ID, '_product_available', true );
echo '<table class="form-table">';
echo '<tr>';
echo '<th><label for="product_price">Цена (руб.)</label></th>';
echo '<td><input type="number" id="product_price" name="product_price" value="' . esc_attr( $price ) . '" min="0" step="0.01" class="regular-text"></td>';
echo '</tr>';
echo '<tr>';
echo '<th><label for="product_sku">Артикул</label></th>';
echo '<td><input type="text" id="product_sku" name="product_sku" value="' . esc_attr( $sku ) . '" class="regular-text"></td>';
echo '</tr>';
echo '<tr>';
echo '<th>В наличии</th>';
echo '<td><label><input type="checkbox" name="product_available" value="1" ' . checked( $avail, '1', false ) . '> Да</label></td>';
echo '</tr>';
echo '</table>';
}
// Сохранение данных
add_action( 'save_post', 'product_save_meta', 10, 2 );
function product_save_meta( $post_id, $post ) {
// Проверка nonce
if ( ! isset( $_POST['product_meta_nonce'] ) ||
! wp_verify_nonce( $_POST['product_meta_nonce'], 'product_meta_save' ) ) {
return;
}
// Проверка прав
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// Пропускаем автосохранение
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// Сохраняем поля
if ( isset( $_POST['product_price'] ) ) {
update_post_meta( $post_id, '_product_price',
sanitize_text_field( $_POST['product_price'] )
);
}
if ( isset( $_POST['product_sku'] ) ) {
update_post_meta( $post_id, '_product_sku',
sanitize_text_field( $_POST['product_sku'] )
);
}
// Чекбокс: если не отмечен, $_POST не содержит ключ
update_post_meta( $post_id, '_product_available',
isset( $_POST['product_available'] ) ? '1' : '0'
);
}
Три проверки при сохранении обязательны: nonce защищает от CSRF-атак, current_user_can — от несанкционированного доступа, DOING_AUTOSAVE — от перезаписи при автосохранении.
Вывод кастомных полей в шаблоне
После сохранения поля доступны в шаблонах через get_post_meta:
<?php
// В single.php или content-single.php
$price = get_post_meta( get_the_ID(), '_product_price', true );
$sku = get_post_meta( get_the_ID(), '_product_sku', true );
$avail = get_post_meta( get_the_ID(), '_product_available', true );
if ( $price ) : ?>
<div class="product-info">
<p class="product-price">
Цена: <strong><?php echo esc_html( number_format( $price, 0, ',', ' ' ) ); ?> руб.</strong>
</p>
<?php if ( $sku ) : ?>
<p class="product-sku">Артикул: <?php echo esc_html( $sku ); ?></p>
<?php endif; ?>
<p class="product-availability">
<?php echo $avail === '1' ? '✓ В наличии' : '✗ Нет в наличии'; ?>
</p>
</div>
<?php endif; ?>
Всегда экранируйте вывод через esc_html или esc_attr. Даже если вы сами сохраняете данные, привычка экранировать вывод предотвращает XSS-уязвимости.
Запросы по мета-полям через WP_Query
Кастомные поля можно использовать для фильтрации записей:
// Товары дешевле 5000 руб., в наличии, отсортированные по цене
$products = new WP_Query( array(
'post_type' => 'post',
'posts_per_page' => 20,
'meta_query' => array(
'relation' => 'AND',
'price_clause' => array(
'key' => '_product_price',
'value' => 5000,
'compare' => '<=',
'type' => 'NUMERIC',
),
array(
'key' => '_product_available',
'value' => '1',
'compare' => '=',
),
),
'orderby' => 'price_clause', // сортировка по мета-полю
'order' => 'ASC',
) );
// Записи, у которых ЕСТЬ определённое мета-поле
$with_price = new WP_Query( array(
'meta_key' => '_product_price',
'meta_query' => array(
array(
'key' => '_product_price',
'compare' => 'EXISTS',
),
),
) );
Учтите, что meta_query создаёт JOIN к таблице postmeta, что может замедлить запросы при большом количестве записей. Для высоконагруженных сайтов рассмотрите таксономии вместо мета-полей для фильтрации.
ACF: Advanced Custom Fields
ACF — самый популярный плагин для работы с кастомными полями. Он предоставляет визуальный конструктор полей и удобные функции для шаблонов:
// Получение значения ACF-поля
$price = get_field( 'product_price' ); // текущая запись
$price = get_field( 'product_price', $post_id ); // конкретная запись
// Вывод значения
the_field( 'product_price' ); // echo get_field(...)
// Группа полей (Group)
$details = get_field( 'product_details' );
echo $details['color'];
echo $details['weight'];
// Repeater (повторитель) — PRO версия
if ( have_rows( 'specifications' ) ) :
echo '<table>';
while ( have_rows( 'specifications' ) ) : the_row();
echo '<tr>';
echo '<td>' . esc_html( get_sub_field( 'spec_name' ) ) . '</td>';
echo '<td>' . esc_html( get_sub_field( 'spec_value' ) ) . '</td>';
echo '</tr>';
endwhile;
echo '</table>';
endif;
// Поле-изображение (возврат как массив)
$image = get_field( 'product_photo' );
if ( $image ) :
echo '<img src="' . esc_url( $image['sizes']['medium'] ) . '" '
. 'alt="' . esc_attr( $image['alt'] ) . '" '
. 'width="' . $image['sizes']['medium-width'] . '">';
endif;
// Условная логика в шаблоне
if ( get_field( 'show_banner' ) ) {
get_template_part( 'template-parts/banner' );
}
ACF хранит данные в той же таблице wp_postmeta, что и нативные функции. Поэтому get_post_meta( $id, 'product_price', true ) вернёт то же значение, что и get_field( 'product_price', $id ). Это значит, что вы можете начать с ACF для удобства, а потом перейти на нативные функции без миграции данных.
Когда что использовать
- Нативные функции — когда нужно 1-3 простых поля и вы не хотите зависеть от плагина. Идеально для тем и плагинов, которые распространяются.
- Свои метабоксы — когда нужен полный контроль над интерфейсом и логикой сохранения. Подходит для сложных кастомных решений.
- ACF — когда нужно много разных полей, повторители, гибкое содержимое. Ускоряет разработку в разы, но добавляет зависимость от плагина.
Независимо от выбранного подхода, помните о безопасности: всегда проверяйте nonce при сохранении, валидируйте и санитизируйте входные данные, экранируйте вывод. Кастомные поля — мощный инструмент, который превращает WordPress из блог-платформы в полноценную CMS для любых задач.
