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.

Cart Transform Functions

Cart transform functions allow your app to modify cart contents before checkout. Use them to automatically apply discounts, merge similar items, split bundles, add free gifts, or implement complex pricing logic.

How It Works

Function Manifest

{
  "handle": "my-cart-app",
  "name": "Cart Transformer",
  "version": "1.0.0",
  "functions": {
    "cart_transform": {
      "handle": "cart-rules",
      "name": "Cart Transformation Rules",
      "config": {
        "enableBundling": true,
        "freeGiftThreshold": 5000,
        "freeGiftProductId": "prod_gift123"
      }
    }
  }
}

Input Schema

interface CartTransformInput {
  cart: {
    items: CartItem[];
    subtotal: number;
    total: number;
    currency: string;
    discountCodes: string[];
    attributes: Record<string, string>;
  };
  customer: {
    id: string;
    email: string;
    tags: string[];
    ordersCount: number;
  } | null;
  shop: {
    id: string;
    currency: string;
  };
}

interface CartItem {
  id: string;            // Cart line item ID
  variantId: string;
  productId: string;
  title: string;
  variantTitle: string;
  quantity: number;
  price: number;         // Unit price in cents
  originalPrice: number; // Price before any transforms
  sku: string;
  weight: number;
  properties: Record<string, string>;
  discountAllocations: DiscountAllocation[];
}

Output Schema

interface CartTransformOutput {
  operations: CartOperation[];
}

type CartOperation = 
  | { 
      update: { 
        cartItemId: string;
        price?: number;           // New price in cents
        title?: string;           // Override title
        properties?: Record<string, string>;
      } 
    }
  | { 
      merge: { 
        sourceCartItemIds: string[];  // Items to merge
        title: string;                 // Merged item title
        price: number;                 // Total price
        properties?: Record<string, string>;
      } 
    }
  | { 
      split: { 
        cartItemId: string;
        items: SplitItem[];
      } 
    }
  | { 
      add: { 
        variantId: string;
        quantity: number;
        price?: number;           // Override price (0 for free)
        properties?: Record<string, string>;
      } 
    }
  | { 
      remove: { 
        cartItemId: string;
      } 
    };

interface SplitItem {
  variantId: string;
  quantity: number;
  price: number;
  title?: string;
  properties?: Record<string, string>;
}

Operations

Update Item

Modify an existing cart item:
{
  operations: [
    {
      update: {
        cartItemId: 'item_123',
        price: 1999,  // New price
        title: 'Special Edition T-Shirt',
        properties: {
          discount_reason: 'VIP pricing'
        }
      }
    }
  ]
}

Merge Items

Combine multiple items into a bundle:
{
  operations: [
    {
      merge: {
        sourceCartItemIds: ['item_shirt', 'item_pants', 'item_belt'],
        title: 'Complete Outfit Bundle',
        price: 8999,  // Bundle price (vs individual total of $119.97)
        properties: {
          bundle_type: 'outfit',
          discount: '25% off'
        }
      }
    }
  ]
}

Split Item

Break an item into component parts:
{
  operations: [
    {
      split: {
        cartItemId: 'item_bundle',
        items: [
          {
            variantId: 'variant_shirt',
            quantity: 1,
            price: 2999,
            title: 'Bundle Item: T-Shirt'
          },
          {
            variantId: 'variant_hat',
            quantity: 1,
            price: 1999,
            title: 'Bundle Item: Hat'
          }
        ]
      }
    }
  ]
}

Add Item

Add a new item to the cart:
{
  operations: [
    {
      add: {
        variantId: 'variant_gift',
        quantity: 1,
        price: 0,  // Free gift
        properties: {
          _gift: 'true',
          gift_message: 'Free gift with $50+ order!'
        }
      }
    }
  ]
}

Remove Item

Remove an item from the cart:
{
  operations: [
    {
      remove: {
        cartItemId: 'item_invalid'
      }
    }
  ]
}

Example Implementations

Free Gift with Purchase

