Skip to main content
Openfront’s shipping system provides a flexible adapter-based architecture for integrating with shipping carriers. Built-in support includes Shippo, ShipEngine, and manual shipping, with the ability to add custom providers.

Shipping Provider Architecture

Each shipping provider implements a standard set of functions:
  • getRatesFunction - Get real-time shipping rates
  • createLabelFunction - Generate shipping labels
  • cancelLabelFunction - Cancel/refund labels
  • validateAddressFunction - Validate shipping addresses
  • trackShipmentFunction - Track shipment status

Shipping Providers

Shippo Integration

Shippo provides access to multiple carriers through a single API with competitive rates.

Configuration

SHIPPO_API_TOKEN=shippo_...

Getting Shipping Rates

The Shippo adapter calculates rates from multiple carriers:
export async function getRatesFunction({ provider, order, dimensions }) {
  if (!dimensions) {
    throw new Error("Dimensions are required to get shipping rates");
  }
  
  // Create address first
  const addressToResponse = await fetch(`${SHIPPO_API_URL}/addresses/`, {
    method: "POST",
    headers: {
      Authorization: `ShippoToken ${provider.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: `${order.shippingAddress.firstName} ${order.shippingAddress.lastName}`,
      street1: order.shippingAddress.address1,
      city: order.shippingAddress.city,
      state: order.shippingAddress.province,
      zip: order.shippingAddress.postalCode,
      country: order.shippingAddress.country.iso2,
      phone: order.shippingAddress.phone,
    }),
  });

  const addressTo = await addressToResponse.json();

  // Create shipment to get rates
  const shipmentResponse = await fetch(`${SHIPPO_API_URL}/shipments/`, {
    method: "POST",
    headers: {
      Authorization: `ShippoToken ${provider.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      address_from: { /* warehouse address */ },
      address_to: addressTo.object_id,
      parcels: [{
        length: dimensions.length,
        width: dimensions.width,
        height: dimensions.height,
        distance_unit: dimensions.unit,
        weight: dimensions.weight,
        mass_unit: dimensions.weightUnit,
      }],
    }),
  });

  const shipment = await shipmentResponse.json();

  return shipment.rates.map((rate) => ({
    id: rate.object_id,
    providerId: provider.id,
    service: rate.servicelevel.name,
    carrier: rate.provider,
    price: rate.amount,
    currency: rate.currency,
    estimatedDays: rate.estimated_days,
  }));
}

Creating Shipping Labels

Generate shipping labels with tracking:
export async function createLabelFunction({
  provider,
  order,
  rateId,
  dimensions,
}) {
  // Create transaction (label) with the specific rate
  const transactionResponse = await fetch(`${SHIPPO_API_URL}/transactions/`, {
    method: "POST",
    headers: {
      Authorization: `ShippoToken ${provider.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      rate: rateId,
      label_file_type: "PDF",
      async: false,
    }),
  });

  const transaction = await transactionResponse.json();

  return {
    status: "purchased",
    data: transaction,
    carrier: transaction.provider,
    service: transaction.servicelevel?.name,
    trackingNumber: transaction.tracking_number,
    trackingUrl: transaction.tracking_url_provider,
    labelUrl: transaction.label_url,
  };
}

Address Validation

export async function validateAddressFunction({ provider, address }) {
  const response = await fetch(`${SHIPPO_API_URL}/addresses/`, {
    method: "POST",
    headers: {
      Authorization: `ShippoToken ${provider.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: `${address.firstName} ${address.lastName}`,
      street1: address.address1,
      city: address.city,
      state: address.province,
      zip: address.postalCode,
      country: address.country.iso2,
      validate: true,
    }),
  });

  const validation = await response.json();

  return {
    isValid: validation.validation_results.is_valid,
    suggestedAddress: validation.validation_results.is_valid ? {
      address1: validation.street1,
      city: validation.city,
      province: validation.state,
      postalCode: validation.zip,
    } : null,
    errors: validation.validation_results.messages || [],
  };
}

ShipEngine Integration

ShipEngine provides enterprise-grade shipping with support for major carriers.

Configuration

SHIPENGINE_API_KEY=...

