> ## Documentation Index
> Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Local Pickup Options

> Offer in-store / curbside pickup at merchant-owned locations as a delivery option

# Local Pickup Options

`local_pickup_options` functions return a list of **merchant-owned pickup
locations** (store, warehouse, kiosk) that the customer can choose
instead of having the order shipped. Use it for:

* Buy-online-pickup-in-store (BOPIS).
* Curbside pickup at a specific retail address.
* "Pick up at our warehouse" for B2B customers.

For carrier-operated parcel lockers / partner pickup points (e.g.
DHL Packstation, UPS Access Point) use
[`pickup_point_options`](/functions/pickup-point-options) instead.

<Note>
  **Manifest is honored; runtime dispatch is reserved.** You may declare
  `local_pickup_options` in your `app.json` and ship a `.wasm` today —
  LaunchMyStore stores it, lints it, and surfaces it in the developer
  portal. Wiring into the checkout shipping step lands in the
  delivery-options release; until then, returned options are not yet
  shown to customers. Build now for forward-compat — your function will
  start firing once the consumer is enabled.
</Note>

## Manifest

```json theme={null}
{
  "type": "local_pickup_options",
  "handle": "store-pickup",
  "title": "In-store pickup",
  "entrypoint": "dist/local-pickup.wasm",
  "inputFields": {
    "shippingAddress": ["countryCode", "city", "postalCode"]
  }
}
```

Per-shop cap: **5** active `local_pickup_options` apps.

WASM execution timeout: 1500ms (configurable via
`WASM_TIMEOUT_LOCAL_PICKUP_OPTIONS`).

## Input

```typescript theme={null}
interface LocalPickupOptionsFunctionInput {
  cart: {
    id?: string;
    lines: Array<{
      id: string;
      quantity: number;
      merchandise: {
        id: string;
        productId: string;
        sku?: string;
        weight?: number;
        attributes?: Record<string, string>;
      };
      cost: { subtotal: { amount: string; currencyCode: string } };
    }>;
    cost: {
      subtotal: { amount: string; currencyCode: string };
      total: { amount: string; currencyCode: string };
    };
  };
  shippingAddress?: {
    address1?: string;
    city?: string;
    province?: string;
    countryCode?: string;
    postalCode?: string;
    latitude?: number;
    longitude?: number;
  };
  currency: string; // ISO-4217 of the cart presentment currency
}
```

## Output

```typescript theme={null}
interface LocalPickupOptionsFunctionOutput {
  pickupOptions: Array<{
    id: string;             // stable id; surfaces on the order
    title: string;          // shown in checkout, e.g. "Downtown Store"
    address: {
      address1?: string;
      address2?: string;
      city?: string;
      province?: string;
      countryCode?: string;
      postalCode?: string;
      latitude?: number;
      longitude?: number;
    };
    hours?: string;         // free-text, e.g. "Mon–Fri 9–6, Sat 10–4"
    distance?: number;      // straight-line km from shippingAddress
    fee?: number;           // surcharge in cart currency (default 0)
  }>;
}
```

Return an empty `pickupOptions: []` to opt out of offering pickup for
this cart (e.g. when the cart contains items not stocked at any pickup
location).

## Example — Two retail locations, distance-sorted

```javascript theme={null}
const LOCATIONS = [
  {
    id: 'store-downtown',
    title: 'Downtown Store',
    address: {
      address1: '123 Market St',
      city: 'San Francisco',
      province: 'CA',
      countryCode: 'US',
      postalCode: '94103',
      latitude: 37.7749,
      longitude: -122.4194,
    },
    hours: 'Mon-Fri 9-6, Sat 10-4',
  },
  {
    id: 'store-mission',
    title: 'Mission Store',
    address: {
      address1: '456 Valencia St',
      city: 'San Francisco',
      province: 'CA',
      countryCode: 'US',
      postalCode: '94110',
      latitude: 37.7599,
      longitude: -122.4214,
    },
    hours: 'Daily 10-8',
  },
];

function haversine(a, b) {
  if (!a || !b) return undefined;
  const R = 6371;
  const toRad = (d) => (d * Math.PI) / 180;
  const dLat = toRad(b.latitude - a.latitude);
  const dLon = toRad(b.longitude - a.longitude);
  const h =
    Math.sin(dLat / 2) ** 2 +
    Math.cos(toRad(a.latitude)) *
      Math.cos(toRad(b.latitude)) *
      Math.sin(dLon / 2) ** 2;
  return Math.round(2 * R * Math.asin(Math.sqrt(h)));
}

export default function main(input) {
  const ship = input.shippingAddress;
  const country = ship?.countryCode;

  const pickupOptions = LOCATIONS.filter(
    (loc) => !country || loc.address.countryCode === country,
  )
    .map((loc) => ({
      ...loc,
      distance: ship ? haversine(ship, loc.address) : undefined,
      fee: 0,
    }))
    .sort((a, b) => (a.distance ?? Infinity) - (b.distance ?? Infinity));

  return { pickupOptions };
}
```

## Persistence

Selected pickup option is stored on the order at
`additionalFields.localPickupOption`:

```json theme={null}
{
  "localPickupOption": {
    "appHandle": "my-pickup-app",
    "functionHandle": "store-pickup",
    "id": "store-downtown",
    "title": "Downtown Store",
    "address": { "address1": "123 Market St", "city": "San Francisco", "..." },
    "hours": "Mon-Fri 9-6, Sat 10-4",
    "fee": 0
  }
}
```

The shipping address on the order is set to the merchant location
address so fulfillment / tax / invoicing flow downstream as if it were a
standard ship-to order.

## When customers see what

The pickup options returned here are shown as **a third grouping** in
the checkout shipping-step UI, alongside merchant zones (server-side)
and `shipping_rate` function rates (app-side):

| Source                    | Group in checkout                   |
| ------------------------- | ----------------------------------- |
| Merchant shipping zones   | "Delivery"                          |
| `shipping_rate` functions | "Delivery" (mixed in with merchant) |
| `local_pickup_options`    | "Pickup in store"                   |
| `pickup_point_options`    | "Pickup at a parcel point"          |

The customer picks one. The chosen group determines which post-purchase
emails the merchant sends (`order_ready_for_pickup` vs. `order_shipped`).

## See also

* [Pickup Point Options](/functions/pickup-point-options) — carrier
  parcel lockers / partner pickup points.
* [Shipping Rate](/functions/shipping-rate) — custom shipping rates
  alongside merchant zones.
* [Input field selection](/functions/input-fields) — trim the input
  payload to just the address fields you read.
