WooCommerce: Limit Shipping to Only One State

Today’s snippet has been widely requested by many readers, clients and WooCommerce fans. We already saw in the past how to Limit State Dropdowns to One State Only (for both Shipping & Billing) and How to Sell to one State only (Billing).

However, we never covered a much more common setting: what happens when Billing is allowed to every state but Shipping is limited?

In order to get a little help, I’ve reached out to Diego Zanella, a WooCommerce genius who is also the author of the Aelia Currency Switcher plugin for WooCommerce.

PHP Snippet (1 of 2): Allow Shipping to Only One State @ WooCommerce Cart Shipping Calculator

/**
 * @snippet       Ship to One State @ WooCommerce Cart Shipping Calculator
 * @how-to        Watch tutorial @ https://businessbloomer.com/?p=19055
 * @author        Diego Zanella
 * @compatible    WooCommerce 3.6.4
 * @donate $9     https://businessbloomer.com/bloomer-armada/
 */
 
add_filter( 'woocommerce_states', 'aelia_cart_filter_US_states' );
 
function aelia_cart_filter_US_states( $states ) {
if ( is_cart() ) {
   $states['US'] = array(
      'PA' => 'Pennsylvania',
   );
}
return $states;
}
WooCommerce: Allow Shipping to Only One State in the Cart Shipping Calculator
WooCommerce: Allow Shipping to Only One State in the Cart Shipping Calculator

PHP Snippet (2 of 2): Allow Shipping to Only One State @ WooCommerce Checkout Page

Note: this will only work if you’ve limited shipping to a specific country via the WooCommerce settings. Also, this snippet might limit billing too – in this case check the alternative snippet below.

/**
 * @snippet       Ship to One State @ WooCommerce Checkout #1
 * @how-to        Watch tutorial @ https://businessbloomer.com/?p=19055
 * @author        Rodolfo Melogli
 * @compatible    WooCommerce 3.6.4
 * @donate $9     https://businessbloomer.com/bloomer-armada/
 */

add_filter( 'woocommerce_countries_shipping_country_states', 'bbloomer_set_checkout_shipping_state' );
  
function bbloomer_set_checkout_shipping_state( $states ) {
	$states[ 'US' ] = array( 'PA' => __( 'Pennsylvania', 'woocommerce' ) );
  	return $states;
}

Alternative snippet – in case everything else fails:

/**
 * @snippet       Ship to One State @ WooCommerce Checkout #2
 * @how-to        Watch tutorial @ https://businessbloomer.com/?p=19055
 * @author        Rodolfo Melogli
 * @compatible    WooCommerce 3.6.4
 * @donate $9     https://businessbloomer.com/bloomer-armada/
 */

add_action( 'wp_footer', 'aelia_checkout_shipping_filter_US_states' );
  
function aelia_checkout_shipping_filter_US_states() {
	if ( ! is_checkout() ) {
		return;
	}
	?>
  	
	<script>
	jQuery(document).ready(function($) {

		$(document.body).on('country_to_state_changed', function() {

			function set_shipping_state(state) {
				var $shipping_state = $('#shipping_state');
				var $shipping_state_option = $('#shipping_state option[value="' + state + '"]');
				var $shipping_state_option_no = $('#shipping_state option[value!="' + state + '"]');
				$shipping_state_option_no.remove();
				$shipping_state_option.attr('selected', true);
			}

			var $shipping_country = $('#shipping_country');

			var new_shipping_state = '';

			switch($shipping_country.val()) {
				case 'US':
					new_shipping_state = 'PA';
					break;
			}

			if( ! $.isEmptyObject(new_shipping_state)) {
				set_shipping_state(new_shipping_state);
			} 

		});

	});  
	</script>
		
	<?php
}
WooCommerce: Allow Shipping to Only One State @ Checkout
WooCommerce: Allow Shipping to Only One State @ Checkout

Where to add this snippet?

You can place PHP snippets at the bottom of your child theme functions.php file (before "?>" if you have it). CSS, on the other hand, goes in your child theme style.css file. Make sure you know what you are doing when editing such files - if you need more guidance, please take a look at my free video tutorial "Where to Place WooCommerce Customization?"

Does this snippet (still) work?

Please let me know in the comments if everything worked as expected. I would be happy to revise the snippet if you report otherwise (please provide screenshots). I have tested this code with Storefront theme, the WooCommerce version listed above and a WordPress-friendly hosting on PHP 7+.