function transformCart(input, config) {
  const operations = [];
  const { cart } = input;
  
  const threshold = config.freeGiftThreshold || 5000;
  const giftProductId = config.freeGiftProductId;
  
  // Check if cart qualifies for free gift
  const qualifies = cart.subtotal >= threshold;
  
  // Check if gift is already in cart
  const existingGift = cart.items.find(
    item => item.properties?._free_gift === 'true'
  );
  
  if (qualifies && !existingGift) {
    // Add free gift
    operations.push({
      add: {
        variantId: giftProductId,
        quantity: 1,
        price: 0,
        properties: {
          _free_gift: 'true',
          _gift_message: `Free gift with orders over $${threshold / 100}!`
        }
      }
    });
  } else if (!qualifies && existingGift) {
    // Remove gift if no longer qualifying
    operations.push({
      remove: {
        cartItemId: existingGift.id
      }
    });
  }
  
  return { operations };
}

Automatic Bundle Pricing

function transformCart(input, config) {
  const operations = [];
  const { cart } = input;
  
  // Define bundle combinations
  const bundles = [
    {
      name: 'Complete Skincare Set',
      products: ['prod_cleanser', 'prod_toner', 'prod_moisturizer'],
      discount: 0.20  // 20% off when bought together
    },
    {
      name: 'Workout Essentials',
      products: ['prod_shorts', 'prod_shirt', 'prod_socks'],
      discount: 0.15
    }
  ];
  
  for (const bundle of bundles) {
    // Find items that match this bundle
    const matchingItems = cart.items.filter(
      item => bundle.products.includes(item.productId)
    );
    
    // Check if all bundle products are present
    const hasAllProducts = bundle.products.every(
      productId => matchingItems.some(item => item.productId === productId)
    );
    
    if (hasAllProducts) {
      const originalTotal = matchingItems.reduce(
        (sum, item) => sum + (item.price * item.quantity), 0
      );
      const bundlePrice = Math.round(originalTotal * (1 - bundle.discount));
      
      operations.push({
        merge: {
          sourceCartItemIds: matchingItems.map(item => item.id),
          title: bundle.name,
          price: bundlePrice,
          properties: {
            _bundle: 'true',
            original_price: originalTotal.toString(),
            savings: `${bundle.discount * 100}% off`
          }
        }
      });
    }
  }
  
  return { operations };
}

Volume Pricing / Quantity Breaks

function transformCart(input, config) {
  const operations = [];
  const { cart } = input;
  
  // Define quantity break pricing
  const quantityBreaks = {
    'prod_tshirt': [
      { minQty: 1, priceEach: 2499 },
      { minQty: 3, priceEach: 2199 },
      { minQty: 6, priceEach: 1999 },
      { minQty: 12, priceEach: 1799 }
    ]
  };
  
  for (const item of cart.items) {
    const breaks = quantityBreaks[item.productId];
    if (!breaks) continue;
    
    // Find applicable tier
    const applicableTier = [...breaks]
      .reverse()
      .find(tier => item.quantity >= tier.minQty);
    
    if (applicableTier && applicableTier.priceEach < item.price) {
      const savings = (item.price - applicableTier.priceEach) * item.quantity;
      
      operations.push({
        update: {
          cartItemId: item.id,
          price: applicableTier.priceEach,
          properties: {
            ...item.properties,
            _volume_discount: 'true',
            original_price: item.price.toString(),
            tier: `${applicableTier.minQty}+ @ $${(applicableTier.priceEach / 100).toFixed(2)} each`
          }
        }
      });
    }
  }
  
  return { operations };
}

VIP Customer Pricing

function transformCart(input, config) {
  const operations = [];
  const { cart, customer } = input;
  
  if (!customer) return { operations };
  
  // VIP tiers
  const vipDiscounts = {
    'vip_gold': 0.15,
    'vip_silver': 0.10,
    'vip_bronze': 0.05
  };
  
  // Find customer's VIP tier
  const vipTag = customer.tags.find(tag => tag.startsWith('vip_'));
  const discount = vipDiscounts[vipTag];
  
  if (!discount) return { operations };
  
  // Apply VIP pricing to all items
  for (const item of cart.items) {
    // Skip items already on sale or marked non-discountable
    if (item.properties?._no_discount === 'true') continue;
    if (item.price < item.originalPrice) continue;
    
    const vipPrice = Math.round(item.price * (1 - discount));
    
    operations.push({
      update: {
        cartItemId: item.id,
        price: vipPrice,
        properties: {
          ...item.properties,
          _vip_price: 'true',
          original_price: item.price.toString(),
          discount_tier: vipTag
        }
      }
    });
  }
  
  return { operations };
}

