• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » How to build custom WooCommerce core overrides extensions utilizing modern Shortcode API schemas

How to build custom WooCommerce core overrides extensions utilizing modern Shortcode API schemas

Leveraging the Shortcode API for Core WooCommerce Overrides

Directly modifying WooCommerce core files is a cardinal sin in WordPress development. It leads to unmaintainable code, lost customizations during updates, and a general mess. However, there are legitimate scenarios where you need to alter or extend the default behavior of WooCommerce’s output, particularly its product listings, cart, and checkout. The Shortcode API, while seemingly basic, offers a powerful and extensible mechanism for achieving these overrides without touching core files. This approach involves creating custom shortcodes that either completely replace or intelligently wrap existing WooCommerce shortcodes, allowing for granular control and maintainability.

Understanding WooCommerce’s Shortcode Architecture

WooCommerce registers several essential shortcodes that power its frontend display. Key among these are:

  • [woocommerce_cart]: Displays the shopping cart page.
  • [woocommerce_checkout]: Displays the checkout page.
  • [products]: Displays a list of products, highly configurable via attributes.
  • [product_category]: Displays a list of product categories.
  • [add_to_cart]: Displays an add-to-cart button for a specific product.
  • [product_page]: Displays a single product page.

These shortcodes are registered using WordPress’s built-in add_shortcode() function. The power lies in the fact that you can “override” these by registering a shortcode with the same name in your plugin or theme’s functions.php. WordPress will then execute your version instead of the original. However, a more robust and less intrusive method is to create new shortcodes that *call* the original WooCommerce shortcodes, allowing for modification of their output or attributes.

Strategy: Wrapper Shortcodes for Controlled Overrides

The most maintainable strategy is to create “wrapper” shortcodes. These new shortcodes will accept their own attributes, process them, potentially modify them, and then execute the original WooCommerce shortcode with the adjusted parameters. This keeps your customizations separate and clearly defined.

Example: Customizing the [products] Shortcode Output

Let’s say we want to display products with a specific class added to their container for custom styling, and perhaps enforce a default number of columns if none is provided. We’ll create a new shortcode, [custom_products], that wraps the original [products] shortcode.

Plugin Setup and Shortcode Registration

We’ll assume this code resides within a custom plugin. The core of this functionality is registering our new shortcode and defining its callback function.

custom-woocommerce-overrides.php (Main Plugin File)

<?php
/**
 * Plugin Name: Custom WooCommerce Core Overrides
 * Description: Demonstrates overriding WooCommerce shortcode behavior using custom shortcodes.
 * Version: 1.0.0
 * Author: Antigravity
 * Author URI: https://example.com
 * Text Domain: custom-wc-overrides
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
}

/**
 * Load plugin textdomain for internationalization.
 */
