# 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.

Page: https://lintelagency.com/resources/shopify-functions/hide-delivery-options-for-po-boxes

## 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.

## Facts

- **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:** 2 November 2026

## 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

```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

```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

```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

```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

```json
{ "operations": [ { "deliveryOptionHide": { "deliveryOptionHandle": "77ff-yurtici" } } ] }
```

### tests/po-box-address.input.json

```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

```json
{
  "operations": [
    { "deliveryOptionHide": { "deliveryOptionHandle": "c0de-ups" } },
    { "deliveryOptionHide": { "deliveryOptionHandle": "c0de-fedex" } }
  ]
}
```

### tests/po-box-after-name.input.json

```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

```json
{
  "operations": [
    { "deliveryOptionHide": { "deliveryOptionHandle": "c0de-ups" } },
    { "deliveryOptionHide": { "deliveryOptionHandle": "c0de-fedex" } }
  ]
}
```

### tests/street-address.input.json

```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

```json
{ "operations": [] }
```

## 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.

## Shopify documentation

- [Delivery Customization Function API](https://shopify.dev/docs/api/functions/latest/delivery-customization)

## Related

- [Activate a delivery, payment or validation Function](https://lintelagency.com/resources/graphql/register-function-customization)
- [Block PO box shipping addresses](https://lintelagency.com/resources/shopify-functions/block-po-box-addresses)
- [Delivery customization: hide, rename and sort shipping options](https://lintelagency.com/journal/shopify-delivery-customization-function)
- [Blocking a Shopify checkout that breaks your rules, with Cart and Checkout Validation](https://lintelagency.com/journal/shopify-checkout-validation)

License: MIT. Free to use in your own store and in client work.