Product Substitution

function transformCart(input, config) {
  const operations = [];
  const { cart } = input;
  
  // Define substitutions (e.g., for out-of-stock items)
  const substitutions = {
    'variant_old_sku': {
      newVariantId: 'variant_new_sku',
      reason: 'Product updated to new version'
    },
    'variant_discontinued': {
      newVariantId: 'variant_replacement',
      reason: 'Original item discontinued, replaced with equivalent'
    }
  };
  
  for (const item of cart.items) {
    const substitution = substitutions[item.variantId];
    if (!substitution) continue;
    
    // Remove old item and add substitute
    operations.push({
      remove: { cartItemId: item.id }
    });
    
    operations.push({
      add: {
        variantId: substitution.newVariantId,
        quantity: item.quantity,
        properties: {
          ...item.properties,
          _substituted: 'true',
          substitution_reason: substitution.reason,
          original_variant: item.variantId
        }
      }
    });
  }
  
  return { operations };
}

Split Bundle into Components

function transformCart(input, config) {
  const operations = [];
  const { cart } = input;
  
  // Find bundles that need to be split for fulfillment
  for (const item of cart.items) {
    if (item.productId !== 'prod_mystery_box') continue;
    
    // Mystery box contains 3 random items
    const boxContents = getBoxContents(item.variantId);
    
    operations.push({
      split: {
        cartItemId: item.id,
        items: boxContents.map((content, index) => ({
          variantId: content.variantId,
          quantity: item.quantity,
          price: 0, // Individual items show as $0
          title: `Mystery Box Item ${index + 1}`,
          properties: {
            _from_bundle: item.id,
            bundle_name: 'Mystery Box'
          }
        }))
      }
    });
  }
  
  return { operations };
}

Cart Display

After transformations, the cart shows modified items:
Your Cart
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Complete Skincare Set                    ← Merged bundle
  Cleanser + Toner + Moisturizer
  $79.99 ($99.97)  Save 20%

T-Shirt x 6                              ← Volume pricing
  $19.99 each ($24.99)
  Bulk discount: 6+ items

FREE GIFT: Travel Bag                    ← Auto-added gift
  $0.00
  Free with orders $50+

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Subtotal: $199.93
You saved: $35.95

Property Conventions

Use underscore-prefixed properties for internal data:
PropertyPurpose
_free_giftMarks auto-added free items
_bundleMarks merged bundle items
_from_bundleLinks split items to original bundle
_volume_discountMarks items with quantity pricing
_vip_priceMarks VIP-discounted items
_no_discountPrevents additional discounts
_substitutedMarks substituted items
Properties starting with underscore are hidden from customers but accessible in Liquid templates and order data.

Error Handling

function transformCart(input, config) {
  try {
    const operations = [];
    
    // Your transformation logic
    
    // Validate operations
    for (const op of operations) {
      if (op.update && !cart.items.find(i => i.id === op.update.cartItemId)) {
        // Skip invalid operations
        continue;
      }
      // Add valid operations
    }
    
    return { operations };
  } catch (error) {
    console.error('Cart transform error:', error);
    return { operations: [] };  // No changes on error
  }
}

Best Practices

Don’t make unexpected changes. Merging items or changing prices should be transparent to the customer.
When applying discounts, store the original price in properties so it can be displayed as a strikethrough.
Test with single items, large quantities, mixed carts, and empty carts.
Only remove items added by your function (like expired free gifts). Never remove customer-added items.
Ensure running the same transform twice produces the same result. Check for existing modifications.
Cart transforms run on every cart update. Keep logic fast and efficient.

See Also