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.

Post-Purchase Extensions

Post-purchase extensions display content on the order confirmation page immediately after a customer completes their purchase. Use them for upsells, surveys, app installations, loyalty program enrollment, and more.

When Post-Purchase Runs

Post-purchase extensions appear between payment completion and the order confirmation page. Customers can skip them if they choose.

Extension Manifest

Register your post-purchase extension in app.json:
{
  "handle": "my-upsell-app",
  "name": "Post-Purchase Upsells",
  "version": "1.0.0",
  "extensions": {
    "post_purchase": [
      {
        "handle": "order-upsell",
        "name": "Order Upsell",
        "component_url": "https://my-app.com/post-purchase/upsell.js"
      },
      {
        "handle": "survey",
        "name": "Customer Survey",
        "component_url": "https://my-app.com/post-purchase/survey.js"
      }
    ]
  }
}

Creating a Post-Purchase Extension

Post-purchase extensions are React components that receive order data:
import { 
  usePostPurchase,
  useOrder,
  Button,
  Layout,
  BlockStack,
  Heading,
  Text,
  Image
} from '@launchmystore/post-purchase-ui-extensions-react';

export default function OrderUpsell() {
  const postPurchase = usePostPurchase();
  const order = useOrder();
  const [isAdding, setIsAdding] = useState(false);
  
  // Get recommended products based on order
  const recommendations = useRecommendations(order.lineItems);
  
  const handleAddToOrder = async (product) => {
    setIsAdding(true);
    
    try {
      await postPurchase.addToOrder({
        variantId: product.variantId,
        quantity: 1
      });
      
      postPurchase.showToast({
        message: `${product.title} added to your order!`,
        type: 'success'
      });
    } catch (error) {
      postPurchase.showToast({
        message: 'Failed to add item',
        type: 'error'
      });
    } finally {
      setIsAdding(false);
    }
  };
  
  const handleDecline = () => {
    postPurchase.done();
  };
  
  if (!recommendations.length) {
    postPurchase.done();
    return null;
  }
  
  return (
    <Layout>
      <BlockStack spacing="loose">
        <Heading level={1}>Wait! Special offer just for you</Heading>
        <Text>Add these items to your order with one click:</Text>
        
        {recommendations.map((product) => (
          <div key={product.id} className="upsell-product">
            <Image 
              src={product.image} 
              alt={product.title}
              width={80}
              height={80}
            />
            <div className="upsell-details">
              <Text weight="bold">{product.title}</Text>
              <Text>
                <s>${product.compareAtPrice}</s> ${product.price}
              </Text>
              <Text size="small" color="subdued">
                {product.savings}% off - Limited time!
              </Text>
            </div>
            <Button
              onClick={() => handleAddToOrder(product)}
              loading={isAdding}
            >
              Add to Order
            </Button>
          </div>
        ))}
        
        <Button onClick={handleDecline} plain>
          No thanks, continue to confirmation
        </Button>
      </BlockStack>
    </Layout>
  );
}

Post-Purchase Context

Extensions receive detailed order information:
interface PostPurchaseContext {
  order: {
    id: string;
    orderNumber: string;
    totalPrice: number;
    subtotalPrice: number;
    shippingPrice: number;
    taxPrice: number;
    currency: string;
    lineItems: LineItem[];
    shippingAddress: Address;
    billingAddress: Address;
    customer: Customer;
    discountCodes: string[];
  };
  shop: {
    id: string;
    name: string;
    domain: string;
  };
}

interface LineItem {
  id: string;
  title: string;
  variantTitle: string;
  quantity: number;
  price: number;
  productId: string;
  variantId: string;
  image: string;
  sku: string;
}

Post-Purchase API

usePostPurchase Hook

const postPurchase = usePostPurchase();

// Add item to the order (one-click upsell)
await postPurchase.addToOrder({
  variantId: 'variant_123',
  quantity: 1,
  customAttributes: { source: 'post_purchase_upsell' }
});

// Show toast notification
postPurchase.showToast({
  message: 'Added to order!',
  type: 'success' // 'success' | 'error' | 'info'
});

// Complete post-purchase and continue to confirmation
postPurchase.done();

// Track analytics event
postPurchase.trackEvent('upsell_viewed', {
  productId: 'prod_123',
  position: 1
});

