Hide courier delivery options for PO box addresses

A Shopify delivery customization Function that hides UPS, FedEx and other courier options when the shipping address is a PO box. Tested JavaScript.

Function API
Delivery Customization
Target
cart.delivery-options.transform.run
Plan
Any plan in a public app; Shopify Plus in a custom app
App scope
write_delivery_customizations
API version
2026-10
Last checked

In short

This delivery customization Function hides courier options such as UPS, FedEx and DHL when the shipping address is a PO box, and keeps postal options available. Courier names come from a JSON metafield. It uses the cart.delivery-options.transform.run target of the Delivery Customization API, version 2026-10. Shopify notes the address may not be available for its built-in shipping rates.

How to use it

  1. Create an app with the Shopify CLI, then add the extension: shopify app generate extension --template delivery_customization, with JavaScript.
  2. Replace the generated shopify.extension.toml and the two files in src/ with the files below.
  3. Run the fixtures with shopify app function run --input tests/po-box-address.input.json.
  4. Deploy with shopify app deploy and create the customization with deliveryCustomizationCreate.
  5. Optionally list your couriers in the $app:function-configuration metafield, for example {"optionTitles": ["UPS", "FedEx"]}.

Code

shopify.extension.toml
api_version = "2026-10"

[[extensions]]
name = "Hide delivery options for PO boxes"
handle = "hide-delivery-po-boxes"
type = "function"

  [[extensions.targeting]]
  target = "cart.delivery-options.transform.run"
  input_query = "src/cart_delivery_options_transform_run.graphql"
  export = "cart-delivery-options-transform-run"

  [extensions.build]
  command = ""
  path = "dist/function.wasm"
src/cart_delivery_options_transform_run.graphql
query CartDeliveryOptionsTransformRunInput {
  cart {
    deliveryGroups {
      deliveryAddress {
        address1
        address2
      }
      deliveryOptions {
        handle
        title
      }
    }
  }
  deliveryCustomization {
    metafield(namespace: "$app", key: "function-configuration") {
      jsonValue
    }
  }
}
src/cart_delivery_options_transform_run.js
// @ts-check

/**
 * @typedef {import("../generated/api").CartDeliveryOptionsTransformRunInput} CartDeliveryOptionsTransformRunInput
 * @typedef {import("../generated/api").CartDeliveryOptionsTransformRunResult} CartDeliveryOptionsTransformRunResult
 */

/** Settings stored as JSON in the customization's metafield. */
const DEFAULT_CONFIG = {
  // Couriers that don't deliver to PO boxes, matched case-insensitively anywhere in the option title.
  optionTitles: ["UPS", "FedEx", "DHL", "Express", "Courier"],
};

/**
 * "PO box" spellings anywhere in the line, after a space or punctuation:
 * "P.O. Box", "PO Box", "Post Office Box", "Postfach", Turkish "Posta Kutusu"
 * and "PK" followed by a number ("Kadıköy PK 34"). The leading boundary keeps
 * words such as "Spk" or "Park" from matching.
 */
const PO_BOX =
  /(?:^|[^\p{L}\p{N}])(?:p\.?\s*o\.?\s*box|post\s*office\s*box|post\s*box|postfach|posta\s*kutusu|p\.?\s*k\.?\s*\d)/iu;

/** @param {string | null | undefined} line */
const isPoBox = (line) => PO_BOX.test(line ?? "");

/**
 * @param {CartDeliveryOptionsTransformRunInput} input
 * @returns {CartDeliveryOptionsTransformRunResult}
 */
