Skip to content

Всё для WordPress, веб-разработки — и не только

🔧 Как сделать тему WordPress готовой к WooCommerce: полное руководство для разработчиков

🔧 Как сделать тему WordPress готовой к WooCommerce: полное руководство для разработчиков

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

Проблема глубже пары CSS-правил. Начиная с WooCommerce 3.3, тема без объявленной поддержки получает упрощённый рендеринг через шорткоды и предупреждение в админке. Полный контроль над вёрсткой вы теряете.

Ниже — готовые сниппеты от объявления поддержки до живой корзины в меню. Каждый проверен на актуальной версии WooCommerce и разбит по разделам, от фундамента к тонкой настройке.

💡 Быстрый обзор:

  • Сначала тема должна явно заявить о поддержке WooCommerce, без этого не включатся переопределения шаблонов и часть функций
  • Код размещайте в отдельном файле (например inc/woocommerce.php), который подгружается только при активном плагине
  • Настройка сетки магазина требует двух фильтров: один меняет колонки, второй добавляет класс на body
  • Галерея товаров, зум, лайтбокс и слайдер включаются отдельными вызовами add_theme_support
  • Корзина в меню должна обновляться через AJAX, используйте фильтр add_to_cart_fragments

1. Фундамент: объявление поддержки и проверка активности

Первое и самое важное, тема должна явно сказать WooCommerce: «я тебя поддерживаю». Без этого плагин не активирует переопределения шаблонов и выводит предупреждение в админке. Начиная с WooCommerce 3.3, тема без объявленной поддержки получает упрощённый рендеринг через шорткоды, это работает, но ограничивает контроль над вёрсткой.

1add_action( 'after_setup_theme', function() {
2 add_theme_support( 'woocommerce' );
3} );

Важно использовать именно хук after_setup_theme, а не init, так рекомендует документация WooCommerce. С версии 3.3+ можно сразу передать настройки сетки:

1add_action( 'after_setup_theme', function() {
2 add_theme_support( 'woocommerce', array(
3 'thumbnail_image_width' => 150,
4 'single_image_width' => 300,
5 'product_grid' => array(
6 'default_rows' => 3,
7 'min_rows' => 2,
8 'max_rows' => 8,
9 'default_columns' => 4,
10 'min_columns' => 2,
11 'max_columns' => 5,
12 ),
13 ) );
14} );

Эти параметры задают значения по умолчанию для кастомайзера (WooCommerce → Product Images / Product Catalog) и избавляют пользователя от необходимости настраивать сетку вручную.

Проверка активности плагина

Когда вы отдаёте тему на распространение, нельзя просто сбросить WooCommerce-сниппеты в конец functions.php, их вызовет фатальную ошибку, если плагин не активен. Код должен загружаться только при работающем WooCommerce:

1define( 'MYTHEME_WOOCOMMERCE_ACTIVE', class_exists( 'WooCommerce' ) );
2
3if ( MYTHEME_WOOCOMMERCE_ACTIVE ) {
4 require_once get_template_directory() . '/inc/woocommerce.php';
5}

Константа вычисляется один раз, и дальше вы в любом месте темы можете обернуть вызов в if ( MYTHEME_WOOCOMMERCE_ACTIVE ). Это чище, чем каждый раз дёргать class_exists или is_plugin_active.

2. Управление стилями WooCommerce

WooCommerce поставляется с тремя CSS-файлами: woocommerce-general, woocommerce-layout и woocommerce-smallscreen. В идеале вы не удаляете их, а переопределяете в своей теме, тогда сторонние WooCommerce-расширения, которые опираются на эти классы, не ломаются. Но если вы пишете всё с нуля и хотите полного контроля, вот как их убрать.

Удалить все стили разом:

1add_filter( 'woocommerce_enqueue_styles', '__return_empty_array' );

Или убрать выборочно:

1function mytheme_remove_woo_styles( $styles ) {
2 unset( $styles['woocommerce-general'] );
3 unset( $styles['woocommerce-layout'] );
4 unset( $styles['woocommerce-smallscreen'] );
5 return $styles;
6}
7add_filter( 'woocommerce_enqueue_styles', 'mytheme_remove_woo_styles' );

На практике чаще применяют второй вариант: убирают woocommerce-layout (процентная сетка), но оставляют woocommerce-general (кнопки, сообщения, формы) и точечно перебивают их своим CSS с более высоким приоритетом.

3. Настройка страниц магазина

Сколько товаров показывать

