How to filter products by shipping class in WooCommerce

Filtering by shipping class in WooCommerce is actually rather simple, since WooCommerce is designed to automatically pick it up from the request query string
product_shipping_class parameter.

So all that’ left for us to do is to add, in the product listing filter area, a dropdown element to show the available shipping classes and give it the name we previously mentioned. Additionally, we also need to mark the one that’s currently selected.

To achieve this, we must use the woocommerce_product_filters action filter, like so:

function s02_add_shipping_class_filter($filtersHtml) {
	$options = s02_get_available_shipping_classes_options();
	$selected = s02_get_selected_shipping_class_filter_option();

	$filtersHtml .= s02_render_shipping_class_filter_dropdown($options, $selected);

	return $filtersHtml;
}

//Prepare an array of available shipping classes.
// Keys are slugs, values are names.
function s02_get_available_shipping_classes_options() {
	$options = array();
	$sCasses = WC_Shipping::instance()->get_shipping_classes();

	foreach ($sCasses as $sc) {
		/** @var \WP_Term $sc */
		$options[$sc->slug] = $sc->name;
	}

	return $options;
}

//Retrieve the currently selected option from HTTP GET
function s02_get_selected_shipping_class_filter_option() {
	return !empty($_GET['product_shipping_class'])
		? sanitize_text_field($_GET['product_shipping_class'])
		: '';
}

//Render the actual filter element
function s02_render_shipping_class_filter_dropdown(array $options, $selectedKey) {
	$html = '<select name="product_shipping_class" id="s02_product_shipping_class_filter">';
	
	$html .= '<option value="">Filter by shipping class</option>';
	foreach ($options as $value => $name) {
		$html .= '<option value="' 
				. esc_attr($value) . '" ' 
				. ( $value === $selectedKey ? 'selected="selected"' : '' ) . '>' 
				. esc_html($name) . '</option>';
	}

	$html .= '</select>';
	return $html;
}

add_filter('woocommerce_product_filters', 
	's02_add_shipping_class_filter', 
	20, 
	1);