Skip to main content
The Shopify Subscriptions Reference App provides a comprehensive customer portal built as a Shopify Customer Account UI extension, allowing subscribers to manage their subscriptions directly.

Overview

The customer portal is a UI extension that appears in Shopify Customer Accounts, providing customers with self-service subscription management capabilities:

View Subscriptions

Browse all active, paused, and canceled subscriptions

Manage Deliveries

Update shipping addresses and delivery methods

Control Billing

Pause, resume, skip, or cancel subscriptions

Update Payment

Manage payment methods for recurring charges
The customer portal is implemented as a UI extension in extensions/buyer-subscriptions/ and integrates seamlessly with Shopify Customer Accounts.

Portal Structure

The customer portal consists of two main views:

Subscription List

Displays all subscriptions for the logged-in customer:
  • Active subscriptions with next billing date
  • Paused subscriptions with pause status
  • Canceled subscriptions with cancellation date
  • Quick status badges and pricing information

Subscription Details

Shows comprehensive information for a single subscription:
  • Upcoming order details and next billing date
  • Price breakdown and line items
  • Delivery information (shipping or pickup)
  • Past order history
  • Management actions (pause, resume, cancel, skip)

Router Implementation

The portal uses client-side routing in extensions/buyer-subscriptions/src/App.tsx:6-17:
export function Router() {
  const currentEntry = useNavigationCurrentEntry();
  const url = new URL(currentEntry.url);

  if (url.pathname.includes('subscriptions')) {
    const id = getSubscriptionIdFromPath(url.pathname);
    return <SubscriptionDetails id={id} />;
  }
  return <SubscriptionList />;
}

Subscription Details Page

The details page provides a comprehensive view and management interface from extensions/buyer-subscriptions/src/SubscriptionDetails/SubscriptionDetails.tsx:32-183:
export function SubscriptionDetails({id}: SubscriptionDetailsProps) {
  const {data, loading, error, refetchSubscriptionContract} =
    useSubscriptionContract({id});
  const {i18n} = useExtensionApi();
  const {showSuccessToast} = useToast();

  const {
    id: contractId,
    deliveryPolicy,
    lines,
    orders,
    shippingAddress,
    shippingMethodTitle,
    pickupAddress,
    priceBreakdownEstimate,
    status,
    upcomingBillingCycles,
    lastOrderPrice,
    lastBillingAttemptErrorType,
  } = data.subscriptionContract;

  const {nextBillingDate} = getBillingCycleInfo(upcomingBillingCycles);

  return (
    <Page
      title={pageTitle}
      primaryAction={
        <DetailsActions
          contractId={contractId}
          status={status}
          refetchSubscriptionContract={refetchSubscriptionContract}
          lastOrderPrice={lastOrderPrice}
          nextOrderPrice={priceBreakdownEstimate?.totalPrice}
          nextBillingDate={nextBillingDate}
        />
      }
    >
      <Grid columns={...} rows="auto" spacing={['loose', 'loose']}>
        <GridItem columnSpan={...}>
          <BlockStack spacing="loose">
            <UpcomingOrderCard {...} />
            <OverviewCard {...} />
          </BlockStack>
        </GridItem>
        <GridItem columnSpan={...}>
          <BlockStack spacing="loose">
            <PriceSummaryCard price={priceBreakdownEstimate} lines={lines} />
            <PastOrdersCard orders={orders} />
          </BlockStack>
        </GridItem>
      </Grid>
    </Page>
  );
}

Customer Actions

Customers can perform several actions on their subscriptions:

Pause Subscription

1

Access Subscription Details

Navigate to an active subscription in the customer portal
2

Click Pause Button

Select the pause action from the primary actions
3

Confirm Pause

Review the pause confirmation modal and confirm
4

Subscription Paused

