REST API в WordPress: Создаём свои endpoints без головной боли

Представьте ситуацию. Вы пишете мобильное приложение для своего магазина. Или делаете headless-фронтенд на Next.js. Вам нужны данные. Вы идёте в стандартный REST API WordPress, делаете запрос к /wp-json/wp/v2/posts… и получаете 150 килобайт JSON-а. Там всё: контент, отрывки, даты, авторы, таксономии, рендеренный HTML. А вам нужен только заголовок и цена.

Знакомо?

Это как заказать в ресторане стакан воды, а вам приносят ведро, удочку и справку от ихтиолога. Вода там есть. Но таскать это неудобно.

В чём же дело? Стандартный API хорош. Но он универсальный. А универсальность часто означает компромисс. Сегодня научимся готовить собственные endpoints. Точечно. Быстро. Без лишнего мусора.

Зачем вообще лезть в API?

Честно говоря, в большинстве ситуаций хватает стандартных маршрутов. Но когда проект растёт, начинаются проблемы. Лишние запросы к базе. Медленная отдача JSON. Невозможность фильтровать данные так, как нужно фронтенду.

Кастомный endpoint исправляет ситуацию. Вы сами определяете, что отдавать. Хотите собрать данные из трёх разных таблиц и отдать одним красивым массивом? Легко. Хотите отдать только ID и цену? Без проблем.

Шаг 1. Создаём почтовый ящик (register_rest_route)

Любой маршрут в WordPress начинается с хука rest_api_init. Представьте, что вы вешаете новую табличку на здание. Нужно сказать системе: «Эй, по такому-то адресу теперь принимают данные».

add_action( 'rest_api_init', function() {
    register_rest_route( 'customcode/v1', '/products/featured', array(
        'methods'  => 'GET',
        'callback' => 'customcode_get_featured_products',
        'permission_callback' => '__return_true',
    ) );
} );

Здесь customcode/v1 — это неймспейс. Как домен первого уровня. А products/featured — сам путь. Метод GET означает, что мы только забираем данные. Если нужно отправлять — пишем POST.

Шаг 2. Пишем логику (Callback-функция)

Теперь нужно наполнить ящик содержимым. Функция-обработчик принимает объект WP_REST_Request и должна вернуть WP_REST_Response.

function customcode_get_featured_products( WP_REST_Request $request ) {
    // Делаем лёгкий запрос к базе
    $products = get_posts( array(
        'post_type'      => 'product',
        'posts_per_page' => 5,
        'meta_key'       => '_featured',
        'meta_value'     => 'yes',
        'fields'         => 'ids', // Берём только ID, это важно для скорости!
    ) );

    $data = array();
    foreach ( $products as $product_id ) {
        $product = wc_get_product( $product_id );
        if ( $product ) {
            $data[] = array(
                'id'    => $product_id,
                'title' => $product->get_name(),
                'price' => $product->get_price(),
                'image' => wp_get_attachment_image_url( $product->get_image_id(), 'thumbnail' ),
            );
        }
    }

    return new WP_REST_Response( $data, 200 );
}

Заметьте, мы не тянем весь пост. Мы берём только ID, заголовок и мета-поле цены. JSON получается лёгким. Фронтенд рендерит его мгновенно.

Шаг 3. Валидация. Не верьте никому.

Позвольте объяснить одну вещь. Никогда, слышите, никогда не отдавайте данные без проверки прав. Даже если вам кажется, что этот endpoint скрыт от глаз. Боты найдут его за секунды.

WordPress требует аргумент permission_callback для каждого маршрута. Для публичных данных возвращаем __return_true. Для приватных — проверяем возможности пользователя.

// Пример для приватного endpoint
'permission_callback' => function() {
    return current_user_can( 'manage_woocommerce' );
},

Если проверка не пройдена, WordPress сам вернёт ошибку 401 Unauthorized. Вам не нужно писать эту логику вручную.

Шаг 4. Принимаем параметры

Эндпоинт без параметров мёртв. Он статичен. Давайте научим его принимать данные. Допустим, мы хотим фильтровать товары по категории.

register_rest_route( 'customcode/v1', '/products', array(
    'methods'  => 'GET',
    'callback' => 'customcode_get_products',
    'permission_callback' => '__return_true',
    'args'     => array(
        'category_id' => array(
            'required'          => false,
            'validate_callback' => function( $param ) {
                return is_numeric( $param );
            },
            'sanitize_callback' => 'absint',
            'default'           => 0,
        ),
    ),
) );

Мы указали тип integer (через проверку is_numeric) и задали дефолтное значение. Если пользователь передаст строку вместо числа, WordPress сам вернёт ошибку 400. Красота.

А внутри callback-функции вы просто берёте это значение:

$category_id = $request->get_param( 'category_id' );

Шаг 5. Тестируем через терминал

Хватит писать код. Пора смотреть, что получилось. Открываем терминал.

# Запрос к публичному endpoint
curl -X GET https://custom-code.ru/wp-json/customcode/v1/products/featured

# Запрос с параметром
curl -X GET "https://custom-code.ru/wp-json/customcode/v1/products?category_id=15"

# Запрос к приватному endpoint (с авторизацией)
curl -X GET https://custom-code.ru/wp-json/customcode/v1/private-data \
     -H "Authorization: Bearer YOUR_JWT_TOKEN"

Если в ответ прилетел чистый, лёгкий JSON без мусора — поздравляю. Вы только что создали свой первый кастомный API.

Частые ошибки (на которых все обжигаются)

Ошибка 401 Unauthorized на публичном маршруте

Вы забыли permission_callback. WordPress теперь требует его обязательно. Без него маршрут просто не зарегистрируется, либо будет отдавать 401. Всегда добавляйте 'permission_callback' => '__return_true' для открытых данных.

Ошибка 404 Not Found

Забыли сбросить постоянные ссылки? Или опечатались в неймспейсе. Проверьте URL дважды. Неймспейс регистрозависим: customcode/v1 и CustomCode/v1 — это разные вещи.

Тяжёлые запросы и падение базы

Не забывайте про кэширование. Если ваш endpoint делает 50 запросов к БД, оберните его в transient. Иначе база встанет под нагрузкой.

function customcode_get_featured_products( WP_REST_Request $request ) {
    $cache_key = 'customcode_featured_products';
    $data = get_transient( $cache_key );

    if ( false === $data ) {
        // ... тяжёлая логика получения данных ...
        set_transient( $cache_key, $data, HOUR_IN_SECONDS );
    }

    return new WP_REST_Response( $data, 200 );
}

Как отдать файлы или картинки?

REST API по умолчанию отдаёт JSON. Если вам нужно отдать PDF или CSV, меняйте заголовки прямо в callback-функции:

$response = new WP_REST_Response( $file_content );
$response->header( 'Content-Type', 'application/pdf' );
$response->header( 'Content-Disposition', 'attachment; filename="report.pdf"' );
return $response;

Итог

Свой REST API в WordPress — это не высшая математика. Это просто ещё один инструмент. Иногда нужно отдать мало данных. Иногда — собрать информацию из пяти разных таблиц в один красивый JSON. Кастомные маршруты дают эту свободу.

Откройте functions.php. Зарегистрируйте первый маршрут. Посмотрите, как он работает. Это чувство стоит потраченных десяти минут.