Developing Custom WooCommerce Theme
Standard WooCommerce looks standard. Storefront theme is starting point, not end result. Custom WooCommerce theme means full control over catalog templates, product card, cart, and checkout without overriding hundreds of CSS rules over someone else's code. Custom theme development from design to production — 10 to 20 business days depending on custom templates and non-standard elements.
Theme Structure
WooCommerce looks for templates in woocommerce/ folder inside active theme. If file found — it overrides original from plugin:
wp-content/themes/my-theme/
├── woocommerce/
│ ├── archive-product.php # Catalog page
│ ├── single-product.php # Product card
│ ├── cart/
│ │ └── cart.php # Cart
│ ├── checkout/
│ │ ├── form-checkout.php # Checkout form
│ │ └── thankyou.php # Confirmation page
│ ├── myaccount/
│ │ └── dashboard.php # Account
│ ├── content-product.php # Product element (card in list)
│ ├── single-product/
│ │ ├── tabs/
│ │ │ └── tabs.php # Product tabs
│ │ └── related.php # Related products
│ └── loop/
│ ├── pagination.php
│ └── add-to-cart.php
└── functions.php
Full template list: wp-content/plugins/woocommerce/templates/.
Product Card Override
Instead of copying content-product.php with minimal edits — write from scratch for design:
<?php
// woocommerce/content-product.php
defined('ABSPATH') || exit;
global $product;
if (!$product || !$product->is_visible()) return;
$product_id = $product->get_id();
$permalink = get_the_permalink();
$thumbnail_url = get_the_post_thumbnail_url($product_id, 'woocommerce_thumbnail');
$badge = get_post_meta($product_id, '_product_badge', true);
?>
<article <?php wc_product_class('product-card', $product); ?>>
<a href="<?= esc_url($permalink) ?>" class="product-card__image-wrap" tabindex="-1">
<?php if ($thumbnail_url) : ?>
<img
src="<?= esc_url($thumbnail_url) ?>"
alt="<?= esc_attr(get_the_title()) ?>"
loading="lazy"
class="product-card__image"
>
<?php else : ?>
<div class="product-card__image product-card__image--placeholder"></div>
<?php endif; ?>
<?php if ($badge) : ?>
<span class="product-card__badge"><?= esc_html($badge) ?></span>
<?php endif; ?>
<?php if ($product->is_on_sale()) : ?>
<span class="product-card__sale"><?= esc_html(wc_get_sale_flash()) ?></span>
<?php endif; ?>
</a>
<div class="product-card__body">
<a href="<?= esc_url($permalink) ?>" class="product-card__title">
<?= esc_html(get_the_title()) ?>
</a>
<div class="product-card__footer">
<span class="product-card__price"><?= wp_kses_post($product->get_price_html()) ?></span>
<?php if ($product->is_in_stock()) : ?>
<?php woocommerce_template_loop_add_to_cart(['class' => 'btn btn--primary btn--sm']); ?>
<?php else : ?>
<span class="product-card__outofstock">Out of stock</span>
<?php endif; ?>
</div>
</div>
</article>
Hooks for Element Addition
Many changes possible via hooks without modifying templates:
// Remove rating from catalog card
remove_action('woocommerce_after_shop_loop_item_title', 'woocommerce_template_loop_rating', 5);
// Add SKU
add_action('woocommerce_after_shop_loop_item_title', function () {
global $product;
$sku = $product->get_sku();
if ($sku) {
echo '<span class="product-card__sku">SKU: ' . esc_html($sku) . '</span>';
}
}, 6);
// Move price before title
remove_action('woocommerce_single_product_summary', 'woocommerce_template_single_price', 10);
add_action('woocommerce_single_product_summary', 'woocommerce_template_single_price', 5);
Custom Product Gallery
WooCommerce uses Flexslider for gallery — heavy and outdated. Replace with custom:
// Disable standard gallery
remove_action('woocommerce_product_thumbnails', 'woocommerce_show_product_thumbnails', 20);
remove_action('woocommerce_before_single_product_summary', 'woocommerce_show_product_images', 20);
// Use custom
add_action('woocommerce_before_single_product_summary', 'my_theme_product_gallery', 20);
function my_theme_product_gallery(): void {
global $product;
$main_image_id = $product->get_image_id();
$gallery_ids = $product->get_gallery_image_ids();
$all_image_ids = $main_image_id ? array_merge([$main_image_id], $gallery_ids) : $gallery_ids;
if (empty($all_image_ids)) {
echo wc_placeholder_img('woocommerce_single');
return;
}
echo '<div class="product-gallery" data-lightbox="product">';
foreach ($all_image_ids as $idx => $image_id) {
$full = wp_get_attachment_image_url($image_id, 'full');
$large = wp_get_attachment_image_url($image_id, 'woocommerce_single');
$alt = get_post_meta($image_id, '_wp_attachment_image_alt', true) ?: get_the_title();
printf(
'<a href="%s" class="product-gallery__slide%s" data-index="%d">
<img src="%s" alt="%s" loading="%s">
</a>',
esc_url($full),
$idx === 0 ? ' product-gallery__slide--active' : '',
$idx,
esc_url($large),
esc_attr($alt),
$idx === 0 ? 'eager' : 'lazy'
);
}
echo '</div>';
}
WooCommerce Support Declaration
Theme must explicitly declare WooCommerce support:
add_action('after_setup_theme', function () {
add_theme_support('woocommerce', [
'thumbnail_image_width' => 450,
'single_image_width' => 800,
'product_grid' => [
'default_rows' => 3,
'min_rows' => 1,
'default_columns' => 4,
'min_columns' => 2,
'max_columns' => 6,
],
]);
});
Typical timelines: custom theme with main template overrides and basic CSS — 5–7 days. Full theme with custom gallery, filters, AJAX cart and responsiveness — 12–18 days.







