Skip to main content

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

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 instead.
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 React 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.

Manifest

{
  "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

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

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

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:
{
  "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):
SourceGroup 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