Кастомные поля профиля пользователя в WordPress: добавление и вывод

Кастомные поля профиля пользователя в WordPress: добавление и вывод

Кратко: У каждого пользователя WordPress есть профиль с базовым набором данных: логин, почта, имя, сайт и описание. Но на реальном сайте этого почти всегда мало — нужны телефон, Telegram, город, должность, номер договора или ссылка на соцсети. Добавить такие поля можно без плагинов, своими силами. Форма выводится двумя хуками — show_user_profile и edit_user_profile, а значения сохраняются на хуках personal_options_update и edit_user_profile_update в метаполя пользователя. Читаются они функцией get_user_meta() — так же, как обычные метаполя записи. Есть и более короткий путь: фильтр user_contactmethods добавляет готовые текстовые поля одной функцией. Ниже — рабочие сниппеты для добавления, безопасного сохранения и вывода полей, разбор частых ошибок и таблица с хуками.

Что такое поля профиля пользователя и зачем они нужны

Профиль пользователя в WordPress — это набор данных, которые хранятся в таблице wp_users и связанной с ней wp_usermeta. Часть полей стандартная и есть у каждого: логин, email, отображаемое имя, пароль, роль. Всё остальное — это произвольные метаполя, которые вы добавляете сами.

Зачем вообще расширять профиль? Причины бывают разные, но чаще всего встречаются три сценария. Первый — сайт-каталог авторов или команда: у каждого сотрудника нужно показать должность, телефон, мессенджер и фото. Второй — клиентский портал или личный кабинет, где у пользователя хранится номер договора, город доставки или реквизиты. Третий — редакционные проекты: у авторов спрашивают соцсети и короткое био, которые затем выводятся под статьями.

Физически все дополнительные данные ложатся в таблицу метаполей wp_usermeta в виде пар «ключ — значение». Например, поле телефона хранится под ключом ds_phone, а его значение — это обычная строка. Такой подход стандартен для WordPress и не ломает ядро: при обновлении системы ваши данные никуда не денутся, потому что вы работаете через официальные функции, а не правите таблицы напрямую.

Важно сразу понять разницу между двумя подходами. Фильтр user_contactmethods меняет только список способов связи и годится для простых текстовых полей. Свои хуки show_user_profile и edit_user_profile дают полный контроль: можно вывести выпадающий список, чекбокс, поле загрузки файла или собственный блок с любой разметкой. Дальше разберём оба варианта, начиная с гибкого.

Какие поля есть в профиле WordPress по умолчанию

Прежде чем добавлять новое, полезно точно знать, что уже есть. В ядре WordPress профиль содержит несколько групп полей, и все они доступны через объект пользователя и функции-обёртки.

  • Учётные данные: логин user_login, пароль, подтверждение email.
  • Имя и контакты: имя first_name, фамилия last_name, псевдоним nickname, отображаемое имя display_name, email, сайт user_url.
  • О себе: биографическое описание description и, если включено, аватар.
  • Способы связи (contact methods): по умолчанию это сайт, Jabber/Google Talk и другие поля, которые фильтруются.
  • Служебные: роль wp_capabilities, язык админки, цвета схемы, включённые горячие клавиши, визуальный редактор.

Каждое из этих значений читается функциями вида get_the_author_meta( 'description' ), get_userdata( $id ) или универсальным get_user_meta(). Отображаемое имя, email и сайт удобно получать через get_the_author_meta( 'display_name', $id ) — такая запись лучше, чем обращение к произвольному метаполю, потому что эти данные ядро держит в отдельном кэше.

Отдельно стоит помнить про права доступа. Редактировать чужой профиль может только пользователь с правом edit_users, а свой — любой авторизованный с правом read. Поэтому при выводе полей на фронте никогда не полагайтесь на то, что данные заполнены: у части пользователей они будут пустыми. Более тонкая настройка ролей и возможностей разобрана в статье про пользовательские роли и права доступа в WordPress — она пригодится, если вы решите, что часть полей заполняют только редакторы.

Как добавить свои поля на страницу профиля: хуки show_user_profile и edit_user_profile