The subscription status changes to PAUSED and billing stops
Implementation from extensions/buyer-subscriptions/src/SubscriptionDetails/DetailsActions/DetailsActions.tsx:20-44:
export function DetailsActions({
  contractId,
  status,
  refetchSubscriptionContract,
}: DetailsActionsProps) {
  const {i18n} = useExtensionApi();
  const {showSuccessToast} = useToast();

  function onPauseSubscription() {
    refetchSubscriptionContract();
    showSuccessToast(SuccessToastType.Paused);
  }

  if (status === 'ACTIVE') {
    return (
      <Button
        overlay={
          <PauseSubscriptionModal
            contractId={contractId}
            onPauseSubscription={onPauseSubscription}
          />
        }
      >
        <Text>{i18n.translate('subscriptionActions.pause')}</Text>
      </Button>
    );
  }
}
Pause actions initiated from the customer portal trigger the same backend services as admin-initiated pauses, ensuring consistency.

Resume Subscription

Customers can reactivate paused subscriptions:
  • Resume modal shows next billing date
  • Displays price comparison (last order vs. next order)
  • Confirms billing will restart immediately
{status === 'PAUSED' ? (
  <Button
    overlay={
      <ResumeSubscriptionModal
        contractId={contractId}
        resumeDate={nextBillingDate}
        lastOrderPrice={lastOrderPrice}
        nextOrderPrice={nextOrderPrice}
        onResumeSubscription={onResumeSubscription}
      />
    }
  >
    <Text>{i18n.translate('subscriptionActions.resume')}</Text>
  </Button>
) : null}

Cancel Subscription

Customers can permanently cancel their subscriptions:
  • Cancel modal explains the action is permanent
  • Shows final billing information
  • Requires explicit confirmation
<Button
  overlay={
    <CancelSubscriptionModal
      contractId={contractId}
      onCancelSubscription={onCancelSubscription}
    />
  }
>
  <Text>{i18n.translate('cancel')}</Text>
</Button>
Cancellations from the customer portal are permanent and cannot be undone. Consider offering a pause option before allowing cancellation.

Skip Next Order

Customers can skip upcoming billing cycles:
  • Available on the Upcoming Order Card
  • Skips the next scheduled billing date
  • Automatically schedules the following billing cycle
function onSkipOrder() {
  showSuccessToast(SuccessToastType.Skipped);
  refetchSubscriptionContract();
}

Delivery Management

Customers can update delivery information:

Update Shipping Address

1

Open Delivery Modal

Click edit on the Overview Card delivery section
2

Enter New Address

Fill in the new shipping address form
3

Select Delivery Method

Choose from available delivery methods for the new address
4

Confirm Changes

Review and save the address update
The address update flow:
<DeliveryModal
  contractId={contractId}
  shippingAddress={shippingAddress}
  shippingMethodTitle={shippingMethodTitle}
  pickupAddress={pickupAddress}
  onDeliveryUpdate={refetchSubscriptionContract}
/>

Delivery Method Selection

When updating addresses, customers can:
  • Choose standard shipping options
  • Select local pickup if available
  • View shipping costs for each method
  • See estimated delivery times
The portal automatically fetches available delivery methods for the new address, ensuring customers only see valid shipping options.

Order Information

Upcoming Order Card

Displays details about the next scheduled order:
  • Next billing date and time
  • Product line items with images
  • Quantities and pricing
  • Skip order option
  • Inventory error warnings (if applicable)
<UpcomingOrderCard
  onSkipOrder={onSkipOrder}
  refetchSubscriptionContract={refetchSubscriptionContract}
  refetchLoading={refetchLoading}
  contractId={contractId}
  upcomingBillingCycles={upcomingBillingCycles}
  hasInventoryError={hasInventoryError}
/>

Past Orders Card

Shows history of completed orders:
  • Order date and order number
  • Total amount charged
  • Link to order status page
  • Payment status
<PastOrdersCard orders={orders} />

Price Summary Card