Фильтр loop_shop_per_page управляет количеством товаров на странице магазина и в архивах (категории, теги):

1function mytheme_woo_posts_per_page( $cols ) {
2 return 12;
3}
4add_filter( 'loop_shop_per_page', 'mytheme_woo_posts_per_page' );

Количество колонок в сетке

Тут нужны два фильтра. Одного loop_shop_columns недостаточно: в отличие от шорткодов WooCommerce (у которых есть обёртка с классом columns-N), на страницах магазина такого класса нет. Поэтому добавляем его на body:

1function mytheme_woo_shop_columns( $columns ) {
2 return 4;
3}
4add_filter( 'loop_shop_columns', 'mytheme_woo_shop_columns' );
5
6function mytheme_woo_shop_columns_body_class( $classes ) {
7 if ( is_shop() || is_product_category() || is_product_tag() ) {
8 $classes[] = 'columns-4';
9 }
10 return $classes;
11}
12add_filter( 'body_class', 'mytheme_woo_shop_columns_body_class' );

Теперь в CSS можно писать селекторы .columns-4 ul.products li.product, и они будут применяться именно к сетке магазина.

Заголовок магазина

WooCommerce по умолчанию выводит «Shop» в качестве заголовка на странице магазина. Если ваша тема уже умеет показывать заголовки архивов, этот дубликат мешает:

1add_filter( 'woocommerce_show_page_title', '__return_false' );

Если же вы используете the_archive_title(), можно подменить заголовок на название страницы магазина (которое задаётся в админке):

1function mytheme_woo_archive_title( $title ) {
2 if ( is_shop() && $shop_id = wc_get_page_id( 'shop' ) ) {
3 $title = get_the_title( $shop_id );
4 }
5 return $title;
6}
7add_filter( 'get_the_archive_title', 'mytheme_woo_archive_title' );

4. Галерея товаров: зум, лайтбокс и слайдер

С версии WooCommerce 3.0 появилась новая галерея товаров на базе FlexSlider, PhotoSwipe и jQuery Zoom. В версиях 3.0-3.2 она выключена по умолчанию и требует явного включения. Начиная с 3.3 галерея включена для не-WooCommerce-тем и выключена для тем, заявивших поддержку, вы включаете нужные компоненты сами:

1add_theme_support( 'wc-product-gallery-slider' );
2add_theme_support( 'wc-product-gallery-zoom' );
3add_theme_support( 'wc-product-gallery-lightbox' );

Можно включить только часть: например, слайдер и зум, да, а лайтбокс заменить собственным решением с Fancybox. Каждая директива подгружает свои скрипты, поэтому лишнее лучше не включать.

Количество колонок в миниатюрах галереи (под основным изображением товара):

1function mytheme_woo_product_thumbnails_columns() {
2 return 4;
3}
4add_action( 'woocommerce_product_thumbnails_columns', 'mytheme_woo_product_thumbnails_columns' );

5. Сопутствующие и проданные товары (cross-sells / up-sells)

На странице товара WooCommerce выводит блоки «Сопутствующие товары» (related) и «С этим также покупают» (up-sells). Их количество и сетка настраиваются отдельно.

Сколько сопутствующих товаров показывать:

1function mytheme_woo_related_posts_per_page( $args ) {
2 $args['posts_per_page'] = 4;
3 return $args;
4}
5add_filter( 'woocommerce_output_related_products_args', 'mytheme_woo_related_posts_per_page' );

Колонки для up-sells и related-блоков, та же история, что с сеткой магазина: меняем и колонки, и класс на body:

1function mytheme_woo_single_loops_columns( $columns ) {
2 return 4;
3}
4add_filter( 'woocommerce_up_sells_columns', 'mytheme_woo_single_loops_columns' );
5
6function mytheme_woo_related_columns( $args ) {
7 $args['columns'] = 4;
8 return $args;
9}
10add_filter( 'woocommerce_output_related_products_args', 'mytheme_woo_related_columns', 10 );
11
12function mytheme_woo_single_loops_columns_body_class( $classes ) {
13 if ( is_singular( 'product' ) ) {
14 $classes[] = 'columns-4';
15 }
16 return $classes;
17}
18add_filter( 'body_class', 'mytheme_woo_single_loops_columns_body_class' );

Обратите внимание: у woocommerce_output_related_products_args два фильтра с разными приоритетами, mytheme_woo_related_posts_per_page (по умолчанию 10) меняет posts_per_page, а mytheme_woo_related_columns (приоритет 10) меняет columns. Они не конфликтуют, потому что работают с разными ключами одного массива $args.