Страница профиля в админке — это файл user-edit.php, и в нужных местах он вызывает два действия. Хук show_user_profile срабатывает, когда пользователь открывает собственный профиль, а edit_user_profile — когда администратор редактирует чужой профиль. Чтобы форма работала в обоих случаях, функцию вешают на оба хука.

Добавьте этот код в functions.php дочерней темы или в собственный плагин:

<?php
/**
 * Выводим свои поля в профиле пользователя.
 */
add_action( 'show_user_profile', 'ds_render_profile_fields' );
add_action( 'edit_user_profile', 'ds_render_profile_fields' );

function ds_render_profile_fields( $user ) {
    // Текущие значения метаполей; пустая строка, если поле ещё не заполнено.
    $phone    = get_user_meta( $user->ID, 'ds_phone', true );
    $city     = get_user_meta( $user->ID, 'ds_city', true );
    $telegram = get_user_meta( $user->ID, 'ds_telegram', true );
    ?>
    <h2>Дополнительная информация</h2>
    <table class="form-table" role="presentation">
        <tr>
            <th><label for="ds_phone">Телефон</label></th>
            <td>
                <input type="text" name="ds_phone" id="ds_phone"
                       value="<?php echo esc_attr( $phone ); ?>"
                       class="regular-text" />
                <p class="description">Формат: +7 900 000-00-00.</p>
            </td>
        </tr>
        <tr>
            <th><label for="ds_city">Город</label></th>
            <td>
                <input type="text" name="ds_city" id="ds_city"
                       value="<?php echo esc_attr( $city ); ?>"
                       class="regular-text" />
            </td>
        </tr>
        <tr>
            <th><label for="ds_telegram">Telegram</label></th>
            <td>
                <input type="text" name="ds_telegram" id="ds_telegram"
                       value="<?php echo esc_attr( $telegram ); ?>"
                       class="regular-text" />
            </td>
        </tr>
    </table>
    <?php
}

Разберём по частям. Функция принимает объект $user, из которого берётся ID. Значения читаются заранее в переменные, чтобы не вызывать get_user_meta() прямо внутри разметки: так код чище и легче отлаживается. Класс form-table и разметка со th/td не обязательны технически, но именно они дают привычный вид админки и не ломают адаптивную вёрстку.

Обязательный момент — esc_attr() вокруг каждого значения в атрибуте value. Если этого не сделать, пользователь с правом редактировать профиль сможет записать в поле кавычку и внедрить свой HTML в страницу админки. Это классическая XSS-уязвимость, и правила безопасного вывода универсальны для всего WordPress — базовый чек-лист собран в материале про безопасность WordPress для новичков.

Если полей много или они логически сгруппированы, вместо таблицы удобно вывести свой блок с классом и оформить его стилями. Главное — сохранить имена name, они должны совпадать с ключами метаполей, иначе сохранение не найдёт, куда писать.

Как сохранить значения: personal_options_update и безопасная очистка данных

Вывести поля — половина задачи. Вторая половина — сохранить их при отправке формы. За это отвечают два хука: personal_options_update для своего профиля и edit_user_profile_update для чужого. Оба передают ID пользователя, которого редактируют.

Сохранение требует трёх проверок: права на редактирование, nonce-защита от подделки запроса и очистка каждого значения. Вот полный пример:

<?php
/**
 * Сохраняем свои поля профиля.
 */
add_action( 'personal_options_update', 'ds_save_profile_fields' );
add_action( 'edit_user_profile_update', 'ds_save_profile_fields' );