Breaks down subscription pricing:
  • Line item subtotals
  • Discounts applied
  • Shipping costs
  • Taxes
  • Total amount
<PriceSummaryCard 
  price={priceBreakdownEstimate} 
  lines={lines} 
/>

Payment Method Management

Customers manage payment methods through Shopify’s native payment management:
  • View current payment method
  • Receive payment update emails from merchants
  • Update payment methods via secure Shopify-hosted pages
  • Receive confirmation of payment updates
Payment method updates use Shopify’s built-in customer payment management for security and PCI compliance.

Email Notifications

The customer portal integrates with email notifications in app/routes/customerAccount.emails.ts:14-56:
export async function action({request}: ActionFunctionArgs) {
  const {sessionToken, cors} =
    await authenticate.public.customerAccount(request);

  const {sub: customerGid, dest: shopDomain} = sessionToken;
  const body = await request.json();
  const {admin_graphql_api_id, operationName} = body;

  if (['PAUSE', 'RESUME'].includes(operationName)) {
    jobs.enqueue(
      new CustomerSendEmailJob({
        payload: {
          admin_graphql_api_id,
          admin_graphql_api_customer_id: customerGid,
          emailTemplate:
            operationName === 'PAUSE'
              ? 'SUBSCRIPTION_PAUSED'
              : 'SUBSCRIPTION_RESUMED',
        },
        shop: shopDomain,
      }),
    );
  }

  return cors(json({status: 'success'}, {status: 200}));
}
Customers receive emails for:
  • Subscription paused confirmation
  • Subscription resumed confirmation
  • Payment retry notifications
  • Payment failure warnings
  • Payment method update requests

Status Badges

Visual indicators for subscription status:
Subscription is active and billing normally. Shows next billing date prominently.
Subscription is temporarily paused. Shows pause status and resume option.
Subscription has been permanently canceled. Shows historical data only, no management actions available.

Error Handling

The customer portal gracefully handles various error conditions:

Inventory Errors

const hasInventoryError =
  (lastBillingAttemptErrorType as BillingAttemptErrorType | null) ===
  BillingAttemptErrorType.InventoryError;
When inventory issues occur:
  • Banner displays inventory error message
  • Customers can’t skip if already skipped due to inventory
  • Clear messaging about merchant restocking

Payment Errors

  • Failed payment banner on subscription details
  • Link to update payment method
  • Clear explanation of retry schedule

Loading States

function SubscriptionDetailsSkeleton() {
  return (
    <Page title="" loading>
      <Grid columns={...} spacing={['loose', 'loose']}>
        <GridItem columnSpan={...}>
          <BlockStack spacing="loose">
            <Card padding>
              <SkeletonTextBlock />
            </Card>
          </BlockStack>
        </GridItem>
      </Grid>
    </Page>
  );
}

Responsive Design

The portal adapts to different screen sizes:
<Grid
  columns={Style.default(['fill'])
    .when({viewportInlineSize: {min: 'medium'}}, ['fill', 'fill'])
    .when({viewportInlineSize: {min: 'large'}}, [
      'fill', 'fill', 'fill', 'fill', 'fill', 'fill',
      'fill', 'fill', 'fill', 'fill', 'fill', 'fill',
    ])}
  rows="auto"
  spacing={['loose', 'loose']}
>
  • Mobile: Single column layout
  • Tablet: Two column layout
  • Desktop: Full grid layout with optimized spacing

Best Practices

1

Clear Action Labels

Use descriptive button labels and confirmation modals to prevent accidental actions
2

Provide Context

Show pricing, dates, and impacts before customers commit to changes
3

Graceful Errors

Display helpful error messages with clear resolution steps
4

Optimize Performance

Use loading states and skeleton screens for better perceived performance
5

Mobile-First Design

Ensure all actions are accessible and easy to use on mobile devices
Test the customer portal on actual mobile devices to ensure touch targets are appropriately sized and forms are easy to complete.

Build docs developers (and LLMs) love