Skip to main content
Subscriptions enable recurring revenue by automatically charging customers at regular intervals. Polar handles the complete subscription lifecycle including trials, upgrades, cancellations, and renewals.

Overview

Subscriptions in Polar:
  • Bill automatically at defined intervals (month, year, etc.)
  • Support trials with automatic conversion
  • Handle metered usage billing
  • Manage seat-based pricing
  • Apply discounts and credits
  • Grant and revoke benefits automatically

Subscription Lifecycle

1

Creation

Created via checkout or API (free products only). Status: incompletetrialing or active
2

Active

Subscription is active, benefits granted, billing occurs regularly.
3

Renewal

Automatic billing at period end. Creates new order.
4

Cancellation

Can be immediate (revoked) or at period end. Status: activecanceled
5

End

Access revoked, benefits removed. Status: canceledended

Creating Subscriptions

Most subscriptions are created through the checkout flow:
const checkout = await polar.checkouts.custom.create({
  products: [subscriptionProductId],
  customer_email: "[email protected]"
});

Programmatic Creation (Free Products Only)

Create free subscriptions directly via API:
const subscription = await polar.subscriptions.create({
  product_id: freeProductId,
  customer_id: customerId
});
Paid subscriptions must go through checkout to handle payment collection and tax calculation.

Subscription Status

Initial state. Payment setup in progress.

Updating Subscriptions

Subscriptions support several update operations:

Change Product

Upgrade or downgrade to a different product:
await polar.subscriptions.update(subscriptionId, {
  product_id: newProductId,
  proration_behavior: "create_prorations"  // or "always_invoice", "none"
});
  • create_prorations: Generate credits for unused time
  • always_invoice: Charge immediately for the difference
  • none: Change takes effect at next billing cycle
  • Organization default used if not specified

Apply Discount

Add or change discounts:
await polar.subscriptions.update(subscriptionId, {
  discount_id: discountId  // or null to remove
});
Discount changes take effect at the next billing cycle.

Extend or End Trial

Modify trial period:
// Extend trial
await polar.subscriptions.update(subscriptionId, {
  trial_end: "2024-12-31T23:59:59Z"  // ISO 8601 date
});

// End trial immediately
await polar.subscriptions.update(subscriptionId, {
  trial_end: "now"
});

Update Seats

For seat-based subscriptions:
await polar.subscriptions.update(subscriptionId, {
  seats: 15,
  proration_behavior: "create_prorations"
});

Adjust Billing Period

Change the next billing date:
await polar.subscriptions.update(subscriptionId, {
  current_billing_period_end: "2024-07-01T00:00:00Z"
});

Canceling Subscriptions

Cancel at Period End

Cancel but allow access until the current period ends:
await polar.subscriptions.update(subscriptionId, {
  cancel_at_period_end: true
});
The subscription:
  • Remains active until period end
  • Sets cancel_at_period_end: true
  • Won’t renew at period end
  • Benefits remain active until end

Revoke Immediately

Cancel and end subscription immediately:
await polar.subscriptions.revoke(subscriptionId);
The subscription:
  • Changes to canceled status immediately
  • Benefits revoked immediately
  • No refund issued
  • No further charges
Immediate revocation cannot be undone. Consider canceling at period end instead.

Subscription Billing

Billing Cycle

Subscriptions bill based on their interval:
{
  recurring_interval: "month",     // month, year
  recurring_interval_count: 1,     // every 1 month
  current_period_start: "2024-01-01T00:00:00Z",
  current_period_end: "2024-02-01T00:00:00Z"
}

Charge Preview

Preview the next charge before it occurs:
const preview = await polar.subscriptions.getChargePreview(subscriptionId);
Returns:
{
  subtotal_amount: 1900,    // Base subscription
  discount_amount: 190,     // Applied discounts
  tax_amount: 153,          // Calculated tax
  total_amount: 1863,       // Final charge
  currency: "usd",
  items: [
    {
      label: "Pro Plan",
      amount: 1900,
      proration: false
    },
    // Metered charges
    {
      label: "API Calls",
      amount: 500,
      proration: false
    }
  ]
}