function ds_save_profile_fields( $user_id ) {
    // 1. Проверяем право на редактирование этого пользователя.
    if ( ! current_user_can( 'edit_user', $user_id ) ) {
        return false;
    }

    // 2. Проверяем nonce из формы профиля (его выводит WordPress).
    if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'update-user_' . $user_id ) ) {
        return false;
    }

    // 3. Очищаем и сохраняем каждое поле.
    if ( isset( $_POST['ds_phone'] ) ) {
        update_user_meta( $user_id, 'ds_phone', sanitize_text_field( wp_unslash( $_POST['ds_phone'] ) ) );
    }
    if ( isset( $_POST['ds_city'] ) ) {
        update_user_meta( $user_id, 'ds_city', sanitize_text_field( wp_unslash( $_POST['ds_city'] ) ) );
    }
    if ( isset( $_POST['ds_telegram'] ) ) {
        $tg = sanitize_text_field( wp_unslash( $_POST['ds_telegram'] ) );
        $tg = ltrim( $tg, '@' ); // убираем собаку из начала, если её ввели.
        update_user_meta( $user_id, 'ds_telegram', $tg );
    }
}

Первая строка с проверкой current_user_can() обязательна. Без неё злоумышленник с минимальными правами может отправить POST-запрос на страницу профиля и переписать данные чужого аккаунта. Проверка nonce закрывает другой вектор — подделку запроса с чужого сайта (CSRF). Параметр nonce у формы профиля называется update-user_{ID}, и WordPress уже выводит его в разметке, поэтому дополнительно ничего печатать не нужно.

Третья часть — очистка. sanitize_text_field() удаляет теги и лишние пробелы, а wp_unslash() убирает экранирование, которое WordPress добавляет к суперглобальным массивам. Для полей с URL используйте esc_url_raw(), для email — sanitize_email(), для числа — absint(). Правило простое: данные приходят от пользователя, значит, им нельзя доверять ни в хранилище, ни при выводе.

Если добавляете поля, которые заполняет только администратор, проверьте право ещё строже — например, manage_options, а не edit_user. И помните, что update_user_meta() молча создаёт ключ, если его нет, — отдельный SQL-запрос писать не нужно.

Как вывести поля на сайте: get_user_meta и страница автора

Хранить данные мало — их нужно показывать посетителям. Основной инструмент здесь get_user_meta(). Функция принимает ID пользователя, ключ метаполя и третий параметр true, который означает «верни одно значение», а не массив.

<?php
// Внутри цикла записей, где автор текущей статьи — get_the_author_meta( 'ID' ).
$author_id = get_the_author_meta( 'ID' );

$phone    = get_user_meta( $author_id, 'ds_phone', true );
$city     = get_user_meta( $author_id, 'ds_city', true );
$telegram = get_user_meta( $author_id, 'ds_telegram', true );

if ( $phone ) {
    printf(
        '<p class="author-phone">Телефон: <a href="%s">%s</a></p>',
        esc_url( 'tel:' . preg_replace( '/[^0-9+]/', '', $phone ) ),
        esc_html( $phone )
    );
}

if ( $telegram ) {
    printf(
        '<p>Telegram: <a href="%s" rel="nofollow noopener" target="_blank">@%s</a></p>',
        esc_url( 'https://t.me/' . rawurlencode( $telegram ) ),
        esc_html( $telegram )
    );
}

Обратите внимание на экранирование: esc_html() для текста, esc_url() для ссылок, а телефон перед вставкой в tel: очищается от скобок и дефисов регулярным выражением. Для Telegram значение кодируется через rawurlencode() — это защищает ссылку от пробелов и спецсимволов.

Логичнее всего выводить такие данные в шаблоне страницы автора. Файл для неё называется author.php, а сами данные доступны через get_queried_object(), который в этом контексте возвращает объект WP_User. Если ваша тема не поддерживает отдельный шаблон автора, его можно создать в дочерней теме — как это правильно сделать и не потерять правки при обновлении, пошагово описано в гайде про дочернюю тему WordPress и в статье про кастомные шаблоны страниц.

Есть и альтернативный способ для простых случаев: те же данные можно вывести прямо в карточке автора через готовые функции the_author_meta() для стандартных полей и get_user_meta() для ваших. А если нужно отдать поля наружу, например для мобильного приложения, их удобно добавить в REST API — про это есть отдельный материал про REST API WordPress.

Поля в форме регистрации и в списке пользователей админки

