Free shipping on initial orders

If you are on Shopify Plus, you can use Shopify Scripts to give customers free shipping when they purchase a subscription from your store. This will apply to the initial order when a customer checks out with a subscription on your Shopify site. You can also set up free shipping for recurring orders from the Promotions page within Ordergroove. Note that free shipping set up in Ordergroove only applies to recurring subscription orders, and not the initial order when a customer purchases a subscription. See this article for more information about subscription shipping and delivery for Shopify.


Set up free shipping

Follow these steps to set up the script for free shipping:

  1. Open the Script Editor in your Shopify admin. If you do not have the Script Editor, you can add it to your store here.
  2. Click "Create script" on the top right to create a new script.
  3. In the popup, select the Shipping rates tab and choose "Blank template", and click "Create script".
  1. Give the script a descriptive title.
  1. Select Code to open up the script code editor.
  1. Paste the following sample code into the code editor. Please note that you may need to make changes to this script so that it works properly on your site.
# SCRIPT BASED ON # https://help.shopify.com/en/manual/checkout-settings/script-editor/examples/shipping-scripts # ================================ Customizable Settings ================================ # ================================================================ # Hide Rate(s) for Product/Country # # If the cart contains any matching items, and we have a matching # country, the entered rate(s) are hidden. # # - 'product_selector_match_type' determines whether we look for # products that do or don't match the entered selectors. Can # be: # - ':include' to check if the product does match # - ':exclude' to make sure the product doesn't match # - 'product_selector_type' determines how eligible products # will be identified. Can be either: # - ':tag' to find products by tag # - ':type' to find products by type # - ':vendor' to find products by vendor # - ':product_id' to find products by ID # - ':variant_id' to find products by variant ID # - ':subscription' to find subscription products # - 'product_selectors' is a list of tags or IDs to identify # associated products # - 'country_code_match_type' determines whether we look for # countries that do, or don't, match the entered options, or # all countries. Can be: # - ':include' to look for countries that DO match # - ':exclude' to look for countries that DO NOT match # - ':all' to look for all countries # - 'country_codes' is a list of country code abbreviations # - ie. United States would be `US` # - 'rate_match_type' determines whether the below strings # should be an exact or partial match. Can be: # - ':exact' for an exact match # - ':partial' for a partial match # - ':all' for all rates # - 'rate_names' is a list of strings to identify rates # - if using ':all' above, this can be set to 'nil' # ================================================================ HIDE_RATES_FOR_PRODUCT_AND_COUNTRY_PLUS_FREE_SHIPPING = [ { product_selector_match_type: :include, product_selector_type: :subscription, product_selectors: [], country_code_match_type: :all, country_codes: [], rate_match_type: :exact, rate_names: [""], discount_type: :percent, discount_amount: 100, discount_message: "Free Subscription Shipping" }, ] # ================================ Script Code (do not edit) ================================ # ================================================================ # ProductSelector # # Finds matching products by the entered criteria. # ================================================================ class ProductSelector def initialize(match_type, selector_type, selectors) @match_type = match_type @comparator = match_type == :include ? 'any?' : 'none?' @selector_type = selector_type @selectors = selectors end def match?(line_item) if self.respond_to?(@selector_type) self.send(@selector_type, line_item) else raise RuntimeError.new('Invalid product selector type') end end def tag(line_item) product_tags = line_item.variant.product.tags.map { |tag| tag.downcase.strip } @selectors = @selectors.map { |selector| selector.downcase.strip } (@selectors & product_tags).send(@comparator) end def type(line_item) @selectors = @selectors.map { |selector| selector.downcase.strip } (@match_type == :include) == @selectors.include?(line_item.variant.product.product_type.downcase.strip) end def vendor(line_item) @selectors = @selectors.map { |selector| selector.downcase.strip } (@match_type == :include) == @selectors.include?(line_item.variant.product.vendor.downcase.strip) end def product_id(line_item) (@match_type == :include) == @selectors.include?(line_item.variant.product.id) end def variant_id(line_item) (@match_type == :include) == @selectors.include?(line_item.variant.id) end def subscription(line_item) !line_item.selling_plan_id.nil? end end # ================================================================ # CountrySelector # # Finds whether the supplied country code matches the entered # strings. # ================================================================ class CountrySelector def initialize(match_type, countries) @match_type = match_type @countries = countries.map { |country| country.upcase.strip } end def match?(country_code) if @match_type == :all true else (@match_type == :include) == @countries.any? { |country| country_code.upcase.strip == country } end end end # ================================================================ # RateNameSelector # # Finds whether the supplied rate name matches any of the entered # names. # ================================================================ class RateNameSelector def initialize(match_type, rate_names) @match_type = match_type @comparator = match_type == :exact ? '==' : 'include?' @rate_names = rate_names&.map { |rate_name| rate_name.downcase.strip } end def match?(shipping_rate) if @match_type == :all true else @rate_names.any? { |name| shipping_rate.name.downcase.send(@comparator, name) } end end end # ================================================================ # HideRatesForProductCountryCampaign # # If the cart contains any matching items, and we have a matching # country, the entered rate(s) are hidden. # ================================================================ class HideRatesForProductCountryCampaign def initialize(campaigns) @campaigns = campaigns end def run(cart, shipping_rates) address = cart.shipping_address return if address.nil? @campaigns.each do |campaign| product_selector = ProductSelector.new( campaign[:product_selector_match_type], campaign[:product_selector_type], campaign[:product_selectors], ) country_selector = CountrySelector.new(campaign[:country_code_match_type], campaign[:country_codes]) product_match = cart.line_items.any? { |line_item| product_selector.match?(line_item) } country_match = country_selector.match?(address.country_code) next unless product_match && country_match rate_name_selector = RateNameSelector.new( campaign[:rate_match_type], campaign[:rate_names], ) shipping_rates.delete_if do |shipping_rate| rate_name_selector.match?(shipping_rate) end discount_applicator = DiscountApplicator.new( campaign[:discount_type], campaign[:discount_amount], campaign[:discount_message], ) shipping_rates.each do |shipping_rate| discount_applicator.apply(shipping_rate) end end end end class DiscountApplicator def initialize(discount_type, discount_amount, discount_message) @discount_type = discount_type @discount_message = discount_message @discount_amount = if discount_type == :percent discount_amount * 0.01 else Money.new(cents: 100) * discount_amount end end def apply(shipping_rate) rate_discount = if @discount_type == :percent shipping_rate.price * @discount_amount else @discount_amount end shipping_rate.apply_discount(rate_discount, message: @discount_message) end end CAMPAIGNS = [ HideRatesForProductCountryCampaign.new(HIDE_RATES_FOR_PRODUCT_AND_COUNTRY_PLUS_FREE_SHIPPING), ] CAMPAIGNS.each do |campaign| campaign.run(Input.cart, Input.shipping_rates) end Output.shipping_rates = Input.shipping_rates
  1. Click "Save and Publish" on the top right to save the script.

With this script in place, you'll see free shipping applied at checkout when there is a subscription item in the cart.


Did this page help you?