Unit Conversion

ShipEngine requires specific unit formats:
const WEIGHT_UNIT_MAP = {
  oz: "ounce",
  lb: "pound",
  kg: "kilogram",
  g: "gram",
};

const DIMENSION_UNIT_MAP = {
  in: "inch",
  cm: "centimeter",
};

function convertDimensions(dim) {
  if (dim.unit === "m") {
    return {
      length: dim.length * 100,
      width: dim.width * 100,
      height: dim.height * 100,
      unit: "centimeter",
    };
  } else if (dim.unit === "ft") {
    return {
      length: dim.length * 12,
      width: dim.width * 12,
      height: dim.height * 12,
      unit: "inch",
    };
  }
  return {
    length: dim.length,
    width: dim.width,
    height: dim.height,
    unit: DIMENSION_UNIT_MAP[dim.unit] || dim.unit,
  };
}

Getting Rates

export async function getRatesFunction({ provider, order, dimensions }) {
  // List available carriers
  const carriers = await listCarriersFunction(provider);
  const carrier_ids = carriers.map((carrier) => carrier.carrier_id);

  const convertedDimensions = convertDimensions(dimensions);
  const convertedWeight = convertWeight(dimensions);

  const payload = {
    shipment: {
      ship_to: { /* destination address */ },
      ship_from: { /* warehouse address */ },
      packages: [{
        weight: convertedWeight,
        dimensions: convertedDimensions,
      }],
    },
    rate_options: { carrier_ids },
  };

  const response = await fetch(`${SHIPENGINE_API_URL}/rates`, {
    method: "POST",
    headers: {
      "API-Key": provider.accessToken,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
  });

  const result = await response.json();

  return result.rate_response.rates.map((rate) => ({
    id: JSON.stringify({
      id: rate.rate_id,
      service: rate.service_code,
    }),
    providerId: provider.id,
    service: rate.service_type || rate.service_code,
    carrier: rate.carrier_friendly_name,
    price: (rate.shipping_amount.amount + rate.other_amount.amount).toFixed(2),
    currency: rate.shipping_amount.currency.toUpperCase(),
    estimatedDays: rate.delivery_days,
  }));
}

Creating Labels

export async function createLabelFunction({
  provider,
  order,
  rateId,
  dimensions,
}) {
  const { service, id } = JSON.parse(rateId);

  const payload = {
    shipment: {
      service_code: service,
      ship_to: { /* customer address */ },
      ship_from: { /* warehouse address */ },
      packages: [{
        weight: convertedWeight,
        dimensions: convertedDimensions,
      }],
    },
    label_format: "PDF",
  };

  const response = await fetch(`${SHIPENGINE_API_URL}/labels`, {
    method: "POST",
    headers: {
      "API-Key": provider.accessToken,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
  });

  const result = await response.json();

  return {
    status: "purchased",
    carrier: result.carrier_code,
    service: result.service_type,
    trackingNumber: result.tracking_number,
    trackingUrl: result.tracking_url,
    labelUrl: result.label_download.pdf,
  };
}

Shipping Flow

1
Configure Shipping Provider
2
Set up your shipping provider in the admin dashboard:
3
  • Navigate to Platform > Shipping Providers
  • Click Create Shipping Provider
  • Enter provider details and credentials
  • Configure warehouse/origin address
  • Select regions where this provider is available
  • 4
    Add Shipping Options
    5
    Create shipping options for each region:
    6
    mutation {
      createShippingOption(data: {
        name: "Standard Shipping"
        priceType: "calculated"
        region: { connect: { id: "region_id" } }
        provider: { connect: { id: "provider_id" } }
      }) {
        id
        name
      }
    }
    
    7
    Calculate Rates at Checkout
    8
    Get real-time shipping rates for a cart:
    9
    query {
      cart(where: { id: "cart_id" }) {
        availableShippingMethods {
          id
          name
          price
          estimatedDays
        }
      }
    }
    
    10
    Select Shipping Method
    11
    Customer chooses their preferred shipping:
    12
    mutation {
      setShippingMethod(
        cartId: "cart_id"
        shippingMethodId: "method_id"
      ) {
        id
        shippingMethod {
          name
          price
        }
      }
    }
    
    13
    Generate Label
    14
    After order placement, create shipping label:
    15
    mutation {
      createShippingLabel(
        orderId: "order_id"
        fulfillmentId: "fulfillment_id"
        dimensions: {
          length: 10
          width: 8
          height: 6
          weight: 2
          unit: "in"
          weightUnit: "lb"
        }
      ) {
        id
        trackingNumber
        trackingUrl
        labelUrl
      }
    }
    

    Package Dimensions

    Dimensions are required for calculating accurate rates:
    interface Dimensions {
      length: number
      width: number
      height: number
      weight: number
      unit: "in" | "cm" | "m" | "ft"      // Distance unit
      weightUnit: "lb" | "oz" | "kg" | "g"  // Weight unit
    }
    
    Openfront automatically:
    • Stores default dimensions on product variants
    • Calculates dimensions from cart items
    • Converts units to provider requirements

    Shipping Profiles

    Group products with similar shipping requirements:
    ShippingProfile {
      name: string
      type: "default" | "gift_card" | "digital"
      
      products: [→ Product]
      shippingOptions: [→ ShippingOption]
    }
    
    Use cases:
    • Default: Physical products requiring shipping
    • Gift Card: Digital delivery, no shipping
    • Digital: Downloadable products

    Address Validation

    Validate addresses before creating labels:
    const validation = await validateAddressFunction({
      provider,
      address: {
        firstName: "John",
        lastName: "Doe",
        address1: "1234 Main St",
        city: "San Francisco",
        province: "CA",
        postalCode: "94102",
        country: { iso2: "US" },
      },
    });
    
    if (!validation.isValid) {
      console.log("Address errors:", validation.errors);
    } else {
      console.log("Validated address:", validation.suggestedAddress);
    }
    

    Tracking Shipments

    Track shipment status and location:
    const tracking = await trackShipmentFunction({
      provider,
      trackingNumber: "1Z999AA10123456784",
    });
    
    console.log(tracking.status);
    console.log(tracking.events);
    

    Creating Custom Shipping Adapters

    Add custom shipping providers:
    // features/integrations/shipping-providers/custom-carrier.ts
    
    export async function getRatesFunction({ provider, order, dimensions }) {
      // Call your carrier's API
      const response = await fetch(`https://api.carrier.com/rates`, {
        method: "POST",
        headers: { "API-Key": provider.accessToken },
        body: JSON.stringify({ /* rate request */ }),
      });
      
      const rates = await response.json();
      
      return rates.map(rate => ({
        id: rate.id,
        providerId: provider.id,
        service: rate.service,
        carrier: rate.carrier,
        price: rate.price,
        currency: "USD",
        estimatedDays: rate.transit_days,
      }));
    }
    
    export async function createLabelFunction({ provider, order, rateId, dimensions }) {
      // Generate shipping label
      const response = await fetch(`https://api.carrier.com/labels`, {
        method: "POST",
        headers: { "API-Key": provider.accessToken },
        body: JSON.stringify({ /* label request */ }),
      });
      
      const label = await response.json();
      
      return {
        status: "purchased",
        trackingNumber: label.tracking_number,
        labelUrl: label.label_url,
      };
    }
    
    // Implement other required functions...
    
    Register in features/integrations/shipping-providers/index.ts:
    export const shippingProviderAdapters = {
      shippo: () => import("./shippo"),
      shipengine: () => import("./shipengine"),
      manual: () => import("./manual"),
      custom: () => import("./custom-carrier"),
    };
    

    Multi-Location Fulfillment

    Manage inventory across multiple warehouses:
    Location {
      name: string
      address: string
      
      variants: [→ ProductVariant]
    }
    
    Openfront automatically:
    • Routes orders to nearest fulfillment location
    • Calculates shipping from correct origin
    • Manages stock across locations

    Best Practices

    • Validate addresses before label creation
    • Store dimensions on product variants
    • Use calculated pricing for accurate rates
    • Handle carrier errors gracefully
    • Test with sandbox credentials
    • Monitor webhook events for tracking updates

    Build docs developers (and LLMs) love