Иногда поле нужно не только в профиле, но и при регистрации. Здесь помогает всё тот же get_user_meta() в паре с хуком register_form, а сохранение вешается на user_register. Для сайтов, где регистрация открыта, это самый частый запрос: запросить телефон при создании аккаунта.

<?php
// Поле в форме регистрации.
add_action( 'register_form', function () {
    ?>
    <p>
        <label for="ds_phone">Телефон<br />
        <input type="text" name="ds_phone" id="ds_phone" class="input"
               value="<?php echo esc_attr( wp_unslash( $_POST['ds_phone'] ?? '' ) ); ?>" />
        </label>
    </p>
    <?php
} );

// Сохранение поля после успешной регистрации.
add_action( 'user_register', function ( $user_id ) {
    if ( isset( $_POST['ds_phone'] ) ) {
        update_user_meta( $user_id, 'ds_phone', sanitize_text_field( wp_unslash( $_POST['ds_phone'] ) ) );
    }
} );

Если поле обязательное, добавьте проверку на хуке registration_errors и верните объект WP_Error при пустом значении. Иначе пользователь сможет зарегистрироваться без телефона, а вы получите пустую карточку. Ещё важный нюанс: значение в поле подставляется из $_POST, чтобы после ошибки валидации форма не теряла уже введённые данные.

Второй сценарий — показать поле прямо в списке всех пользователей, чтобы не открывать каждого. Список формируется на фильтре manage_users_columns, а содержимое ячейки выводится на manage_users_custom_column:

<?php
// Добавляем колонку «Телефон» в таблицу пользователей.
add_filter( 'manage_users_columns', function ( $columns ) {
    $columns['ds_phone'] = 'Телефон';
    return $columns;
} );

// Заполняем ячейку значением метаполя.
add_filter( 'manage_users_custom_column', function ( $output, $column, $user_id ) {
    if ( 'ds_phone' === $column ) {
        $phone = get_user_meta( $user_id, 'ds_phone', true );
        return $phone ? esc_html( $phone ) : '&mdash;';
    }
    return $output;
}, 10, 3 );

Такой столбец заметно ускоряет работу администратора: не нужно открывать каждый профиль, чтобы найти нужный контакт. Аналогично можно добавить колонку с городом или ролью. Список пользователей на больших сайтах грузится постранично, поэтому дополнительные запросы метаполей не станут проблемой; но если полей много, стоит помнить о кэше — как её устроить, рассказано в статье про кэширование WordPress.

Таблица: хуки и функции для работы с полями профиля

Сведём в одну таблицу основные инструменты, чтобы держать их под рукой.

Хук или функция Что делает Где используется Параметры
show_user_profile Выводит поля в своём профиле Админка, страница профиля $user
edit_user_profile Выводит поля в чужом профиле Админка, редактирование пользователя $user
personal_options_update Сохраняет поля своего профиля Отправка формы профиля $user_id
edit_user_profile_update Сохраняет поля чужого профиля Отправка формы пользователя $user_id
user_contactmethods Быстро добавляет текстовые поля связи Фильтр списка способов связи $methods
get_user_meta() Читает значение метаполя Шаблоны, плагины ID, ключ, single

Из таблицы видно закономерность: вывод и сохранение всегда идут парами. Если вы добавили поле на show_user_profile, но забыли про personal_options_update, оно появится в форме, но не сохранится. Это самая частая причина жалобы «я добавил поле, а значение не записывается».

Для самых простых случаев достаточно одного фильтра user_contactmethods — он добавляет поле и автоматически берёт на себя сохранение, потому что WordPress сам обрабатывает эти «способы связи». Такой путь короче, но менее гибкий: вы не сможете задать описание поля, тип ввода или свою разметку.

Загрузка изображения и аватара в профиль

Отдельная задача — дать пользователю загрузить свою картинку: логотип компании, фото специалиста или просто аватар. Простейший путь — использовать штатную медиабиблиотеку и хранить в метаполе не сам файл, а ID вложения. Тогда вывод делается одной функцией wp_get_attachment_image(), а размеры WordPress генерирует сам.

