Skip to main content
The SubscriptionContract model provides methods for querying and managing subscription contracts throughout their lifecycle, from creation through billing and fulfillment.

Overview

Subscription contracts represent active subscriptions between customers and merchants. This model handles:
  • Retrieving subscription contract lists and details
  • Accessing billing attempt information
  • Calculating price breakdowns
  • Managing contract lifecycle operations
  • Fetching contract data for rebilling

Core Methods

getContracts

Retrieves a paginated list of subscription contracts.
graphql
GraphQLClient
required
The authenticated GraphQL client instance
variables
SubscriptionContractsQueryVariables
required
Query parameters including filters and pagination
Returns: Promise<{subscriptionContracts: SubscriptionContractListItem[], subscriptionContractPageInfo: PaginationInfo, hasContractsWithInventoryError: boolean}> TypeScript Signature:
async function getContracts(
  graphql: GraphQLClient,
  variables: SubscriptionContractsQueryVariables,
): Promise<{
  subscriptionContracts: SubscriptionContractListItem[];
  subscriptionContractPageInfo: PaginationInfo;
  hasContractsWithInventoryError: boolean;
}>
Usage Example:
import { getContracts } from '~/models/SubscriptionContract/SubscriptionContract.server';

const { 
  subscriptionContracts, 
  subscriptionContractPageInfo,
  hasContractsWithInventoryError 
} = await getContracts(graphql, {
  first: 50,
  query: 'status:ACTIVE',
});

console.log(`Found ${subscriptionContracts.length} active contracts`);
subscriptionContracts.forEach(contract => {
  console.log(`Contract ${contract.id}`);
  console.log(`Customer: ${contract.customer.displayName}`);
  console.log(`Status: ${contract.status}`);
  console.log(`Total: ${contract.totalPrice.amount} ${contract.totalPrice.currencyCode}`);
});