export function cartDeliveryOptionsTransformRun(input) {
  const config = { ...DEFAULT_CONFIG, ...input.deliveryCustomization?.metafield?.jsonValue };
  const titles = config.optionTitles.map((title) => title.toLowerCase());

  const operations = input.cart.deliveryGroups
    .filter((group) => isPoBox(group.deliveryAddress?.address1) || isPoBox(group.deliveryAddress?.address2))
    .flatMap((group) => group.deliveryOptions)
    .filter((option) => titles.some((title) => (option.title ?? "").toLowerCase().includes(title)))
    .map((option) => ({ deliveryOptionHide: { deliveryOptionHandle: option.handle } }));

  return { operations };
}
Test input and expected output
tests/configured-couriers.input.json
{
  "cart": {
    "deliveryGroups": [
      {
        "deliveryAddress": { "address1": "Atatürk Cad. No: 5", "address2": "PK 112" },
        "deliveryOptions": [
          { "handle": "77ff-ptt", "title": "PTT Kargo" },
          { "handle": "77ff-yurtici", "title": "Yurtiçi Kargo" }
        ]
      }
    ]
  },
  "deliveryCustomization": { "metafield": { "jsonValue": { "optionTitles": ["Yurtiçi"] } } }
}
tests/configured-couriers.output.json
{ "operations": [ { "deliveryOptionHide": { "deliveryOptionHandle": "77ff-yurtici" } } ] }
tests/po-box-address.input.json
{
  "cart": {
    "deliveryGroups": [
      { "deliveryAddress": { "address1": "P.O. Box 4521", "address2": null }, "deliveryOptions": [
          { "handle": "c0de-usps", "title": "USPS Priority Mail" },
          { "handle": "c0de-ups", "title": "UPS Ground" },
          { "handle": "c0de-fedex", "title": "FedEx 2Day" }
        ] }
    ]
  },
  "deliveryCustomization": { "metafield": null }
}
tests/po-box-address.output.json
{
  "operations": [
    { "deliveryOptionHide": { "deliveryOptionHandle": "c0de-ups" } },
    { "deliveryOptionHide": { "deliveryOptionHandle": "c0de-fedex" } }
  ]
}
tests/po-box-after-name.input.json
{
  "cart": {
    "deliveryGroups": [
      {
        "deliveryAddress": {
          "address1": "John Smith PO Box 5",
          "address2": null
        },
        "deliveryOptions": [
          {
            "handle": "c0de-usps",
            "title": "USPS Priority Mail"
          },
          {
            "handle": "c0de-ups",
            "title": "UPS Ground"
          },
          {
            "handle": "c0de-fedex",
            "title": "FedEx 2Day"
          }
        ]
      }
    ]
  },
  "deliveryCustomization": {
    "metafield": null
  }
}
tests/po-box-after-name.output.json
{
  "operations": [
    { "deliveryOptionHide": { "deliveryOptionHandle": "c0de-ups" } },
    { "deliveryOptionHide": { "deliveryOptionHandle": "c0de-fedex" } }
  ]
}
tests/street-address.input.json
{
  "cart": {
    "deliveryGroups": [
      { "deliveryAddress": { "address1": "120 Boxwood Lane", "address2": "Apt 4" }, "deliveryOptions": [
          { "handle": "c0de-usps", "title": "USPS Priority Mail" },
          { "handle": "c0de-ups", "title": "UPS Ground" },
          { "handle": "c0de-fedex", "title": "FedEx 2Day" }
        ] }
    ]
  },
  "deliveryCustomization": { "metafield": null }
}
tests/street-address.output.json
{ "operations": [] }

Written by Lintel. MIT license: free to use in your own store and in client work.

Limits and edge cases

  • Shopify’s reference warns that hiding options for PO boxes is only supported for rates from carrier services or shipping apps; with built-in rates the address data might not be available.
  • PO box detection is a text check (“PO Box”, “P.O. Box”, “Post Office Box”, “Postfach”, Turkish “Posta Kutusu”/“PK”). Unusual spellings won’t match.
  • To stop PO box orders altogether, use the validation example instead; it blocks checkout with a message.

Questions

Why hide options instead of blocking PO boxes?
Many stores can ship to PO boxes with the postal service but not with couriers. Hiding only the courier options keeps those orders possible.
Does this work with Shopify’s own shipping rates?
Not reliably. Shopify says the address needed to detect a PO box might not be available to the Function for built-in rates. It’s supported for carrier-calculated and app rates.
Can I detect PO boxes in other languages?
Yes. Extend the PO_BOX pattern in the code with the local spelling, for example “Apartado” for Spanish addresses, and add a fixture for it.
  • Written for Function API version 2026-10 and checked against Shopify’s reference on the date shown. Shopify releases a new version every quarter.
  • Custom apps that contain Functions only run on Shopify Plus stores; a public app with the same Function runs on any plan unless the page says otherwise.
  • Every example ships with test fixtures, and we run them before publishing. Run them again after you change the code: shopify app function run.
  • MIT license. Use the code in your own store or in client work.

Need this built, tested and maintained for your store? See Shopify Functions development.

Have something to build? Let’s talk it through.

Taking a limited number of projects at a time. Every brief is read by an engineer, not a salesperson.

Gencer KarakayaShopify Engineer

An engineer reads your brief, and we reply by email.