<?php
// Кнопка выбора изображения в профиле (упрощённый вариант).
function ds_render_avatar_field( $user ) {
    $image_id = (int) get_user_meta( $user->ID, 'ds_avatar_id', true );
    $preview  = $image_id ? wp_get_attachment_image( $image_id, 'thumbnail' ) : '';
    ?>
    <h2>Фото профиля</h2>
    <table class="form-table"><tr>
        <th><label for="ds_avatar_id">Изображение</label></th>
        <td>
            <div class="ds-avatar-preview"><?php echo $preview; ?></div>
            <input type="hidden" name="ds_avatar_id" id="ds_avatar_id"
                   value="<?php echo esc_attr( $image_id ); ?>" />
            <button type="button" class="button" id="ds-pick-avatar">Выбрать</button>
            <p class="description">Загрузите квадратное изображение от 200×200 px.</p>
        </td>
    </tr></table>
    <?php
}

Кнопка «Выбрать» открывает медиабиблиотеку через небольшой скрипт на JavaScript, подключённый через хуки admin_enqueue_scripts. Внутри используется объект wp.media и его метод open(); после выбора ID вложения записывается в скрытое поле. При сохранении значение прогоняется через absint(), потому что это число. Так вы не храните путь к файлу жёстко и можете переиспользовать одно изображение в разных размерах.

// JS: открываем медиабиблиотеку для выбора фото профиля.
( function ( $ ) {
    $( document ).on( 'click', '#ds-pick-avatar', function ( e ) {
        e.preventDefault();
        var frame = wp.media( {
            title: 'Выберите изображение',
            button: { text: 'Использовать' },
            multiple: false
        } );

        frame.on( 'select', function () {
            var attachment = frame.state().get( 'selection' ).first().toJSON();
            $( '#ds_avatar_id' ).val( attachment.id );
            $( '.ds-avatar-preview' ).html(
                '<img src="' + attachment.sizes.thumbnail.url + '" alt="" />'
            );
        } );

        frame.open();
    } );
} )( jQuery );

Если же вы просто хотите дать пользователям собственную аватарку вместо граватара, есть и штатный путь: включить в настройках обсуждения «Показывать аватары» и разрешить загрузку. Но у этого решения свои ограничения — оно опирается на сервисы и не всегда подходит для закрытых проектов. Собственное метаполе с ID вложения даёт полный контроль и работает быстрее.

И ещё: если сайт отправляет пользователям письма (например, уведомление о смене телефона), проверьте, что почта реально уходит — об этом подробно в статье про настройку SMTP и wp_mail.

Частые ошибки при работе с полями профиля

Большинство проблем с полями профиля возникает по нескольким типовым причинам. Вот список, который стоит проверить прежде, чем искать баг глубже.

  • Поле выводится, но не сохраняется. Забыт второй хук: к show_user_profile нужен personal_options_update, а к edit_user_profile — edit_user_profile_update.
  • Значение сохраняется для себя, но не для других. Функция сохранения повешена только на один из двух хуков обновления.
  • Данные теряются при отправке формы. Имя name в HTML не совпадает с ключом, по которому читается метаполе. Проверьте посимвольно.
  • Поле показывает «Array». Значение читается без третьего параметра true, и get_user_meta() вернул массив.
  • Ломается вёрстка админки. Использована произвольная разметка вместо table.form-table, либо не подключены стандартные стили.
  • В профиле чужого HTML. Значение выведено без esc_attr() или esc_html() — это дыра для XSS.
  • Поле пропадает после обновления темы. Код был в родительской теме; переносите правки в дочернюю или в отдельный плагин.

Ещё одна тонкость: любые правки такого рода лучше держать изолированными. Если добавить десяток функций подряд в functions.php без комментариев, через месяц в них легко запутаться. Хорошая практика — держать каждый блок с понятным префиксом и описанием, как в подборке про полезные сниппеты для functions.php. А если поле связано с формой на фронте, пригодится материал про контактную форму в WordPress и про то, как работают AJAX-запросы в admin-ajax.