if (hasContractsWithInventoryError) {
  console.warn('Some contracts have inventory errors');
}
GraphQL Query Used:
query SubscriptionContracts($first: Int, $after: String, $query: String) {
  subscriptionContracts(first: $first, after: $after, query: $query) {
    edges {
      node {
        id
        status
        currencyCode
        customer {
          displayName
        }
        deliveryPolicy {
          interval
          intervalCount
        }
        lines(first: 50) {
          edges {
            node {
              id
              title
              variantTitle
              quantity
              productId
              lineDiscountedPrice {
                amount
              }
            }
          }
        }
        linesCount {
          count
        }
        billingAttempts(first: 10, reverse: true) {
          edges {
            node {
              id
              errorCode
              errorMessage
            }
          }
        }
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
  contractsWithInventoryError: subscriptionContracts(
    first: 1
    query: "status:ACTIVE AND billingAttempt.errorCode:INVENTORY_ALLOCATIONS_NOT_FOUND"
  ) {
    edges {
      node {
        id
      }
    }
  }
}

getContractDetails

Retrieves comprehensive details for a single subscription contract.
graphql
GraphQLClient
required
The authenticated GraphQL client instance
id
string
required
The subscription contract ID (e.g., gid://shopify/SubscriptionContract/123)
Returns: Promise<SubscriptionContractDetails> TypeScript Signature:
async function getContractDetails(
  graphql: GraphQLClient,
  id: string,
): Promise<SubscriptionContractDetails>
Usage Example:
import { getContractDetails } from '~/models/SubscriptionContract/SubscriptionContract.server';

const contract = await getContractDetails(
  graphql,
  'gid://shopify/SubscriptionContract/1'
);

console.log('Contract Details:');
console.log('- Customer:', contract.customer?.displayName);
console.log('- Email:', contract.customer?.email);
console.log('- Status:', contract.status);
console.log('- Delivery Method:', contract.deliveryMethod?.name);
console.log('- Lines:', contract.lines.length);
console.log('- Subtotal:', contract.priceBreakdownEstimate.subtotalPrice.amount);
console.log('- Shipping:', contract.priceBreakdownEstimate.totalShippingPrice.amount);

// Access customer addresses
contract.customer?.addresses.forEach(({ id, address }) => {
  console.log(`Address ${id}: ${address}`);
});

// Check billing attempts
console.log('Recent billing attempts:', contract.billingAttempts.length);

getContractEditDetails

Retrieves contract details optimized for editing operations.
graphql
GraphQLClient
required
The authenticated GraphQL client instance
id
string
required
The subscription contract ID
Returns: Promise<SubscriptionContractEditDetails> TypeScript Signature:
async function getContractEditDetails(
  graphql: GraphQLClient,
  id: string,
): Promise<SubscriptionContractEditDetails>
Usage Example:
import { getContractEditDetails } from '~/models/SubscriptionContract/SubscriptionContract.server';

const contract = await getContractEditDetails(
  graphql,
  'gid://shopify/SubscriptionContract/1'
);

// Each line includes current one-time purchase price for comparison
contract.lines.forEach(line => {
  console.log(`${line.title}:`);
  console.log(`- Subscription price: ${line.currentPrice.amount}`);
  console.log(`- One-time price: ${line.currentOneTimePurchasePrice}`);
});

console.log('Delivery Policy:', contract.deliveryPolicy);
console.log('Price Breakdown:', contract.priceBreakdownEstimate);
Note: This method fetches the current one-time purchase price for each product variant, which is useful for displaying savings when editing a contract.

getContractDetailsForRebilling

Retrieves contract details specifically for rebilling operations.
graphql
GraphQLClient
required
The authenticated GraphQL client instance
id
string
required
The subscription contract ID
Returns: Promise<SubscriptionContract> TypeScript Signature:
async function getContractDetailsForRebilling(
  graphql: GraphQLClient,
  id: string,
): Promise<SubscriptionContract>
Usage Example:
import { getContractDetailsForRebilling } from '~/models/SubscriptionContract/SubscriptionContract.server';

try {
  const contract = await getContractDetailsForRebilling(
    graphql,
    'gid://shopify/SubscriptionContract/1'
  );
  
  console.log('Contract ready for rebilling');
  console.log('Customer ID:', contract.customer?.id);
  console.log('Next billing date:', contract.nextBillingDate);
} catch (error) {
  console.error('Failed to load contract for rebilling:', error);
}

findSubscriptionContractWithBillingCycle

Finds a subscription contract along with a specific billing cycle.
shop
string
required
The shop domain (e.g., example.myshopify.com)
contractId
string
required
The subscription contract ID
date
string
required
The billing cycle date in ISO format (e.g., 2024-03-15)
Returns: Promise<{subscriptionContract: SubscriptionContract, subscriptionBillingCycle: SubscriptionBillingCycle}> TypeScript Signature:
interface FindSubscriptionContractWithBillingCycleArgs {
  shop: string;
  contractId: string;
  date: string;
}

async function findSubscriptionContractWithBillingCycle({
  shop,
  contractId,
  date,
}: FindSubscriptionContractWithBillingCycleArgs): Promise<{
  subscriptionContract: SubscriptionContract;
  subscriptionBillingCycle: SubscriptionBillingCycle;
}>
Usage Example:
import { findSubscriptionContractWithBillingCycle } from '~/models/SubscriptionContract/SubscriptionContract.server';

const { subscriptionContract, subscriptionBillingCycle } = 
  await findSubscriptionContractWithBillingCycle({
    shop: 'example.myshopify.com',
    contractId: 'gid://shopify/SubscriptionContract/1',
    date: '2024-03-15',
  });

console.log('Contract:', subscriptionContract.id);
console.log('Billing Cycle Index:', subscriptionBillingCycle.cycleIndex);
console.log('Billing Attempts:', subscriptionBillingCycle.billingAttempts.edges.length);

// Access billing attempt details
subscriptionBillingCycle.billingAttempts.edges.forEach(({ node }) => {
  console.log(`Attempt ${node.id}:`);
  console.log(`- Error Code: ${node.errorCode}`);
  console.log(`- Ready: ${node.ready}`);
});
GraphQL Query Used:
query SubscriptionContractWithBillingCycle($contractId: ID!, $date: DateTime!) {
  subscriptionContract(id: $contractId) {
    id
    status
    customer {
      id
      displayName
    }
  }
  subscriptionBillingCycle(contractId: $contractId, date: $date) {
    cycleIndex
    billingAttempts(first: 10) {
      edges {
        node {
          id
          ready
          errorCode
          errorMessage
        }
      }
    }
  }
}

getContractCustomerId

Retrieves the customer ID associated with a subscription contract.
shopDomain
string
required
The shop domain
subscriptionContractId
string
required
The subscription contract ID
Returns: Promise<string> TypeScript Signature:
async function getContractCustomerId(
  shopDomain: string,
  subscriptionContractId: string,
): Promise<string>
Usage Example:
import { getContractCustomerId } from '~/models/SubscriptionContract/SubscriptionContract.server';

const customerId = await getContractCustomerId(
  'example.myshopify.com',
  'gid://shopify/SubscriptionContract/1'
);

console.log('Customer ID:', customerId);
// Output: gid://shopify/Customer/123

getProductVariantPrice

Retrieves the current price for a product variant.
graphql
GraphQLClient
required
The authenticated GraphQL client instance
productVariantId
string
required
The product variant ID
Returns: Promise<number> TypeScript Signature:
async function getProductVariantPrice(
  graphql: GraphQLClient,
  productVariantId: string,
): Promise<number>
Usage Example:
import { getProductVariantPrice } from '~/models/SubscriptionContract/SubscriptionContract.server';

const price = await getProductVariantPrice(
  graphql,
  'gid://shopify/ProductVariant/123'
);

console.log('Current variant price:', price);
// Output: 29.99

TypeScript Interfaces

SubscriptionContractListItem

interface SubscriptionContractListItem {
  id: string;
  status: SubscriptionContractStatusEnum;
  totalPrice: MoneyV2;
  customer: {
    displayName?: string | null;
  };
  deliveryPolicy?: {
    interval: string;
    intervalCount: number;
  } | null;
  billingAttempts?: {
    id: string;
    errorCode?: string | null;
    processingError?: string | null;
  }[];
  lines: SubscriptionLineWithProductId[];
  lineCount: number;
}

SubscriptionContractDetails

interface SubscriptionContractDetails {
  id: string;
  status: SubscriptionContractStatusEnum;
  customer?: {
    id: string;
    displayName?: string | null;
    email?: string | null;
    addresses: FormattedAddressWithId[];
  };
  deliveryMethod?: {
    __typename: string;
    address?: {
      address1?: string | null;
      address2?: string | null;
      city?: string | null;
      province?: string | null;
      country?: string | null;
      zip?: string | null;
    } | null;
    name: string;
    isLocalPickup: boolean;
  };
  lines: SubscriptionLine[];
  billingAttempts: BillingAttempt[];
  lastPaymentStatus: SubscriptionContractLastPaymentStatusEnum | null;
  priceBreakdownEstimate: {
    subtotalPrice: MoneyV2;
    totalShippingPrice: MoneyV2;
  };
}

SubscriptionContractEditDetails

interface SubscriptionContractEditDetails {
  id: string;
  status: SubscriptionContractStatusEnum;
  lines: (SubscriptionLine & {
    currentOneTimePurchasePrice?: number;
  })[];
  deliveryMethod?: {
    name: string;
    isLocalPickup: boolean;
  };
  deliveryPolicy: {
    intervalCount: number;
    interval: string;
  };
  priceBreakdownEstimate: {
    subtotalPrice: MoneyV2;
    totalShippingPrice: MoneyV2;
  };
}

MoneyV2

interface MoneyV2 {
  amount: number;
  currencyCode: string;
}

FormattedAddressWithId

interface FormattedAddressWithId {
  id: string;
  address: string; // Formatted address string
}

SubscriptionContractStatusEnum

enum SubscriptionContractStatusEnum {
  ACTIVE = 'ACTIVE',
  PAUSED = 'PAUSED',
  CANCELLED = 'CANCELLED',
  EXPIRED = 'EXPIRED',
  FAILED = 'FAILED',
}

Helper Functions

getContractPriceBreakdown

Internal helper function that calculates price breakdowns for contracts.
function getContractPriceBreakdown({
  currencyCode,
  lines,
  discounts = [],
  deliveryPrice = { amount: 0 },
}: {
  currencyCode: string;
  lines: { lineDiscountedPrice: { amount: number } }[];
  discounts?: { targetType: string }[];
  deliveryPrice?: { amount: number };
}): {
  subtotalPrice: MoneyV2;
  totalShippingPrice: MoneyV2;
}
This function:
  • Sums all line item prices to calculate the subtotal
  • Checks if shipping discounts are applied
  • Returns formatted price objects with currency codes

Build docs developers (and LLMs) love