If you think this code saved you time & money, please join other Business Bloomer supporters and avail of 365 days of WooCommerce benefits. Thank you in advance :)

Need Help with WooCommerce Customization?

Check out these free video tutorials!

  • how-to-edit-woocommerce-with-php-snippets
  • woocommerce-hooks-add_action-list-visual
  • woocommerce-customize-single-product-page-PHP

Diego Zanella

Owner at Aelia.co, I founded the business in January 2013 to fill a gap in the WooCommerce market after designing and developing, from the ground up, the Currency Switcher for WooCommerce, an innovative plugin that quickly became the leading multi-currency solution for WooCommerce.

Rodolfo Melogli

Author, WooCommerce expert and WordCamp speaker, Rodolfo has worked as a WooCommerce freelancer since 2011. His goal is to help entrepreneurs and developers overcome their WooCommerce nightmares. Rodolfo loves travelling, chasing tennis & soccer balls and, of course, wood fired oven pizza.

29 thoughts on “WooCommerce: Limit Shipping to Only One State

  1. Hey, I added this code to my client’s website back in May 29 and just recently my client noticed some orders made from the front-end do not have taxes applied. After some testing I found this code was the cause. I have since switched my client’s WooCommerce setting to calculate tax based on their store address for now since they only ship within their province.

    I want to ask has anyone encountered this particular issue with this code? And if there are other alternatives to limiting shipping checkout to one state/province? Any insight would be helpful, thank you!

    1. Hey Wallace, thanks so much for your comment! Yes, this is definitely fixable, but I’m afraid it’s custom work. If you’d like to get a quote, feel free to contact me here. Thanks a lot for your understanding!

  2. Hi, any updates on this, I could not get it to work with current woocommerce version.

    1. Hi there, just tested and it still works πŸ™‚

  3. Hi,
    First of all thank you for your help. I have found an error in your script, there is missing double quotes in state id attribute.

    1. Thank you Sandeep! Is the error here:

      <option id="' + state + ' value="' + state + '">
      
  4. I came up with a slightly different solution based on the work done here. It utilizes wp_localize_script to be able to populate the states from a PHP function. In this case, ssc_ship_states returns an array of states for multiple countries (Australia, US, Canada), so all three of those countries will have a restricted list of states/provinces.

    function ssc_checkout_shipping_filter_US_states() {
      if(!is_checkout()) {
        return;
      }
    	global $SSC_ASSET_PATH;
    	
    	wp_enqueue_script( 'checkout-states', $SSC_ASSET_PATH . 'js/checkout-states.js', array('jquery'), null, true );
    	wp_localize_script( 'checkout-states', 'state_data', array(
    		'ship_states' => ssc_ship_states()
    	));
    };
    add_action('wp_enqueue_scripts', 'ssc_checkout_shipping_filter_US_states', 100);
    

    Then I use a separate JS file that gets enqueued in the footer:

    jQuery(document).ready(function($) {
    	$(document.body).on('country_to_state_changed', function(event, args) {
    		function set_shipping_states(states) {
    			var $shipping_state = $('#shipping_state');
    			$shipping_state.find('option:not([value=""])').remove();
    			
    			for(state in states) {
    				$shipping_state.append('<option id="' + state + ' value="' + state + '">' + states[state] + '</option>');
    			}
    		}
    		
    		var $shipping_country = $('#shipping_country');
    		
    		var new_shipping_states = {};
    		var country = $shipping_country.val();
    		if (state_data.ship_states[country]) {
    			new_shipping_states = state_data.ship_states[country];
    		}
    		
    		if(!$.isEmptyObject(new_shipping_states)) {
    			set_shipping_states(new_shipping_states);
    		}
    	});
    });
    

    It should be noted that I also have removed Select2 scripts and styles from Woo because they seem pointless to me and cause more headache than they are worth:

    function bones_dequeue_stylesandscripts_select2() {
    	if ( class_exists( 'woocommerce' ) ) {
    		wp_dequeue_style( 'select2' );
    		wp_deregister_style( 'select2' );
    		wp_dequeue_style( 'selectWoo' );
    		wp_deregister_style( 'selectWoo' );
    
    		wp_dequeue_script( 'select2');
    		wp_deregister_script('select2');
    		wp_dequeue_script( 'selectWoo');
    		wp_deregister_script('selectWoo');
    	} 
    } 
    add_action( 'wp_enqueue_scripts', 'bones_dequeue_stylesandscripts_select2', 100 );
    
    1. Brilliant, thanks for sharing!

  5. Hi! I’ve copied the 2nd snippet exactly (convenient that I need the PA option!) and it’s not having any impact on my site. It’s in my child theme functions .php.

    Do you have any thoughts on this?

    1. Hey Laurie, thanks for your comment! Yes, the 2nd snippet highly depends on your theme/custom coding so there is something there not triggering. Or, https://businessbloomer.com/woocommerce-allow-shipping-one-state-only/#comment-24806

    2. Darn! Thanks for the info. I tried to go back to the first snippet, which was working and now it’s not. Do you have any advice on that? Thanks!

      1. Laurie, are you using the exact same code? If not, can you show me your custom code?

  6. Heads up to anyone trying snippet #2 to hide states on Checkout…

    It *appears* to work and correctly replaces the shipping states, but I discovered it was preventing Woo from loading our shipping methods when somebody tried to “ship to a different address,” saying “There are no shipping methods available. Please double check your address, or contact us if you need any help.” Removing the snippet fixed the issue.

    It’s happening because Woo uses Select2.js to replace the State dropdown with a bunch of custom ‘s, but the snippet here uses jQuery to put an back onto the page, breaking however Woo sends address info back to calculate shipping.

    Hopefully I’ll have some time to fix it to work with Select2, but in the meantime you should probably remove snippet #2 from here Rodolfo. Thanks for all your helpful coding though!

    1. Thank you so much for your feedback Justin πŸ™‚

    2. i found the same thing. Also, this doesn’t affect the My Account Shipping address. If there’s a solution, please post.

      thanks!

    3. It’s happening to me also. OceanWP theme, Woo 3.1.2, WP 4.8.2.
      The filter on shipping states dropdown *works* but the order cannnot be completed because the shippping methods aren’t displayed: β€œThere are no shipping methods available. Please double check your address, or contact us if you need any help.”

      If someone have a workaround for this would be awesome.

  7. Hi Rodolfo,

    Thank you for your work. If I reside in Canada and im dealing with Provinces. Would i just need to modify the code from “US” to “Canada” and “states” to “provinces and PA to ON (Ontario) ? Thank you for your help.

    1. Hey Paul, thanks for your comment! Correct – you only need to double check the exact 2-letters codes that WooCommerce uses. They’re in the /i18n/states/ folder πŸ™‚

  8. Hi,

    I realize a website for a client and he wants to limit shipping of his burgers to a perimeter of two miles so only people in this perimeter can order.

    Is it possible to do that ?

    1. Hey Romain, thanks so much for your comment! This is pretty complicated – how are you going to measure the “miles” on the checkout page? Maybe by ZIP code? You’ll need to find a rule first and then “translate” that in WooCommerce – not sure how you can manage to do so in this case πŸ™‚

  9. Make sure the states are separated by a comma. The list with all the states is included in this code. Remove the states you do not want. Last state on the list does not require a comma.

    
    
    /**
    * @Control the List of States shown in the Shipping Section of Checkout Page
    */
    
    add_action('wp_footer', 'aelia_checkout_shipping_filter_US_states');
     
    function aelia_checkout_shipping_filter_US_states() {
      if(!is_checkout()) {
        return;
      }
     ?&gt;
     
      jQuery(document).ready(function($) {
     
       $(document.body).on('country_to_state_changed', function(event, args) {
        function set_shipping_states(states) {
         var $shipping_state = $('#shipping_state');
         $shipping_state.find('option:not([value=""])').remove();
     
         for(state in states) {
          $shipping_state.append('' + states[state] + '');
         }
        }
     
        var $shipping_country = $('#shipping_country');
     
        var new_shipping_states = {};
        switch($shipping_country.val()) {
         case 'US':
          new_shipping_states = { 
            'AL': 'Alabama',
    	'AK': 'Alaska', 
    	'AZ': 'Arizona', 
    	'AR': 'Arkansas', 
    	'CO': 'Colorado', 
    	'CT': 'Connecticut', 
    	'DE': 'Delaware', 
    	'DC': 'District Of Columbia', 
    	'FL': 'Florida', 
    	'GA': 'Georgia', 
    	'HI': 'Hawaii', 
    	'ID': 'Idaho', 
    	'IL': 'Illinois', 
    	'IN': 'Indiana', 
    	'IA': 'Iowa', 
    	'KS': 'Kansas', 
    	'KY': 'Kentucky', 
    	'LA': 'Louisiana', 
    	'ME': 'Maine', 
    	'MD': 'Maryland', 
    	'MA': 'Massachusetts', 
    	'MI': 'Michigan', 
    	'MN': 'Minnesota', 
    	'MS': 'Mississippi', 
    	'MO': 'Missouri', 
    	'MT': 'Montana', 
    	'NE': 'Nebraska', 
    	'NV': 'Nevada', 
    	'NH': 'New Hampshire', 
    	'NJ': 'New Jersey', 
    	'NM': 'New Mexico', 
    	'NY': 'New York', 
    	'NC': 'North Carolina', 
    	'ND': 'North Dakota', 
    	'OH': 'Ohio', 
    	'OK': 'Oklahoma', 
    	'PA': 'Pennsylvania', 
    	'RI': 'Rhode Island', 
    	'SC': 'South Carolina', 
    	'SD': 'South Dakota', 
    	'TN': 'Tennessee', 
    	'TX': 'Texas', 
    	'UT': 'Utah', 
    	'VT': 'Vermont', 
    	'VA': 'Virginia', 
    	'WV': 'West Virginia', 
    	'WI': 'Wisconsin', 
    	'WY': 'Wyoming',
    	'AA': 'Armed Forces (AA)', 
    	'AE': 'Armed Forces (AE)', 
    	'AP': 'Armed Forces (AP)'
          }
          break;
        }
     
        if(!$.isEmptyObject(new_shipping_states)) {
         set_shipping_states(new_shipping_states);
        }
       });
      });
     
     &lt;?php
    };
    
    
  10. Hi,

    Thanks for the code!
    If i add 2 states, seems not to work.
    Do i need to change something else?

    1. Hey Dan, thanks for your comment! Would you be able to paste your snippet here, as it should definitely work πŸ™‚ Let me know!

      1. Thanks for your replay.
        Here it is:

        
        /**
        * @snippet Allow Shipping to Only One State @ WooCommerce Cart
        * @how-to Watch tutorial @ https://businessbloomer.com/?p=19055
        * @sourcecode https://businessbloomer.com/?p=20462
        * @author Diego Zanella
        * @testedwith WooCommerce 2.5.5
        */
         
        add_action('wp_footer', 'aelia_checkout_shipping_filter_US_states');
         
        function aelia_checkout_shipping_filter_US_states() {
          if(!is_checkout()) {
            return;
          }
         ?&gt;
         
          jQuery(document).ready(function($) {
         
           $(document.body).on('country_to_state_changed', function(event, args) {
            function set_shipping_states(states) {
             var $shipping_state = $('#shipping_state');
             $shipping_state.find('option:not([value=""])').remove();
         
             for(state in states) {
              $shipping_state.append('' + states[state] + '');
             }
            }
         
            var $shipping_country = $('#shipping_country');
         
            var new_shipping_states = {};
            switch($shipping_country.val()) {
             case 'US':
              new_shipping_states = {
               'NJ': 'New Jersey'
               'NY': 'New York'
              }
              break;
            }
         
            if(!$.isEmptyObject(new_shipping_states)) {
             set_shipping_states(new_shipping_states);
            }
           });
          });
         
         &lt;?php
        };
        
        

        If i leave only 1, it works fine!

        1. Uhm, you might be missing a comma here:

          new_shipping_states = {
                 'NJ': 'New Jersey',
                 'NY': 'New York'
                }
          

          Let me know πŸ™‚

          1. Thanks again, but did not work.
            If I add one more state, all of them appear in woocommerce.

            With one state it works perfectly.
            Its really strange.

            Any other solution?

            1. Hey πŸ™‚ That’s the correct solution so maybe you have a snippet/plugin/theme conflict. Try troubleshooting this maybe – let me know!

  11. Hi,
    I have a client who has a Flower Shop in the UK. She only wants to sell her flowers to local customers within a 10 mile radius of her shop. I was able to generate a list of all the post codes that this includes.
    However, how would I disable shipping for all customers of the UK outside of this post code list?.

    Any help would be much appreciated.

    tia,
    Andy

    1. Hey Andy, thanks for your comment! Are you on WooCommerce 2.6+ – the new shipping “zones” should help you do exactly that? Let me know

Questions? Feedback? Support? Leave your Comment Now!
_____

If you are writing code, please wrap it between: [php]code_here[/php]. Failure to complying with this (as well as going off topic) will result in comment deletion. You should expect a reply in about a week - this is a popular blog but I need to get paid work done first. Please consider joining #BloomerArmada to ask me 1-to-1 WooCommerce questions. Thank you :)

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.