6. Пагинация и значок распродажи

Стрелки пагинации магазина можно заменить на иконки вашей темы:

1function mytheme_woo_pagination_args( $args ) {
2 $args['prev_text'] = '<i class="fa fa-angle-left"></i>';
3 $args['next_text'] = '<i class="fa fa-angle-right"></i>';
4 return $args;
5}
6add_filter( 'woocommerce_pagination_args', 'mytheme_woo_pagination_args' );

Вместо Font Awesome подставьте свои классы иконок или прямые SVG.

Текст на значке распродажи (Sale!) легко переопределить, полезно для сайтов на другом языке или просто чтобы убрать восклицательный знак:

1function mytheme_woo_sale_flash() {
2 return '<span class="onsale">' . esc_html__( 'Sale', 'woocommerce' ) . '</span>';
3}
4add_filter( 'woocommerce_sale_flash', 'mytheme_woo_sale_flash' );

Для русского языка замените 'Sale' на 'Скидка', перевод подхватится автоматически, если у вас есть MO-файлы WooCommerce на русском.

7. Динамическая корзина в меню

Пожалуй, самый востребованный элемент в любой магазинной теме, иконка корзины в навигации с актуальной суммой. Реализация состоит из трёх частей: добавление элемента в меню, генерация HTML корзины и AJAX-обновление.

1// Добавление ссылки в меню
2function mytheme_add_menu_cart_item( $items, $args ) {
3 if ( $args->theme_location === 'primary' ) {
4 $css_class = 'menu-item menu-item-type-cart menu-item-type-woocommerce-cart';
5 if ( is_cart() ) {
6 $css_class .= ' current-menu-item';
7 }
8 $items .= '<li class="' . esc_attr( $css_class ) . '">';
9 $items .= mytheme_menu_cart_item();
10 $items .= '</li>';
11 }
12 return $items;
13}
14add_filter( 'wp_nav_menu_items', 'mytheme_add_menu_cart_item', 10, 2 );
15
16// HTML корзины
17function mytheme_menu_cart_item() {
18 $cart_count = WC()->cart->cart_contents_count;
19 $css_class = 'wpex-menu-cart-total wpex-cart-total-' . intval( $cart_count );
20 $url = $cart_count ? WC()->cart->get_cart_url() : wc_get_page_permalink( 'shop' );
21 $html = WC()->cart->get_cart_total();
22 $html = str_replace( 'amount', '', $html );
23
24 return '<a href="' . esc_url( $url ) . '" class="' . esc_attr( $css_class ) . '">'
25 . '<span class="cart-icon"></span>'
26 . wp_kses_post( $html )
27 . '</a>';
28}
29
30// AJAX-обновление фрагментов
31function mytheme_menu_cart_link_fragments( $fragments ) {
32 $fragments['.wpex-menu-cart-total'] = mytheme_menu_cart_item();
33 return $fragments;
34}
35add_filter( 'add_to_cart_fragments', 'mytheme_menu_cart_link_fragments' );

Критически важный момент: **не оборачивайте эти функции в **is_admin(). AJAX-запросы корзины приходят через admin-ajax.php, и если функция недоступна в админ-контексте, обновление цены в меню просто не сработает. theme_location замените на идентификатор своего меню (в примере primary).

8. Альтернативный подход: шаблон woocommerce.php

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

Создайте в корне темы файл woocommerce.php (копия page.php) и замените главный цикл на вызов woocommerce_content():

1<?php
2get_header(); ?>
3
4<div id="primary" class="content-area">
5 <main id="main" class="site-main">
6 <?php woocommerce_content(); ?>
7 </main>
8</div>
9
10<?php get_footer();
11

Этот метод проще, но у него есть цена: один шаблон на все WooCommerce-страницы, и магазин, и категории, и карточку товара. Тонкую настройку (разную сетку, разный сайдбар на разных страницах) делать сложнее. Выбирайте его, только если текущая тема совсем не дружит с WooCommerce на уровне разметки, и вы готовы принять ограничения.

⁉️🤔 Частые вопросы

Обязательно ли удалять стандартные стили WooCommerce?

