How to Migrate Shipping Script for PO Box & Return Logic to Shopify Functions?

Topic summary

A developer is migrating a deprecated Shopify Script to a Shopify Function (Delivery Customization) that controls shipping rate visibility based on two conditions:

Core Logic Requirements:

  1. PO Box Detection - When shipping addresses contain PO Box-related terms (“P.O. Box”, “Postal Box”, etc.), only specific carriers like USPS should display; all other rates are hidden

  2. Return/Exchange Filtering - When cart items lack specific properties (exchange_credit, narvar_exchange_credit), rates labeled “return shipping” are removed

Technical Challenges:

  • Checking address1/address2 fields for PO Box triggers within Delivery Customization Functions
  • Implementing string matching (partial/exact) for rate name filtering
  • Detecting line item properties in function input to conditionally filter rates
  • Translating Ruby-based Script logic to JavaScript (Wasm) or Rust

Suggested Approach:

Use regex on address fields for PO Box detection, leverage custom cart attributes or line item properties for return logic, and consider Shopify’s Functions API with edge functions for added flexibility.

The developer seeks code examples, best practices for rate filtering, and community templates demonstrating similar conditional shipping logic in the new Functions framework.

Summarized with AI on October 26. AI used: claude-sonnet-4-5-20250929.

Hi Shopify Community,

I’m currently migrating this custom Shopify Script to a Shopify Function as well, since Shopify Scripts are being deprecated. This script controls which shipping rates are displayed to customers based on specific conditions.

  1. Whether the shipping address is a PO Box
  2. Whether the order is a return/exchange (based on line item properties)

This script is essential to our shipping experience, and I’d like to help replicate this logic using Shopify Functions (likely through a Delivery Customisation Function).

:white_check_mark: What the Script Does:

:small_blue_diamond: 1. PO Box Address Handling

  • If the shipping address contains PO Box-related terms (e.g. “P.O. Box”, “Postal Box”), only specific shipping rates (e.g. USPS) are shown.
  • All other shipping rates are hidden.

:small_blue_diamond: 2. Return Shipping Logic

  • If none of the cart items include a custom property like exchange_credit, the script removes any shipping rates labeled “return shipping”.

:red_question_mark:What I Need Help With:

I want to recreate the exact logic in Shopify Functions, but I’m not sure how to:

  • Check if address1 or address2 contains PO Box triggers in a Delivery Customization Function
  • Show/hide shipping rates based on string matching (partial or exact) of the rate name
  • Detect line item properties (e.g. exchange_credit) in the input and use them to filter rates
  • Implement the conditional logic that only shows USPS if PO Box is found
# ================= Customizable Settings =================
SHOW_RATES_FOR_ZIP_PROVINCE_COUNTRY = [
  {
    po_box_triggers: [
      "po box", "post office", "p o box", "p.o.box", "p.o. box", "p.o box", "pobox",
      "post office box", "post box", "p. o. box", "po. box", "postal box", "postal code", "PO Box",
    ],
    rate_match_type: :exact,
    rate_names: ["USPS *(ONLY OPTION FOR P.O. BOX/APO/FPO/DPO)"],
  },
]

# ========== Helper Classes ==========

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

class AddressSelector
  def initialize(triggers)
    @triggers = triggers.map { |trigger| trigger.downcase.strip }
  end

  def match?(address)
    address_fields = [address.address1, address.address2].map do |line|
      line.nil? ? "" : line.downcase
    end

    address_fields = address_fields.join(" ")
    @triggers.any? { |trigger| address_fields.include?(trigger) }
  end
end

class ShowRatesForZipProvinceCountryCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart, shipping_rates)
    address = cart.shipping_address

    @campaigns.each do |campaign|
      rate_name_selector = RateNameSelector.new(campaign[:rate_match_type], campaign[:rate_names])

      if address.nil?
        full_match = false
      else
        country_match = AddressSelector.new(campaign[:po_box_triggers]).match?(address)
        full_match = country_match
      end

      shipping_rates.delete_if do |shipping_rate|
        rate_name_selector.match?(shipping_rate) != full_match
      end
    end
  end
end

# ========== Run Campaigns ==========
CAMPAIGNS = [
  ShowRatesForZipProvinceCountryCampaign.new(SHOW_RATES_FOR_ZIP_PROVINCE_COUNTRY),
]

CAMPAIGNS.each do |campaign|
  campaign.run(Input.cart, Input.shipping_rates)
end

# ========== Narvar Return Shipping Filter ==========
is_return = Input.cart.line_items.map { |x|
  x.properties.keys.include?('exchange_credit') || x.properties.keys.include?('_exchange_credit') ||
    x.properties.keys.include?('narvar_exchange_credit') || x.properties.keys.include?('_narvar_exchange_credit')
}.any?

if !is_return
  Input.shipping_rates.delete_if do |shipping_rate|
    shipping_rate.name.downcase.include?('return shipping')
  end
end

# ========== Output ==========
Output.shipping_rates = Input.shipping_rates

:repeat_button: Would Appreciate Guidance On:

  • Mapping these classes and matchers into JavaScript (Wasm) or Rust as a Shopify Function
  • Best practices for rate filtering and property checking in the new framework
  • Any sample templates or community examples doing similar filtering logic

Thanks in advance for your help! I’d be happy to share a working Function example once it’s built, in case it helps others too.

Shopify Delivery Customization Function if the PO Box can be detected using regex on the address field used in rate filtering. Use a custom cart attribute or line item property for Return logic to pass in, based on that filter rates. For additional flexibility, you may also use Shopify’s Functions API with an edge function.