WooCommerce is a robust, flexible e-commerce engine – but the real magic happens when we start extending it beyond its standard capabilities.
One of my latest projects did exactly that: I built a dynamic popup window that doesn’t just display information but also communicates directly with WooCommerce, sending data back to the system in real time and influencing the order process itself.
The Goal
The goal was to replace a static “extra options” form (for example: cake box, candles, adult extras, etc.) with an interactive popup interface where users could make selections and instantly see updated prices and combinations.
Behind the scenes, JavaScript listens to user interactions and sends updates to the WooCommerce backend via AJAX.
How It Works
On the frontend, jQuery monitors each user selection.
When someone chooses a specific cake add-on (like a candle type, color, or decoration), the script:
- Collects the relevant data- attributes (such as data-price or data-type),
- Builds a JSON object from the selection,
- Sends it back to the WooCommerce backend via AJAX,
- Where a PHP function saves it as custom order meta data.
In other words, the popup isn’t just a visual element – it’s an active UI layer that communicates directly with WooCommerce.
Key Technologies
- JavaScript / jQuery – to manage dynamic elements, events, and value changes.
- AJAX ↔ PHP bridge – to send and receive data between frontend and backend.
- WooCommerce hooks (do_action, add_action) – to integrate data into the checkout and order flow.
- Custom order meta fields – to store all additional product information (flavor, size, box type, color, etc.).
The Result
The final product delivers a truly interactive ordering experience:
- The customer instantly sees updated prices and options.
- Once the popup is confirmed, all selected data is automatically transferred to the cart.
- The same data appears in the order confirmation emails and in the WooCommerce admin panel.
All of this works fully within WooCommerce’s architecture – no third-party plugins or core file modifications required.
Summary
This project demonstrates that WooCommerce is not just an e-commerce engine – it’s a powerful development framework.
By leveraging WordPress hooks, WooCommerce actions, JavaScript, and AJAX, we can build experiences that feel seamless, dynamic, and deeply integrated.
Here, the popup isn’t just decoration – it became a vital part of the data flow itself.
Code Examples
1) Minimal popup UI (HTML)
<!-- Popup modal (example) -->
<div id="cake-extras-popup" class="popup" hidden>
<h3>Choose extras</h3>
<label>Symbol</label>
<select id="jel_gyertya">
<option value="sziv">❤️ Heart</option>
<option value="X">✖️ X</option>
<option value="?">❓ Question</option>
</select>
<label>Color</label>
<select id="jel_gyertya_szin">
<option value="csillámos pink">Glitter Pink</option>
<option value="csillámos arany">Glitter Gold</option>
<option value="csillámos ezüst">Glitter Silver</option>
</select>
<label>Box</label>
<select id="tortadoboz" class="box-selector">
<option value="0" data-price="0">No box</option>
<option value="1" data-price="500">5-slice box (500 Ft)</option>
<option value="2" data-price="1000">10-slice box (1000 Ft)</option>
</select>
<button id="cake-extras-confirm">Add to order</button>
</div>
Your real popup can be any UI; what matters is we can read the selections when “Confirm” is clicked.
2) Enqueue JS and pass AJAX settings (functions.php)
add_action('wp_enqueue_scripts', function () {
wp_enqueue_script(
'cake-extras',
get_stylesheet_directory_uri() . '/js/cake-extras.js',
['jquery'],
'1.0',
true
);
wp_localize_script('cake-extras', 'CakeAjax', [
'url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('cake_extras_nonce'),
]);
});
3) Frontend JS: collect → send via AJAX → store in session
jQuery(function ($) {
// Example: read price of the selected box
function getSelectedBoxPrice() {
const price = $('#tortadoboz option:selected').data('price') || 0;
return Number(price);
}
$('#cake-extras-confirm').on('click', function () {
const payload = {
symbol: $('#jel_gyertya').val(), // sziv | X | ?
color: $('#jel_gyertya_szin').val(), // e.g. "csillámos arany"
box: $('#tortadoboz').val(), // 0/1/2...
box_price: getSelectedBoxPrice(), // 0 / 500 / 1000
};
$.post(CakeAjax.url, {
action: 'save_cake_extras',
_ajax_nonce: CakeAjax.nonce,
data: payload
}).done(function (res) {
if (res && res.success) {
alert('Extras saved for this order. Now add the product to cart!');
// Close your modal here.
} else {
alert('Could not save extras.');
}
});
});
});
4) PHP AJAX handler: save extras to session
add_action('wp_ajax_save_cake_extras', 'save_cake_extras');
add_action('wp_ajax_nopriv_save_cake_extras', 'save_cake_extras');
function save_cake_extras() {
check_ajax_referer('cake_extras_nonce');
if (empty($_POST['data']) || !class_exists('WC_Session')) {
wp_send_json_error();
}
$raw = (array) $_POST['data'];
// Sanitize
$extras = [
'symbol' => sanitize_text_field($raw['symbol'] ?? ''),
'color' => sanitize_text_field($raw['color'] ?? ''),
'box' => sanitize_text_field($raw['box'] ?? ''),
'box_price' => floatval($raw['box_price'] ?? 0),
];
if (function_exists('WC') && WC()->session) {
WC()->session->set('cake_extras', $extras);
wp_send_json_success();
}
wp_send_json_error();
}
We temporarily keep the popup data in the WooCommerce session until the product is added to the cart.
5) Inject session data into the cart item (so it travels through checkout)
// Attach extras when a product is added to the cart
add_filter('woocommerce_add_cart_item_data', function ($cart_item_data, $product_id, $variation_id) {
if (function_exists('WC') && WC()->session) {
$extras = WC()->session->get('cake_extras');
if (!empty($extras) && is_array($extras)) {
$cart_item_data['cake_extras'] = $extras;
// Optional: add the box price to the line item price
if (!empty($extras['box_price'])) {
$cart_item_data['cake_extras']['_adjust_price'] = (float) $extras['box_price'];
}
// Clear session so it doesn't leak to another add_to_cart
WC()->session->__unset('cake_extras');
}
}
return $cart_item_data;
}, 10, 3);
6) Adjust the cart line price (optional: if extras add cost)
add_action('woocommerce_before_calculate_totals', function ($cart) {
if (is_admin() && !defined('DOING_AJAX')) return;
if (empty($cart) || empty($cart->get_cart())) return;
foreach ($cart->get_cart() as $cart_item_key => $item) {
if (!empty($item['cake_extras']['_adjust_price'])) {
$extra = (float) $item['cake_extras']['_adjust_price'];
$price = (float) $item['data']->get_price('edit');
$item['data']->set_price($price + $extra);
}
}
});
7) Show extras under the product name in Cart/Checkout
add_filter('woocommerce_get_item_data', function ($item_data, $cart_item) {
if (empty($cart_item['cake_extras'])) return $item_data;
$x = $cart_item['cake_extras'];
if (!empty($x['symbol'])) {
$item_data[] = [
'name' => __('Symbol', 'your-textdomain'),
'value' => esc_html($x['symbol']),
];
}
if (!empty($x['color'])) {
$item_data[] = [
'name' => __('Color', 'your-textdomain'),
'value' => esc_html($x['color']),
];
}
if (isset($x['box']) && $x['box'] !== '') {
$item_data[] = [
'name' => __('Box', 'your-textdomain'),
'value' => esc_html($x['box']),
];
}
if (!empty($x['box_price'])) {
$item_data[] = [
'name' => __('Box price', 'your-textdomain'),
'value' => wc_price((float) $x['box_price']),
];
}
return $item_data;
}, 10, 2);
8) Persist extras into the Order Items (so they appear in admin & emails)
add_action('woocommerce_checkout_create_order_line_item', function ($item, $cart_item_key, $values, $order) {
if (empty($values['cake_extras'])) return;
$x = $values['cake_extras'];
if (!empty($x['symbol'])) {
$item->add_meta_data(__('Symbol', 'your-textdomain'), $x['symbol']);
}
if (!empty($x['color'])) {
$item->add_meta_data(__('Color', 'your-textdomain'), $x['color']);
}
if (isset($x['box']) && $x['box'] !== '') {
$item->add_meta_data(__('Box', 'your-textdomain'), $x['box']);
}
if (!empty($x['box_price'])) {
$item->add_meta_data(__('Box price', 'your-textdomain'), wc_price((float) $x['box_price']));
}
}, 10, 4);
WooCommerce will render order item meta in the admin order screen and in customer/admin emails automatically. If you need a custom layout, hook into the email templates (e.g., woocommerce_email_order_meta or override an email template).
9) (Optional) Toggle an “adults only” label based on selected extras
jQuery(function ($) {
$(document).on('change', '.step[data-step="2"] .addon[data-adult="1"] input[type="checkbox"]', function () {
let hasAdult = false;
$('.step[data-step="2"] .addon[data-adult="1"] input[type="checkbox"]').each(function () {
if ($(this).is(':checked')) hasAdult = true;
});
window.CakeOrder = window.CakeOrder || {};
CakeOrder.hasAdultExtra = hasAdult;
$('#cake-order label.adult')[hasAdult ? 'show' : 'hide']();
});
});
Why these examples matter
- Popup → AJAX → Session keeps the UI responsive while safely handing off data to WooCommerce.
- Cart item data + order item meta ensures data survives the entire checkout and lands in emails/admin.
- Price adjustment hook keeps the total correct without hacking product data.
- No core edits: everything is done via hooks and a tiny script, which is update-safe.
If you want, I can bundle these into a single paste-ready mini-plugin structure so you can drop it into /wp-content/plugins/ and activate.
Buy me a coffee?
If you enjoyed this story, you can buy me a coffee. You don’t have to – but it means a lot and I always turn it into a new adventure.
Buy a coffee for Steve

Linktree
Short introduction