Не обязательно. Более безопасный путь, оставить их и переопределять селекторы в своей теме с более высоким приоритетом. Так вы сохраняете совместимость с расширениями, которые опираются на стандартные классы WooCommerce. Полное удаление (__return_empty_array) оправдано только если вы пишете магазинную тему с нуля и точно знаете, что никакие сторонние плагины не будут добавлять элементы, стилизованные под WooCommerce. В большинстве случаев достаточно выборочно отключить woocommerce-layout (процентная сетка конфликтует с флексбоксами и гридами современных тем), а woocommerce-general и woocommerce-smallscreen оставить и аккуратно перебить через каскад CSS, так вы получаете чистую сетку без потери совместимости.

Почему не работает смена колонок в магазине?

Потому что одного фильтра loop_shop_columns недостаточно. WooCommerce использует классы .columns-N на обёртке для работы сетки, и они автоматически добавляются только к шорткодам ([products columns="4"]). На страницах магазина и архивах этой обёртки нет, класс нужно вешать на body через body_class. Два фильтра из раздела 3 решают проблему полностью.

Фильтр loop_shop_columns меняет PHP-переменную, которую WooCommerce передаёт в шаблон, но CSS-сетка завязана на класс-обёртку. Без класса columns-4 на body ваши стили ul.products li.product не имеют селектора-контекста и либо не применяются, либо бьют по всем продуктовым спискам на сайте.

Нужно ли пересобирать тему после обновления WooCommerce?

Если вы используете хуки (все сниппеты из этой статьи), нет. Хуки меняются крайне редко. Если вы переопределяете шаблоны (файлы из woocommerce/templates/), да, нужно сверять свои копии с актуальными после каждого мажорного обновления. Именно поэтому официальная документация рекомендует хуки как предпочтительный метод кастомизации.

Хуки, это контракт. WooCommerce гарантирует их стабильность между версиями. Шаблоны, это реализация, которая может измениться в любой момент: добавятся новые экшены, изменится разметка, переименуются классы. За пять лет существования хуков loop_shop_columns и woocommerce_output_related_products_args их сигнатура не менялась ни разу, а файл content-product.php за тот же период обновлялся десятки раз.

Можно ли использовать is_admin() для защиты функций корзины?

Нет, и это частая ловушка. AJAX-запросы WooCommerce (добавление в корзину, обновление количества) обрабатываются через admin-ajax.php, в этом контексте is_admin() возвращает true. Если вы обернёте функции корзины в if ( ! is_admin() ), AJAX-обновление фрагментов (add_to_cart_fragments) перестанет работать, и сумма в иконке корзины не будет меняться без перезагрузки страницы.

Оборачивать в ! is_admin() нужно только то, что не должно выполняться в админке (вывод HTML на фронте), но функции-обработчики AJAX должны быть доступны всегда. Для кода из раздела 7 не ставьте is_admin() вообще, WooCommerce сам вызывает нужные колбэки через свой AJAX-обработчик, и ограничение сломает логику.

Как добавить поддержку WooCommerce в блочную тему (FSE)?

Для блочных тем декларация поддержки не изменилась, тот же add_theme_support('woocommerce'). Но вместо хуков и переопределения PHP-шаблонов вы работаете в редакторе сайта: WooCommerce предоставляет блоки (Products, Product Search, Cart, Checkout), которые вставляются в шаблоны через Appearance → Editor. Тонкая кастомизация делается через theme.json и CSS-переменные.

Блочные темы принципиально меняют подход: вы не пишете PHP-хуки для вёрстки, а собираете страницы визуально из готовых блоков WooCommerce. Но фундамент, add_theme_support в functions.php, остаётся тем же самым. А для тонких правок, которые блоками не сделать (например, кастомный текст на кнопке «В корзину»), старые добрые фильтры из этой статьи работают и в FSE-темах.

Какую стратегию выбрать под ваш проект

Если вы дорабатываете существующую тему, начните с шагов 1 и 2: объявите поддержку и настройте сетку. В большинстве случаев этого достаточно, чтобы магазин выглядел прилично. Дальше добавляйте только те правки, которые решают конкретную проблему: кривую галерею, некрасивую пагинацию, отсутствие корзины в меню.

Пишете тему с нуля для продажи или распространения, пройдите весь список. Каждый пункт добавляет пользователю то, что он ожидает от магазинной темы «из коробки»: управляемую сетку, зум на товарах, живую иконку корзины. Код вынесите в inc/woocommerce.php и подгружайте по константе, тогда тема одинаково хорошо работает и как блог, и как магазин.

Отправная точка всегда одна и та же:

1add_action( 'after_setup_theme', function() {
2 add_theme_support( 'woocommerce' );
3} );

Восемь строчек. Всё остальное, детали, которые отличают тему «совместимую» от темы «заточенной».