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:
Layout Components
Interactive Components
Display Components
Page - Page container with title and actions
Grid / GridItem - Responsive grid layout
Card - Content containers
BlockStack / InlineStack - Flex layouts
Button - Action buttons with navigation
Banner - Status and error messages
Modal - Dialogs for confirmations
Text - Typography
Image - Product images
Badge - Status indicators
SkeletonTextBlock - Loading states
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:
{
"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
Error Boundaries : Wrap components in error boundaries to prevent crashes
Loading States : Always show skeleton screens during data fetching
Optimistic Updates : Provide immediate feedback before API calls complete
Refetch Data : Refresh subscription data after mutations
Toast Notifications : Show success/error toasts for user actions
Testing
Test the extension locally:
This starts the development server and allows testing in a Shopify development store.
The extension requires customer authentication and active subscription contracts for testing.