WordPress AJAX tutorial – multi add product categories and terms from the front end

Multi add product terms from front end
Multi add product terms from front end

In this tutorial you will learn how to add categories and terms to multiple products from front end in WordPress. We will use AJAX, jQuery and PHP to multi add terms to products. This might be handy if you add new taxonomy to your website. It will allow you to add new terms to the existing products fast.

In below example, taxonomy will be color.
First we will create custom page template, showing only products that are missing color.
Go to your WordPress theme folder and copy/paste page.php. Rename file to your choice and enter a Template name. Later on we will assign this template to the newly created page, where colors will be added to

See below our custom WP Query for showing only products, missing pa_product_colors taxomomy.

    $pa_product_colors_cat_terms = get_terms('pa_product_colors', array('fields'=>'ids'));
	$default_posts_per_page = 200;
	$paged = ( get_query_var( 'paged' ) ) ? absint( get_query_var( 'paged' ) ) : 1;
	$args = array(
		'post_type'       => 'pa_products',
		'paged'		  => $paged, 
		'posts_per_page'  => $default_posts_per_page,
		'order'           => 'DESC',
		'tax_query' => array(
            'taxonomy' => 'pa_product_colors',
			'field'    => 'id',
			'terms'    => $pa_product_colors_cat_terms,
            'operator' => 'NOT IN', 
	$query = new WP_Query( $args );

Below is code displaying product details, within the WordPress loop. Here we show the product image, title and price. Feel free to remove or add product information.

<div id="product-wrap" class="product-wrap row col-sm-9 col-md-9 col-lg-9 row">
	if ( $query->have_posts() ) : 
		while ( $query->have_posts() ) : $query->the_post();

//product data
$product_image = get_post_meta( get_the_ID(), 'PA_products_image', true); 
$product_url = get_post_meta( get_the_ID(), 'PA_products_affiliate_url', true); 
$product_price = get_post_meta( get_the_ID(), 'PA_products_price', true); 
 <div class="product-row col-sm-3 col-md-3 col-lg-3">	
	<div class="product">
        <div class="product-info">
		<img src="<?php echo $product_image; ?>" title="<?php the_title(); ?>" alt="<?php the_title(); ?>"/>
	<input type="hidden" class="pa_product_id" name="pa_product_id"  value="<?php echo get_the_ID(); ?>">		
		<div class="product-title"><a href="<?php echo get_permalink(get_the_ID()); ?>" title=""><?php the_title(); ?></a></div>	
		  <div class="price">
		 <span class="eur"> € <?php echo $product_price; ?></span>
			<div class="trigger"></div>
			<svg version="1.1" id="checkmark" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
				 viewBox="0 0 37 37" style="enable-background:new 0 0 37 37;" xml:space="preserve">
				<polyline class="checkmark path" points="11.6,20 15.9,25.2 26.4,12.8 "/>

<?php endwhile; else: ?>
	<div class="section-title mt">
		<p><?php _e('Sorry, no posts matched your criteria.','helloirena'); ?></p> 
<?php endif; ?>

Next we will display all terms from our taxonomy in the sidebar and the pagination at the bottom of the page.

	 <div class="product-color missing-terms col-sm-2 col-md-2 col-lg-2">
	 <span><?php echo esc_html__('Color'); ?></span>
		 $product_colors = get_terms('pa_product_colors', array('hide_empty' => false, 'parent' => 0 ));
		  foreach ($product_colors as $product_color) { ?>
			<div class="form-check">
				<label class="form-check-label" for="<?php echo $product_color->name . $object_id; ?>"> 
				<input class="form-check-input" type="checkbox" value="<?php echo $product_color->term_id; ?>" id="<?php echo $product_color->name; ?>"  name="<?php echo $product_color->name; ?>">
				<?php echo $product_color->name ?>
				<span class="checkmark"></span>

		 <?php }   
		//Set the nonce
		$ajax_nonce = wp_create_nonce( "PA_checked_cat_nonce_field" );

	<div class="text-center">
		<!-- pagination -->
		<nav class="navigation pagination">
		<h2 class="screen-reader-text">Pagination</h2>
		<div class="nav-links">
		$big = 999999999; 
		echo paginate_links( array(
			'screen_reader_text' => __('Post navigation', 'marmalade'), 
			'prev_text'          => '<span aria-hidden="true">&laquo;</span>',
			'next_text'          => '<span aria-hidden="true">&raquo;</span>',
			'mid_size'  => 2,
			'end_size'  => 1,
			'type'		=>'list',
			'base' => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
			'format' => '?paged=%#%',
			'current' => max( 1, get_query_var('paged') ),
			'total' => $query->max_num_pages,
		) );

After that comes the jQuery and AJAX part. On click of each product, we will draw an checkmark over it and reduce it’s opacity. If Product is checked, it will be added to the productIds[] array, and if its unchecked it will be removed from the array.

All color terms are shown as checkboxes.
On click of each checkbox we will pass below information via AJAX to the PHP function adding the terms.
– product color (term that had been checked)
– id’s of all checked products (available in the productIds array).

jQuery(document).ready(function() {

 var productIds = [];
 jQuery.each(jQuery('.product'), function(){ 

 var product = jQuery(this);
	var productInfo = product.find('.product-info');
	var svg = product.find('svg');
	var trigger =  product.find('.trigger');
var paProductId = product.find('.pa_product_id').val(); 
 product.on('click', function() {
			var index = productIds.indexOf(paProductId);
			if (index !== -1) { productIds.splice(index, 1)};
	} else {
	  if (productIds.indexOf(paProductId) == -1) { productIds.push(paProductId); }  

jQuery.each(jQuery('.product-color'), function(){ 
var terms = jQuery(this);
var checkbox = terms.find('input[type="checkbox"]');
jQuery.each(checkbox, function(){

jQuery(this).on('click', function() {

	var paColor = jQuery(this).val();
	var ajaxurl = '<?php echo admin_url( 'admin-ajax.php' ); ?>';
         type: 'POST',
		 url: ajaxurl, 
		 data: { 
		 action: 'pa_product_color',
		 security: '<?php echo $ajax_nonce; ?>',
		 PA_color: paColor,
		 product_ids: productIds,
		 success: function(data) { 


Below code comes in our functions.php file. Here we add the action from our AJAX call in jQuery function above (pa_product_color).
In pa_add_product_color function we tell WordPress to add color terms to each product id in our array of product id’s.

Below you can see, that $_POST[‘product_ids’] and $_POST[‘PA_color’] values were passed via the AJAX function to PHP, as well.
Now we will loop through all $product_ids and if a product id doesn’t have the term that had been checked, we will add it, using wp_set_object_terms() function.

add_action('wp_ajax_nopriv_pa_product_color', 'pa_add_product_color');
add_action('wp_ajax_pa_product_color', 'pa_add_product_color');

function pa_add_product_color() {
	global $wpdb; 
	if( check_ajax_referer( 'PA_checked_cat_nonce_field', 'security' ) ) {

		$post_ids = $_POST['product_ids']; 
		$term_id = $_POST['PA_color'];
		$term = array( $term_id );
		$term = array_map( 'intval', $term );
		$term = array_unique( $term );
		foreach($post_ids as $key => $post_id) {
		// colors
			if( !has_term($term, 'pa_product_colors', $post_id ) ) {
				wp_set_object_terms( $post_id, $term, 'pa_product_colors', true ); 

Below is some CSS code, styling the custom checkbox, changing opacity of each checked product and adding a checkmark on top of it. You can apply custom style to products and terms, if you wish.

    padding-left: 0;
.form-check-label {
    position: relative;
    padding-left: 29px;
    margin-bottom: 5px;
    cursor: pointer;
    font-size: 16px;
    display: inline-flex;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
.form-check-label input {
  position: absolute;
  opacity: 0;
  cursor: pointer;
  height: 0;
  width: 0;
.checkmark {
  position: absolute;
  top: 0;
  left: 0;
  height: 20px;
  width: 20px;
  background-color: #dadada;

.form-check-label input:checked ~ .checkmark {
  background-color: #000000;
.checkmark:after {
  content: "";
  position: absolute;
  display: none;

.form-check-label input:checked ~ .checkmark:after {
  display: block;
.form-check-label .checkmark:after {
    left: 8px;
    top: 4px;
    width: 5px;
    height: 10px;
    border: solid white;
    border-width: 0 2px 2px 0;
    -webkit-transform: rotate(45deg);
    -ms-transform: rotate(45deg);
    transform: rotate(45deg);
.form-check-color-label {
	font-size: 15px;
	position: absolute;
	top: 0;
	left: 0;
	height: 20px;
	width: 20px;
	background-color: #dadada;
    stroke-dasharray: 50;
    stroke-dashoffset: 50;
    -webkit-transition: stroke-dashoffset 0.8s 0s ease-out;
    -moz-transition: stroke-dashoffset 0.8s 0s ease-out;
    -ms-transition: stroke-dashoffset 0.8s 0s ease-out;
    -o-transition: stroke-dashoffset 0.8s 0s ease-out;
    transition: stroke-dashoffset 0.8s 0s ease-out;
.drawn + svg .path{
    opacity: 1;
    stroke-dashoffset: 0;
svg#checkmark {
	position: absolute !important;
    top: 0 !important;

If you wish to develop this even further, you can tell AJAX to reload page or only products on success – inside success: function(data) {}

Hope this tutorial was of value to you! If you wish to receive such tutorials, enter your name and email address below to get notified.

Where I can send tutorials from now on?

( I hate spam too, so I will never send such to you! )