useOrder Hook

const order = useOrder();

console.log(order.orderNumber);      // "#1234"
console.log(order.totalPrice);       // 9999 (cents)
console.log(order.lineItems);        // Array of items
console.log(order.customer.email);   // "customer@example.com"

useShop Hook

const shop = useShop();

console.log(shop.name);    // "My Store"
console.log(shop.domain);  // "mystore.launchmystore.io"

UI Components

The @launchmystore/post-purchase-ui-extensions-react package includes styled components:

Layout Components

import { 
  Layout, 
  BlockStack, 
  InlineStack,
  Divider 
} from '@launchmystore/post-purchase-ui-extensions-react';

<Layout maxWidth={600} padding="base">
  <BlockStack spacing="loose">
    <Heading>Special Offer</Heading>
    
    <InlineStack spacing="base" alignment="center">
      <Image src={product.image} />
      <Text>{product.title}</Text>
    </InlineStack>
    
    <Divider />
    
    <Button>Add to Order</Button>
  </BlockStack>
</Layout>

Typography

import { Heading, Text } from '@launchmystore/post-purchase-ui-extensions-react';

<Heading level={1}>Main Heading</Heading>
<Heading level={2}>Subheading</Heading>

<Text>Regular text</Text>
<Text weight="bold">Bold text</Text>
<Text size="small">Small text</Text>
<Text color="subdued">Muted text</Text>
<Text color="success">Success text</Text>

Buttons

import { Button } from '@launchmystore/post-purchase-ui-extensions-react';

<Button onClick={handleAdd}>Add to Order</Button>
<Button onClick={handleAdd} loading>Adding...</Button>
<Button onClick={handleDecline} plain>No thanks</Button>
<Button onClick={handleRemove} destructive>Remove</Button>
<Button disabled>Sold Out</Button>

Images

import { Image } from '@launchmystore/post-purchase-ui-extensions-react';

<Image 
  src={product.image}
  alt={product.title}
  width={200}
  height={200}
  borderRadius="base"
/>

Common Use Cases

One-Click Upsells

Offer complementary products that can be added instantly

Surveys

Ask customers how they heard about you or gather feedback

Loyalty Enrollment

Invite customers to join your rewards program

App Promotion

Encourage customers to download your mobile app

Example: Customer Survey

import { useState } from 'react';
import {
  usePostPurchase,
  useOrder,
  Layout,
  BlockStack,
  Heading,
  Text,
  Button,
  RadioGroup,
  TextField
} from '@launchmystore/post-purchase-ui-extensions-react';

export default function CustomerSurvey() {
  const postPurchase = usePostPurchase();
  const order = useOrder();
  
  const [source, setSource] = useState('');
  const [feedback, setFeedback] = useState('');
  const [submitting, setSubmitting] = useState(false);
  
  const sourceOptions = [
    { value: 'google', label: 'Google Search' },
    { value: 'social', label: 'Social Media' },
    { value: 'friend', label: 'Friend/Family Referral' },
    { value: 'ad', label: 'Online Advertisement' },
    { value: 'other', label: 'Other' }
  ];
  
  const handleSubmit = async () => {
    setSubmitting(true);
    
    try {
      // Send survey data to your app
      await fetch('https://my-app.com/api/survey', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          orderId: order.id,
          customerId: order.customer.id,
          source,
          feedback
        })
      });
      
      postPurchase.showToast({
        message: 'Thanks for your feedback!',
        type: 'success'
      });
      
      postPurchase.trackEvent('survey_completed', { source });
      postPurchase.done();
    } catch (error) {
      postPurchase.showToast({
        message: 'Failed to submit survey',
        type: 'error'
      });
      setSubmitting(false);
    }
  };
  
  const handleSkip = () => {
    postPurchase.trackEvent('survey_skipped');
    postPurchase.done();
  };
  
  return (
    <Layout maxWidth={500} padding="loose">
      <BlockStack spacing="loose">
        <Heading level={1}>Quick Question</Heading>
        <Text>Help us improve by answering a quick question:</Text>
        
        <BlockStack spacing="tight">
          <Text weight="bold">How did you hear about us?</Text>
          <RadioGroup
            options={sourceOptions}
            value={source}
            onChange={setSource}
          />
        </BlockStack>
        
        <TextField
          label="Any additional feedback? (Optional)"
          value={feedback}
          onChange={setFeedback}
          multiline
          maxLength={500}
        />
        
        <Button 
          onClick={handleSubmit} 
          loading={submitting}
          disabled={!source}
        >
          Submit
        </Button>
        
        <Button onClick={handleSkip} plain>
          Skip
        </Button>
      </BlockStack>
    </Layout>
  );
}