Metered Billing

For subscriptions with metered prices:
const subscription = await polar.subscriptions.get(subscriptionId);

// Check current usage
subscription.meters.forEach(meter => {
  console.log(meter.meter.name);
  console.log('Consumed:', meter.consumed_units);
  console.log('Current charges:', meter.amount / 100);
});
See Usage-Based Billing for details.

Subscription Metadata

Store and update custom data:
await polar.subscriptions.update(subscriptionId, {
  metadata: {
    external_id: "sub_xyz",
    plan_tier: "premium",
    source: "website"
  }
});
Metadata is:
  • Included in all API responses
  • Sent in webhooks
  • Searchable via filters
  • Preserved across updates

Custom Field Data

Custom field values collected at checkout are accessible:
const subscription = await polar.subscriptions.get(subscriptionId);
console.log(subscription.custom_field_data);
// { [fieldId]: "customer response" }

Filtering Subscriptions

Query subscriptions with filters:
const { items } = await polar.subscriptions.list({
  organization_id: [orgId],
  product_id: [productId],
  customer_id: [customerId],
  active: true,                    // Only active subs
  cancel_at_period_end: false,     // Not set to cancel
  metadata: { plan: "enterprise" }
});

Exporting Subscriptions

Export subscription data as CSV:
const response = await fetch(
  'https://api.polar.sh/v1/subscriptions/export',
  {
    headers: {
      Authorization: `Bearer ${apiKey}`
    }
  }
);
const csv = await response.text();
Includes:
  • Customer email
  • Creation date
  • Active status
  • Product name
  • Price and currency
  • Billing interval

Webhooks

Listen for subscription events:

subscription.created

New subscription created

subscription.updated

Subscription modified

subscription.active

Subscription became active

subscription.canceled

Subscription canceled

subscription.revoked

Immediately revoked

subscription.ended

Subscription ended

Subscription Locking

Subscriptions are locked during updates to prevent race conditions:
try {
  await polar.subscriptions.update(subscriptionId, { seats: 10 });
} catch (error) {
  if (error.status === 409) {
    // Subscription is locked by another operation
    // Wait and retry
  }
}

Best Practices

  • Default to cancel at period end to maximize revenue
  • Only offer immediate revocation when explicitly requested
  • Track cancellation reasons via metadata
  • Use create_prorations for fair billing
  • Preview charges before making changes
  • Consider deferring downgrades to next billing cycle
  • Enable trials on products, not individual checkouts
  • Track trial redemptions to prevent abuse
  • Send notifications before trial ends
  • Check meter consumption regularly for metered subscriptions
  • Set up alerts for unusual usage patterns
  • Use cap amounts to prevent surprise charges
  • Listen for subscription.updated to track changes
  • Handle subscription.ended to revoke internal access
  • Use idempotency keys for webhook handlers

Common Patterns

Free Trial to Paid

// 1. Create subscription with trial via checkout
const checkout = await polar.checkouts.custom.create({
  products: [productId],
  trial_interval: "day",
  trial_interval_count: 14
});

// 2. Trial converts automatically when it ends
// Listen for subscription.active webhook

Self-Service Upgrades

// Customer upgrades from Basic to Pro
await polar.subscriptions.update(subscriptionId, {
  product_id: proProductId,
  proration_behavior: "create_prorations"
});

Pausing Subscriptions

// Set a future end date
await polar.subscriptions.update(subscriptionId, {
  ends_at: "2024-07-01T00:00:00Z"
});

// Resume by removing end date
await polar.subscriptions.update(subscriptionId, {
  ends_at: null
});

API Reference

List Subscriptions

Query subscriptions with filters

Get Subscription

Retrieve subscription details

Create Subscription

Create free subscription

Update Subscription

Modify subscription

Revoke Subscription

Cancel immediately

Charge Preview

Preview next charge

Build docs developers (and LLMs) love