Понимание хуков вообще — ключ к работе с профилем, ведь и вывод, и сохранение построены именно на них. Если термины action и filter пока не до конца ясны, начните с материала про хуки actions и filters в WordPress.

TimeWeb

Частые вопросы о кастомных полях профиля пользователя в WordPress

Как добавить своё поле в профиль пользователя WordPress?

Повесьте функцию на хуки show_user_profile и edit_user_profile — она выведет поле в форме профиля. Затем добавьте вторую функцию на personal_options_update и edit_user_profile_update для сохранения. Значение пишите в метаполе через update_user_meta(), а читайте через get_user_meta(). Для простых текстовых полей можно обойтись одним фильтром user_contactmethods.

Почему поле в профиле не сохраняется после нажатия «Обновить»?

Скорее всего не повешен хук сохранения. Для своего профиля это personal_options_update, для чужого — edit_user_profile_update. Вторая частая причина — несовпадение атрибута name у поля и ключа, по которому вы читаете метаполе. Проверьте обе вещи: вывод и сохранение должны идти парой.

Чем user_contactmethods отличается от show_user_profile?

user_contactmethods — это фильтр, который меняет список «способов связи» и добавляет простое текстовое поле. WordPress сам сохраняет такие поля, отдельный хук не нужен. Хуки show_user_profile и edit_user_profile дают полный контроль: можно вывести выпадающий список, чекбокс, блок загрузки файла и любую свою разметку, но сохранение придётся написать самому.

Как вывести значение поля профиля на сайте?

Используйте get_user_meta( $user_id, 'ds_phone', true ): третий параметр true вернёт одно значение, а не массив. ID автора в цикле записей даёт get_the_author_meta( 'ID' ). Результат обязательно экранируйте: esc_html() для текста и esc_url() для ссылок. Удобнее всего выводить такие данные в шаблоне author.php.

Безопасно ли сохранять поля профиля напрямую из POST?

Нет, если не сделать три проверки. Сначала current_user_can( 'edit_user', $user_id ) — право на редактирование, затем wp_verify_nonce() с параметром update-user_{ID} — защита от подделки запроса. И только потом очистка значения через sanitize_text_field() и wp_unslash(). Без этого можно потерять данные или получить уязвимость.

Можно ли добавить загрузку фото прямо в профиль пользователя?

Да. Удобнее всего хранить в метаполе ID вложения из медиабиблиотеки: кнопку открытия подключают через скрипт с объектом wp.media, а вывод делают функцией wp_get_attachment_image(). При сохранении ID прогоняется через absint(). Альтернатива — включить штатные аватары в настройках обсуждения, но это менее гибко.

Вывод

Расширение профиля пользователя в WordPress — задача из тех, что решаются своими силами за один вечер. Механика простая: вывод формы вешается на show_user_profile и edit_user_profile, сохранение — на personal_options_update и edit_user_profile_update, а данные живут в метаполях и читаются через get_user_meta(). Если поля простые и текстовые, хватит одного фильтра user_contactmethods.

Главное, что стоит унести из материала, — три правила. Первое: вывод и сохранение всегда идут парой, иначе значение не запишется. Второе: любые данные из формы проверяются правами, nonce и функциями очистки. Третье: всё, что выводится на страницу, экранируется через esc_attr(), esc_html() или esc_url(). Соблюдение этих трёх пунктов закрывает почти все проблемы, с которыми новички сталкиваются при работе с профилями.

Если добавлять код и настраивать профили некогда, это можно поручить команде, которая делает сайты на WordPress под ключ: мы настраиваем роли и права, свои поля, личные кабинеты и формы, а после запуска сопровождаем проект. Посмотрите услуги разработки и продвижения, изучите портфолио выполненных проектов и почитайте блог о сайтах и продвижении. Ещё полезно свериться с технической базой: материал про ACF поля в WordPress и кастомные типы записей помогут, когда полей станет по-настоящему много. Обсудить задачу и получить смету можно через форму контактов — подберём решение под ваш проект и бюджет.

Clearfy

Поделиться:
Telegram ВКонтакте