function custom_wc_overrides_load_textdomain() {
    load_plugin_textdomain( 'custom-wc-overrides', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
}
add_action( 'plugins_loaded', 'custom_wc_overrides_load_textdomain' );

/**
 * Register the custom_products shortcode.
 */
function register_custom_products_shortcode() {
    add_shortcode( 'custom_products', 'custom_products_shortcode_handler' );
}
add_action( 'init', 'register_custom_products_shortcode' );

/**
 * Handler for the custom_products shortcode.
 *
 * This function wraps the core WooCommerce [products] shortcode,
 * allowing for custom attribute manipulation and output filtering.
 *
 * @param array  $atts    Shortcode attributes.
 * @param string $content Content enclosed within the shortcode.
 * @return string HTML output.
 */
function custom_products_shortcode_handler( $atts, $content = null ) {
    // Ensure WooCommerce is active.
    if ( ! class_exists( 'WooCommerce' ) ) {
        return '<p>' . esc_html__( 'WooCommerce is not active.', 'custom-wc-overrides' ) . '</p>';
    }

    // Define default attributes and merge with user-provided ones.
    $default_atts = array(
        'columns'        => 4, // Default to 4 columns
        'orderby'        => 'date',
        'order'          => 'desc',
        'posts_per_page' => 12,
        'tax_query'      => '', // Placeholder for potential tax queries
        'class'          => '', // Custom class to add to the wrapper
    );

    $atts = shortcode_atts( $default_atts, $atts, 'custom_products' );

    // Sanitize and validate attributes.
    $atts['columns']        = absint( $atts['columns'] );
    $atts['posts_per_page'] = absint( $atts['posts_per_page'] );
    $atts['class']          = sanitize_text_field( $atts['class'] );

    // Prepare attributes for the original WooCommerce [products] shortcode.
    // We need to reconstruct the attribute string as the original shortcode expects it.
    // Note: This is a simplified approach. For complex attributes like tax_query,
    // you might need to parse and reconstruct them more carefully.
    $wc_atts = array();
    foreach ( $atts as $key => $value ) {
        // Skip our custom 'class' attribute as it's handled separately.
        if ( $key === 'class' ) {
            continue;
        }
        // Ensure empty values are not passed, unless they are intended (e.g., for tax_query).
        if ( $value !== '' || $key === 'tax_query' ) {
            $wc_atts[$key] = $value;
        }
    }

    // Add a custom class to the product loop wrapper.
    // This requires hooking into WooCommerce's template actions or filtering its output.
    // A more direct way is to modify the output *after* the shortcode runs.
    // For simplicity here, we'll add a class to the shortcode's output wrapper.
    // A more advanced approach would involve using WC_Shortcode_Products::get_content()
    // and filtering its return value, or using WC_Product_Query.

    // Let's simulate adding a class to the output wrapper.
    // The actual [products] shortcode outputs a div with class 'products'.
    // We'll wrap our output and add our custom class there.

    // To truly modify the output of the [products] shortcode, we'd typically
    // use filters like 'woocommerce_shortcode_products_query' or
    // 'woocommerce_product_loop_start' / 'woocommerce_product_loop_end'.
    // However, for a direct override demonstration, we can capture the output.

    // Capture the output of the original [products] shortcode.
    // This is a common technique for shortcode manipulation.
    ob_start();
    echo do_shortcode( '[products ' . build_shortcode_atts_string( $wc_atts ) . ']' );
    $products_output = ob_get_clean();

    // Now, let's wrap this output with our custom class.
    $custom_class = ! empty( $atts['class'] ) ? ' ' . esc_attr( $atts['class'] ) : '';
    $wrapper_class = 'custom-product-loop' . $custom_class;

    // We can also modify the output further here if needed.
    // For instance, adding a header or footer.

    $final_output = '<div class="' . esc_attr( $wrapper_class ) . '">';
    $final_output .= $products_output;
    $final_output .= '</div>';

    return $final_output;
}

/**
 * Helper function to build shortcode attribute string.
 * Similar to WordPress's internal function but for clarity.
 *
 * @param array $atts Attributes array.
 * @return string Shortcode attribute string.
 */
function build_shortcode_atts_string( $atts ) {
    $atts_string = '';
    foreach ( $atts as $key => $value ) {
        if ( is_array( $value ) ) {
            // Handle array attributes like tax_query if necessary.
            // For simplicity, we'll assume simple string values for now.
            // A more robust solution would serialize or format arrays correctly.
            $atts_string .= sprintf( '%s="%s" ', $key, implode( ',', $value ) );
        } else {
            $atts_string .= sprintf( '%s="%s" ', $key, esc_attr( $value ) );
        }
    }
    return trim( $atts_string );
}

// Example of how to add a filter to modify the query *before* the [products] shortcode runs.
// This is a more advanced and recommended way to customize.
function filter_woocommerce_products_query( $args ) {
    // Example: Always order by price if not specified otherwise.
    if ( ! isset( $args['orderby'] ) || $args['orderby'] === 'date' ) {
        $args['orderby'] = 'price';
        $args['order']   = 'asc'; // Or 'desc'
    }
    // Example: Add a specific category if not specified.
    if ( ! isset( $args['tax_query'] ) ) {
        $args['tax_query'] = array(
            array(
                'taxonomy' => 'product_cat',
                'field'    => 'slug',
                'terms'    => 'featured-products', // Replace with your category slug
                'operator' => 'IN',
            ),
        );
    }
    return $args;
}
// Uncomment the line below to enable the query filter.
// add_filter( 'woocommerce_shortcode_products_query', 'filter_woocommerce_products_query', 10, 1 );

Explanation of the Code

1. Plugin Boilerplate: Standard WordPress plugin headers and basic setup.

2. register_custom_products_shortcode(): This function hooks into init and uses add_shortcode() to register our new shortcode [custom_products], pointing to our handler function custom_products_shortcode_handler().

3. custom_products_shortcode_handler( $atts, $content ):

  • WooCommerce Check: Ensures WooCommerce is active before proceeding.
  • Attribute Merging: Defines default attributes (like columns, posts_per_page) and merges them with any attributes provided by the user in the shortcode.
  • Sanitization: Crucially, all attributes are sanitized (e.g., absint() for numbers, sanitize_text_field() for strings) to prevent security vulnerabilities and ensure valid input.
  • Attribute Reconstruction: The original [products] shortcode expects attributes in a specific format. We build a new array $wc_atts containing only the attributes relevant to the original shortcode. Our custom class attribute is handled separately.
  • Output Buffering: We use ob_start() and ob_get_clean() to capture the output of the original [products] shortcode. This is a common pattern for manipulating shortcode output. We call do_shortcode() with the reconstructed attributes.
  • Wrapper Div: The captured output is then wrapped in a new <div> with our custom class (e.g., custom-product-loop) and any additional classes provided by the user via the class attribute.

4. build_shortcode_atts_string(): A utility function to convert the attribute array back into a string format suitable for passing to do_shortcode().

5. Query Filtering (Commented Out): The commented-out section demonstrates a more advanced and often preferred method: using WordPress filters. The woocommerce_shortcode_products_query filter allows you to modify the query arguments *before* the products are fetched and displayed by the original shortcode. This is cleaner than output buffering for modifying query parameters.

Usage in WordPress Editor

Once the plugin is activated, you can use the new shortcode in your posts, pages, or even within other shortcodes:

[custom_products columns="3" posts_per_page="8" class="my-special-product-grid"]

This will render the product loop with 3 columns, 8 products per page, and apply the classes custom-product-loop and my-special-product-grid to the main wrapper div.

Advanced Techniques: Modifying Cart and Checkout

Overriding [woocommerce_cart] and [woocommerce_checkout] requires a similar approach but with more complexity due to the dynamic nature of these pages. You cannot simply wrap them with output buffering and expect to modify them easily, as they often rely on session data and complex template rendering.

Overriding [woocommerce_cart]

To modify the cart page, you’d typically create a custom page template and use the [woocommerce_cart] shortcode within it. If you need to alter the cart’s behavior or appearance *without* a custom template, you’ll need to hook into WooCommerce’s action and filter system.

Example: Adding a notice above the cart table

/**
 * Add a custom notice above the WooCommerce cart table.
 */
function custom_wc_cart_notice() {
    // Only display on the cart page and if WooCommerce is active.
    if ( is_cart() && class_exists( 'WooCommerce' ) ) {
        // Check if the cart is empty.
        if ( WC()->cart && ! WC()->cart->is_empty() ) {
            echo '<div class="woocommerce-info custom-cart-notice">' . esc_html__( 'Welcome back! Don\'t forget to check out our latest deals.', 'custom-wc-overrides' ) . '</div>';
        }
    }
}
add_action( 'woocommerce_before_cart_table', 'custom_wc_cart_notice', 10 );

/**
 * Example of overriding the [woocommerce_cart] shortcode itself.
 * This is generally NOT recommended for complex pages like cart/checkout.
 * Use hooks and template overrides instead.
 */
function custom_woocommerce_cart_shortcode( $atts ) {
    // Ensure WooCommerce is active.
    if ( ! class_exists( 'WooCommerce' ) ) {
        return '<p>' . esc_html__( 'WooCommerce is not active.', 'custom-wc-overrides' ) . '</p>';
    }

    // You could potentially modify $atts here if the original shortcode supported it.
    // However, [woocommerce_cart] has very limited attributes.

    // Capture the output of the original shortcode.
    ob_start();
    // The original shortcode doesn't accept attributes in the same way [products] does.
    // We simply call do_shortcode to render the default cart.
    echo do_shortcode( '[woocommerce_cart]' );
    $cart_output = ob_get_clean();

    // Now, wrap or modify $cart_output.
    // For instance, adding a custom class to the main cart div.
    // This is fragile as WooCommerce's output structure can change.
    $cart_output = str_replace( '<div class="woocommerce">', '<div class="woocommerce custom-cart-wrapper">', $cart_output );

    return $cart_output;
}
// Uncomment to override the shortcode (use with caution).
// add_shortcode( 'woocommerce_cart', 'custom_woocommerce_cart_shortcode' );

In this example, woocommerce_before_cart_table is a hook that fires just before the cart table is rendered. This is the preferred method for adding content or modifying behavior on the cart page without directly overriding the shortcode.

Overriding [woocommerce_checkout]

Similar to the cart, the checkout page is highly dynamic. Direct shortcode overriding is discouraged. Instead, leverage hooks like woocommerce_before_checkout_form, woocommerce_checkout_before_customer_details, woocommerce_checkout_after_order_review, etc.

Example: Adding a custom field to the checkout

/**
 * Add a custom field to the checkout billing details section.
 */
function custom_checkout_billing_field( $fields ) {
    // Add a custom field after the billing phone field.
    $fields['billing']['billing_custom_field'] = array(
        'label' => __( 'Your Reference', 'custom-wc-overrides' ),
        'placeholder' => _x( 'Enter a reference number', 'billing placeholder', 'custom-wc-overrides' ),
        'required' => false,
        'clear' => true,
        'type' => 'text',
        'class' => array( 'form-row-wide' ),
        'priority' => 120, // Adjust priority to position it
    );
    return $fields;
}
add_filter( 'woocommerce_checkout_fields', 'custom_checkout_billing_field' );

/**
 * Save the custom checkout field value.
 */
function save_custom_checkout_billing_field( $order_id ) {
    if ( ! empty( $_POST['billing_custom_field'] ) ) {
        update_post_meta( $order_id, '_billing_custom_field', sanitize_text_field( $_POST['billing_custom_field'] ) );
    }
}
add_action( 'woocommerce_checkout_update_order_meta', 'save_custom_checkout_billing_field' );

/**
 * Display the custom field value on the order edit screen in admin.
 */
function display_custom_checkout_billing_field_admin( $order ) {
    echo '<p><strong>' . __( 'Your Reference:', 'custom-wc-overrides' ) . '</strong>: ' . get_post_meta( $order->get_id(), '_billing_custom_field', true ) . '</p>';
}
add_action( 'woocommerce_admin_order_data_billing_address', 'display_custom_checkout_billing_field_admin', 10, 1 );

This demonstrates adding a custom field using the woocommerce_checkout_fields filter, saving its value using woocommerce_checkout_update_order_meta, and displaying it in the admin order details. These hooks provide a clean and robust way to extend checkout functionality.

Best Practices and Considerations

  • Prioritize Hooks and Filters: Whenever possible, use WooCommerce’s extensive action and filter hooks instead of directly overriding shortcodes. This is the most maintainable and future-proof approach.
  • Use Output Buffering Sparingly: While effective for simple output manipulation, output buffering can become brittle if the underlying shortcode’s HTML structure changes significantly in future WooCommerce updates.
  • Sanitize All Inputs: Always sanitize user-provided attributes and data to prevent security vulnerabilities (XSS, SQL injection, etc.).
  • Namespace Your Code: Use unique prefixes for your functions, classes, and shortcodes to avoid conflicts with other plugins or themes.
  • Internationalization: Use translation functions (__(), _e(), _x()) and load a text domain for your plugin.
  • Documentation: Clearly document your shortcodes, their attributes, and their purpose.
  • Testing: Thoroughly test your overrides on various WooCommerce versions and with different product types and configurations.

By strategically employing the Shortcode API as a wrapper and prioritizing WooCommerce’s built-in hooks and filters, you can effectively build custom extensions that override or enhance core WooCommerce functionality in a clean, maintainable, and update-safe manner.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • WordPress Development Recipe: Secure token-based API authentication for OpenAI Completion API in custom plugins
  • How to construct high-throughput import engines for large custom subscription logs sets using custom XML/JSON parsers
  • How to implement custom Filesystem API endpoints with token authentication in Gutenberg blocks
  • How to analyze and reduce CPU consumption of custom Command Query Responsibility Segregation (CQRS) event mediators
  • Step-by-Step Guide: Refactoring legacy hooks to use Active Record Wrapper pattern in theme layers

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (652)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (868)
  • PHP (5)
  • PHP Development (38)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (636)
  • SEO & Growth (492)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (319)
  • WordPress Theme Development (357)

Recent Posts

  • WordPress Development Recipe: Secure token-based API authentication for OpenAI Completion API in custom plugins
  • How to construct high-throughput import engines for large custom subscription logs sets using custom XML/JSON parsers
  • How to implement custom Filesystem API endpoints with token authentication in Gutenberg blocks

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (868)
  • Debugging & Troubleshooting (652)
  • Security & Compliance (636)
  • SEO & Growth (492)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala