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.

Shipping Rate Functions

Shipping rate functions allow your app to provide custom shipping options during checkout. Use them to integrate with third-party carriers, offer location-based pricing, or implement complex shipping logic.

How It Works

Function Manifest

Define your shipping rate function in app.json:
{
  "handle": "my-shipping-app",
  "name": "Custom Shipping",
  "version": "1.0.0",
  "functions": {
    "shipping_rate": {
      "handle": "custom-rates",
      "name": "Custom Shipping Rates",
      "config": {
        "apiEndpoint": "https://my-app.com/api/shipping/rates",
        "apiKey": "{{secrets.SHIPPING_API_KEY}}",
        "defaultCurrency": "USD"
      }
    }
  }
}

Input Schema

Your function receives cart and address data:
interface ShippingRateInput {
  cart: {
    items: CartItem[];
    subtotal: number;
    totalWeight: number;
    currency: string;
  };
  shippingAddress: {
    firstName: string;
    lastName: string;
    address1: string;
    address2: string;
    city: string;
    province: string;
    provinceCode: string;
    country: string;
    countryCode: string;
    zip: string;
    phone: string;
  };
  customer: {
    id: string;
    email: string;
    tags: string[];
  } | null;
  shop: {
    id: string;
    name: string;
    currency: string;
    weightUnit: 'kg' | 'lb';
  };
}

interface CartItem {
  id: string;
  variantId: string;
  productId: string;
  title: string;
  quantity: number;
  price: number;
  weight: number;
  requiresShipping: boolean;
  sku: string;
  properties: Record<string, string>;
}

Output Schema

Return an array of shipping rates:
interface ShippingRateOutput {
  rates: ShippingRate[];
}

interface ShippingRate {
  // Required fields
  name: string;           // Display name (e.g., "Express Shipping")
  price: number;          // Price in cents (e.g., 999 = $9.99)
  
  // Optional fields
  code?: string;          // Internal identifier
  description?: string;   // Shown below the rate name
  deliveryRange?: {       // Estimated delivery window
    min: number;          // Minimum days
    max: number;          // Maximum days
  };
  carrierIdentifier?: string;  // Carrier name for tracking
  phoneRequired?: boolean;     // Require phone for this rate
}

Example Implementation

Basic Example

// Function config in app.json
{
  "functions": {
    "shipping_rate": {
      "handle": "tiered-shipping",
      "config": {
        "freeShippingThreshold": 5000,  // $50.00
        "standardRate": 599,             // $5.99
        "expressRate": 1299              // $12.99
      }
    }
  }
}

// Function logic (executed by backend)
function calculateShippingRates(input, config) {
  const { cart, shippingAddress } = input;
  const rates = [];
  
  // Check if all items ship free
  const allFreeShipping = cart.items.every(
    item => item.properties?.free_shipping === 'true'
  );
  
  if (allFreeShipping || cart.subtotal >= config.freeShippingThreshold) {
    rates.push({
      name: 'Free Shipping',
      price: 0,
      description: '5-7 business days',
      deliveryRange: { min: 5, max: 7 }
    });
  } else {
    rates.push({
      name: 'Standard Shipping',
      price: config.standardRate,
      description: '5-7 business days',
      deliveryRange: { min: 5, max: 7 }
    });
  }
  
  // Express shipping (not available for some locations)
  const expressExcluded = ['HI', 'AK', 'PR'];
  if (!expressExcluded.includes(shippingAddress.provinceCode)) {
    rates.push({
      name: 'Express Shipping',
      price: config.expressRate,
      description: '2-3 business days',
      deliveryRange: { min: 2, max: 3 },
      carrierIdentifier: 'UPS'
    });
  }
  
  return { rates };
}

Weight-Based Shipping

function calculateWeightBasedRates(input, config) {
  const { cart, shippingAddress } = input;
  const totalWeight = cart.totalWeight; // in shop's weight unit
  
  // Weight tiers (weight in kg)
  const tiers = [
    { maxWeight: 1, standard: 499, express: 999 },
    { maxWeight: 5, standard: 799, express: 1499 },
    { maxWeight: 10, standard: 1299, express: 2499 },
    { maxWeight: 25, standard: 1999, express: 3999 },
    { maxWeight: Infinity, standard: 2999, express: 5999 }
  ];
  
  const tier = tiers.find(t => totalWeight <= t.maxWeight);
  
  return {
    rates: [
      {
        name: 'Standard Shipping',
        price: tier.standard,
        description: `For orders up to ${tier.maxWeight}kg`,
        deliveryRange: { min: 5, max: 7 }
      },
      {
        name: 'Express Shipping',
        price: tier.express,
        description: `For orders up to ${tier.maxWeight}kg`,
        deliveryRange: { min: 2, max: 3 }
      }
    ]
  };
}

Zone-Based Shipping

function calculateZoneBasedRates(input, config) {
  const { shippingAddress, cart } = input;
  
  // Define shipping zones
  const zones = {
    domestic: {
      countries: ['US'],
      rates: { standard: 599, express: 1299, overnight: 2499 }
    },
    canada: {
      countries: ['CA'],
      rates: { standard: 1299, express: 2499 }
    },
    europe: {
      countries: ['GB', 'DE', 'FR', 'IT', 'ES', 'NL', 'BE', 'AT', 'CH'],
      rates: { standard: 1999, express: 3999 }
    },
    international: {
      countries: [],
      rates: { standard: 2999 }
    }
  };
  
  // Find matching zone
  let zone = zones.international;
  for (const [name, z] of Object.entries(zones)) {
    if (z.countries.includes(shippingAddress.countryCode)) {
      zone = z;
      break;
    }
  }
  
  const rates = [];
  
  if (zone.rates.standard) {
    rates.push({
      name: 'Standard International',
      price: zone.rates.standard,
      deliveryRange: { min: 7, max: 14 }
    });
  }
  
  if (zone.rates.express) {
    rates.push({
      name: 'Express International',
      price: zone.rates.express,
      deliveryRange: { min: 3, max: 5 }
    });
  }
  
  if (zone.rates.overnight) {
    rates.push({
      name: 'Overnight',
      price: zone.rates.overnight,
      deliveryRange: { min: 1, max: 1 }
    });
  }
  
  return { rates };
}

Third-Party Carrier Integration

async function fetchCarrierRates(input, config) {
  const { cart, shippingAddress, shop } = input;
  
  // Call your carrier API
  const response = await fetch(config.apiEndpoint, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${config.apiKey}`
    },
    body: JSON.stringify({
      origin: shop.address,
      destination: {
        city: shippingAddress.city,
        state: shippingAddress.provinceCode,
        zip: shippingAddress.zip,
        country: shippingAddress.countryCode
      },
      packages: cart.items.map(item => ({
        weight: item.weight * item.quantity,
        quantity: item.quantity
      }))
    })
  });
  
  const carrierRates = await response.json();
  
  // Transform carrier response to LaunchMyStore format
  return {
    rates: carrierRates.map(rate => ({
      name: rate.serviceName,
      price: Math.round(rate.totalPrice * 100), // Convert to cents
      code: rate.serviceCode,
      description: rate.deliveryEstimate,
      deliveryRange: {
        min: rate.transitDays.min,
        max: rate.transitDays.max
      },
      carrierIdentifier: rate.carrier
    }))
  };
}

Combining with Store Rates

Your shipping rates appear alongside the merchant’s configured rates:
Shipping Options
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

○ Standard Shipping - $5.99                 ← Store rate
  5-7 business days
  
○ Express Shipping - $12.99                 ← Store rate
  2-3 business days

──────────────────────────────────────────
Powered by My Shipping App
──────────────────────────────────────────

○ Same Day Delivery - $19.99               ← Your function
  Today by 8 PM
  
○ Pickup at Store - FREE                   ← Your function
  Ready in 2 hours

Error Handling

If your function fails, return an error:
function calculateShippingRates(input, config) {
  const { shippingAddress } = input;
  
  // Check if we can ship to this address
  const unsupportedCountries = ['CU', 'IR', 'KP', 'SY'];
  
  if (unsupportedCountries.includes(shippingAddress.countryCode)) {
    return {
      rates: [],
      error: {
        code: 'UNSUPPORTED_DESTINATION',
        message: 'We cannot ship to this destination'
      }
    };
  }
  
  // ... calculate rates
}
If your function returns an error or times out, your app’s shipping rates will not appear. Store-configured rates will still be shown.

Testing

Use the function sandbox to test with various scenarios:
{
  "cart": {
    "items": [
      { "title": "T-Shirt", "quantity": 2, "price": 2499, "weight": 0.3 }
    ],
    "subtotal": 4998,
    "totalWeight": 0.6
  },
  "shippingAddress": {
    "city": "New York",
    "provinceCode": "NY",
    "countryCode": "US",
    "zip": "10001"
  }
}

Best Practices

Carrier APIs can be slow. Cache rates for identical requests to improve checkout speed.
If your carrier API fails, return sensible fallback rates rather than nothing.
Always return prices in cents. Round consistently (e.g., always round up for shipping).
Customers want to know when their order will arrive. Always include deliveryRange.
Test with heavy items, multiple items, PO boxes, military addresses, and international destinations.
Offering free shipping above a threshold can increase average order value.

Rate Limits

Shipping rate functions must complete within:
  • Timeout: 5 seconds
  • Memory: 128 MB
Functions that exceed limits will fail, and store-configured rates will be used instead.

See Also