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
Creation
Created via checkout or API (free products only).
Status: incomplete → trialing or active
Active
Subscription is active, benefits granted, billing occurs regularly.
Renewal
Automatic billing at period end. Creates new order.
Cancellation
Can be immediate (revoked) or at period end.
Status: active → canceled
End
Access revoked, benefits removed.
Status: canceled → ended
Creating Subscriptions
Via Checkout (Recommended)
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
incomplete
trialing
active
canceled
past_due
unpaid
ended
Initial state. Payment setup in progress.
In trial period. No charges yet. Benefits granted.
Subscription active and billing. Benefits granted.
Canceled but still active until period end.
Payment failed. Retrying automatic collection.
Payment failed after retries. Benefits revoked.
Subscription permanently ended. Benefits revoked.
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
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.
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