Skip to main content

Buyer Subscriptions Extension

The buyer subscriptions extension provides a comprehensive customer-facing interface for managing active subscriptions. Built as a customer account page extension, it allows customers to view, modify, pause, resume, and cancel their subscriptions.

Extension Configuration

The extension is configured in shopify.extension.toml:
extensions/buyer-subscriptions/shopify.extension.toml
api_version = "2025-01"

[[extensions]]
type = "ui_extension"
name = "Management page"
handle = "buyer-subscriptions"

  [[extensions.targeting]]
  module = "./src/index.tsx"
  target = "customer-account.page.render"

  [extensions.capabilities]
  api_access = true
  network_access = true
This extension targets customer-account.page.render, which injects it as a page in the customer account area.

Architecture

Entry Point

The extension uses a Router component to handle navigation between list and detail views:
extensions/buyer-subscriptions/src/index.tsx
import {reactExtension} from '@shopify/ui-extensions-react/customer-account';
import {Router} from './App';
import {ErrorBoundary} from 'foundation/ErrorBoundary';

export default reactExtension('customer-account.page.render', () => <App />);

function App() {
  return (
    <ErrorBoundary>
      <Router />
    </ErrorBoundary>
  );
}

Router Logic

The router determines which view to render based on the URL:
extensions/buyer-subscriptions/src/App.tsx
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 />;
}

Key Components

Subscription List

Displays all active subscriptions for the customer:
extensions/buyer-subscriptions/src/SubscriptionList/SubscriptionList.tsx
export function SubscriptionList() {
  const {i18n} = useExtensionApi();
  const {data, loading, error, refetchSubscriptionListData} =
    useSubscriptionListData();

  if (loading && !data) {
    return <SubscriptionListLoadingState />;
  }

  if (error) {
    return <SubscriptionListErrorState />;
  }

  if (data?.subscriptionContracts.length === 0) {
    return <SubscriptionListEmptyState />;
  }

  return (
    <Page title={i18n.translate('subscriptions')}>
      <Grid columns={...} spacing="loose" rows="auto">
        {data.subscriptionContracts.map((contract) => (
          <SubscriptionListItem
            key={contract.id}
            {...contract}
            refetchSubscriptionListData={refetchSubscriptionListData}
          />
        ))}
      </Grid>
    </Page>
  );
}

List View

Grid layout showing all subscriptions with key details, status, and quick actions

Detail View

Comprehensive view of a single subscription with upcoming orders and management options

Subscription Details

Provides detailed view and management capabilities for a specific subscription:
extensions/buyer-subscriptions/src/SubscriptionDetails/SubscriptionDetails.tsx
export function SubscriptionDetails({id}: SubscriptionDetailsProps) {
  const {data, loading, error, refetchSubscriptionContract} =
    useSubscriptionContract({id});
  const {i18n} = useExtensionApi();
  const {showSuccessToast} = useToast();

  const {
    deliveryPolicy,
    lines,
    orders,
    shippingAddress,
    status,
    upcomingBillingCycles,
    lastOrderPrice,
    priceBreakdownEstimate,
  } = data.subscriptionContract;

  return (
    <Page
      title={i18n.translate('manageSubscription')}
      secondaryAction={<Button to="extension:/" />}
      primaryAction={
        <DetailsActions
          contractId={contractId}
          status={status}
          refetchSubscriptionContract={refetchSubscriptionContract}
        />
      }
    >
      <Grid>
        <GridItem>
          <UpcomingOrderCard
            upcomingBillingCycles={upcomingBillingCycles}
            contractId={contractId}
          />
          <OverviewCard
            deliveryPolicy={deliveryPolicy}
            shippingAddress={shippingAddress}
          />
        </GridItem>
        <GridItem>
          <PriceSummaryCard price={priceBreakdownEstimate} lines={lines} />
          <PastOrdersCard orders={orders} />
        </GridItem>
      </Grid>
    </Page>
  );
}

UI Components

The extension uses Shopify’s Customer Account UI components:
  • Page - Page container with title and actions
  • Grid / GridItem - Responsive grid layout
  • Card - Content containers
  • BlockStack / InlineStack - Flex layouts

Features

Subscription Management

Customers can perform the following actions:
  • View Details: See subscription items, pricing, and delivery schedule
  • Skip Orders: Skip the next delivery
  • Pause/Resume: Temporarily pause and resume subscriptions
  • Cancel: Cancel active subscriptions
  • Update Delivery Date: Change the next delivery date
  • Modify Payment: Update payment methods
  • Change Address: Update shipping address

Status Handling

The extension handles various subscription states:
enum SubscriptionStatus {
  ACTIVE = 'ACTIVE',
  PAUSED = 'PAUSED',
  CANCELLED = 'CANCELLED',
  EXPIRED = 'EXPIRED',
  FAILED = 'FAILED'
}

Error Handling

Billing errors are displayed to customers with appropriate messaging:
enum BillingAttemptErrorType {
  PaymentError = 'PAYMENT_ERROR',
  InventoryError = 'INVENTORY_ERROR',
  InvalidShippingAddress = 'INVALID_SHIPPING_ADDRESS'
}
When inventory errors occur, customers see a banner explaining the issue and are prompted to modify their subscription.

Data Fetching

The extension uses GraphQL to fetch subscription data:

Subscription List Query

query SubscriptionContracts {
  customer {
    subscriptionContracts(first: 50) {
      edges {
        node {
          id
          status
          nextBillingDate
          lines(first: 10) {
            edges {
              node {
                id
                title
                variantImage {
                  url
                }
                quantity
              }
            }
          }
          deliveryPolicy {
            interval
            intervalCount
          }
        }
      }
    }
  }
}

Subscription Details Query

query SubscriptionContract($id: ID!) {
  subscriptionContract(id: $id) {
    id
    status
    upcomingBillingCycles(first: 1) {
      edges {
        node {
          billingAttemptExpectedDate
        }
      }
    }
    deliveryPolicy {
      interval
      intervalCount
    }
    shippingAddress {
      address1
      city
      province
      zip
    }
    priceBreakdownEstimate {
      totalPrice {
        amount
        currencyCode
      }
    }
  }
}

Customization

Styling

The extension uses Shopify’s design system with responsive breakpoints:
const columns = Style.default(['fill'])
  .when({viewportInlineSize: {min: 'small'}}, ['fill', 'fill'])
  .when({viewportInlineSize: {min: 'medium'}}, ['fill', 'fill', 'fill'])

Localization

Add translations in locales/ directory:
locales/en.json
{
  "subscriptions": "Subscriptions",
  "manageSubscription": "Manage subscription",
  "subscriptionDetails": "Subscription details",
  "pause": "Pause",
  "resume": "Resume",
  "cancel": "Cancel subscription"
}

Custom Hooks

Create custom hooks for business logic:
export function useSubscriptionListData() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  // Fetch and manage subscription data
  
  return {data, loading, refetchSubscriptionListData};
}

Order Action Extension

Adds “Manage subscription” links to order history

Thank You Page

Post-purchase link to subscription management

Best Practices

  1. Error Boundaries: Wrap components in error boundaries to prevent crashes
  2. Loading States: Always show skeleton screens during data fetching
  3. Optimistic Updates: Provide immediate feedback before API calls complete
  4. Refetch Data: Refresh subscription data after mutations
  5. Toast Notifications: Show success/error toasts for user actions

Testing

Test the extension locally:
npm run dev
This starts the development server and allows testing in a Shopify development store.
The extension requires customer authentication and active subscription contracts for testing.

Build docs developers (and LLMs) love