Example: Loyalty Enrollment

import { useState } from 'react';
import {
  usePostPurchase,
  useOrder,
  Layout,
  BlockStack,
  InlineStack,
  Heading,
  Text,
  Button,
  Image
} from '@launchmystore/post-purchase-ui-extensions-react';

export default function LoyaltyEnrollment() {
  const postPurchase = usePostPurchase();
  const order = useOrder();
  const [enrolling, setEnrolling] = useState(false);
  
  // Calculate points earned from this order
  const pointsEarned = Math.floor(order.totalPrice / 100); // 1 point per dollar
  
  const handleEnroll = async () => {
    setEnrolling(true);
    
    try {
      await fetch('https://my-app.com/api/loyalty/enroll', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          customerId: order.customer.id,
          email: order.customer.email,
          initialPoints: pointsEarned
        })
      });
      
      postPurchase.showToast({
        message: `Welcome! ${pointsEarned} points added to your account.`,
        type: 'success'
      });
      
      postPurchase.trackEvent('loyalty_enrolled');
      postPurchase.done();
    } catch (error) {
      postPurchase.showToast({
        message: 'Enrollment failed. Please try again.',
        type: 'error'
      });
      setEnrolling(false);
    }
  };
  
  return (
    <Layout maxWidth={500} padding="loose">
      <BlockStack spacing="loose" alignment="center">
        <Image 
          src="https://my-app.com/loyalty-badge.png"
          alt="Rewards"
          width={80}
          height={80}
        />
        
        <Heading level={1}>Join Our Rewards Program</Heading>
        
        <Text alignment="center">
          You just earned <strong>{pointsEarned} points</strong> from this order!
          Join now to start saving on future purchases.
        </Text>
        
        <BlockStack spacing="tight">
          <InlineStack spacing="base" alignment="center">
            <span></span>
            <Text>Earn 1 point for every $1 spent</Text>
          </InlineStack>
          <InlineStack spacing="base" alignment="center">
            <span></span>
            <Text>100 points = $5 off your next order</Text>
          </InlineStack>
          <InlineStack spacing="base" alignment="center">
            <span></span>
            <Text>Exclusive member-only discounts</Text>
          </InlineStack>
        </BlockStack>
        
        <Button onClick={handleEnroll} loading={enrolling}>
          Join & Claim {pointsEarned} Points
        </Button>
        
        <Button onClick={() => postPurchase.done()} plain>
          Maybe later
        </Button>
      </BlockStack>
    </Layout>
  );
}

Best Practices

Customers want to see their confirmation. Make your post-purchase extension quick and valuable.
Offer genuine discounts, useful information, or quick actions. Don’t just show ads.
Provide a clear “No thanks” or “Skip” option. Never trap customers in the post-purchase flow.
If an API call fails, show a friendly message and allow the customer to continue.
Use trackEvent to understand how customers interact with your extension.
Many customers checkout on mobile. Ensure your extension is responsive.

Timing and Display Rules

You can control when your extension appears:
{
  "handle": "order-upsell",
  "name": "Order Upsell",
  "component_url": "https://my-app.com/post-purchase/upsell.js",
  "display_rules": {
    "min_order_value": 2500,
    "max_order_value": 50000,
    "product_types": ["clothing", "accessories"],
    "exclude_discount_orders": false,
    "customer_tags": ["vip", "repeat"]
  }
}
RuleDescription
min_order_valueMinimum order value in cents
max_order_valueMaximum order value in cents
product_typesOnly show for orders containing these product types
exclude_discount_ordersHide for orders with discount codes
customer_tagsOnly show for customers with these tags

Testing

Test your post-purchase extension using the development mode:
# Start local development server
npm run dev

# In your app settings, set component URL to:
# https://localhost:3000/post-purchase/upsell.js
Use the LaunchMyStore developer tools to simulate different order scenarios.