Polar
Subscriptions and billing
git clone https://github.com/bijectionhq/polar.git
cd polar
git checkout 29621ba52e66074082bf2c5ada013c1e3023fd78Bijection repository access is required. Request access.
Description
Subscriptions and billing
Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.
Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.
Getting started
- Get the source at the revision above and read its package documentation.
- Check the required Bijection SDK version, dependencies, and external service credentials.
- Follow the package setup instructions and the guide to using components.
- Exercise the component in your deployment according to your project’s verification rules.
Open source
Maintained source: bijectionhq/polar. Based on convex/polar. License: Apache-2.0. Consult the repository for license terms, notices, and contributor attribution.
From the upstream publisher
A Polar component for Convex.
Explore the demo ↗Upstream overview
From the upstream package documentation.
Integrates Polar subscriptions and billing into Convex applications with automatic webhook synchronization and React UI components. Provides type-safe subscription management, product syncing, and customer portal integration. Handles all Polar price types including fixed, custom pay-what-you-want, seat-based, and metered pricing models.
Use cases
From the upstream package documentation.
- Add subscription billing to SaaS apps - Configure products with keys like
premiumMonthlyandpremiumYearly, then useCheckoutLinkcomponents for user upgrades - Implement tiered pricing with trials - Support free trials, multiple subscription tiers, and seat-based pricing for team plans
- Build customer self-service portals - Let users manage subscriptions, update payment methods, and view billing history with
CustomerPortalLink - Handle subscription lifecycle events - Process webhook events like
subscription.updatedandproduct.createdwith type-safe handlers - Migrate from other billing providers - Replace existing subscription logic while keeping product data synced between Polar and Convex
How it works
From the upstream package documentation.
The component creates a Polar client in your Convex backend that syncs product and subscription data via webhooks. You configure it with a getUserInfo function that maps your user system to Polar customers, and optionally define product keys for easy reference. The webhook handler at /polar/events keeps subscription data current by processing events like subscription.created and product.updated.
React components like CheckoutLink and CustomerPortalLink generate Polar URLs on-demand through Convex actions. The CheckoutLink supports embedded checkout or redirect modes, while CustomerPortalLink provides subscription management. Functions like getCurrentSubscription and changeCurrentSubscription handle subscription queries and modifications.
Product configuration supports static mapping (products: { premiumMonthly: 'product_id' }) or dynamic listing with listAllProducts. The component handles all Polar price types automatically, exposing different fields based on amountType - from simple priceAmount for fixed pricing to complex seatTiers for seat-based models.
Upstream package FAQ
How does the Polar component handle webhook verification in Convex?
The @convex-dev/polar component includes built-in webhook signature verification using Polar's signing keys. It automatically validates incoming webhooks and processes verified events through your Convex functions, ensuring secure handling of billing data.
What subscription data does this component store in Convex?
The Polar component stores customer profiles, subscription status, billing cycles, and payment events in your Convex database. This data is accessible through standard Convex queries, allowing you to build subscription-gated features and billing dashboards directly in your app.
Can I customize how billing events are processed?
Yes, the @convex-dev/polar component allows you to define custom handlers for different Polar webhook events. You can add business logic to respond to subscription changes, failed payments, or cancellations using standard Convex mutations and actions.
Does this component work with Polar's subscription plans?
The @convex-dev/polar component supports all Polar subscription features including multiple plans, usage-based billing, and plan changes. It automatically syncs subscription plan data and billing cycles with your Convex database for real-time subscription management.
Use with agents and CLI
Give your coding agent the component’s source identity and setup guidance.
Help me integrate Polar with Bijection.
Read: https://bijection.com/components/polar/SKILL.md
Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.Upstream README.md
View repository ↗Documentation from convex/polar, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.
Convex Polar Component 
Add subscriptions and billing to your Convex app with Polar.
Check out the example app for a complete example.
// Get subscription details for the current user
// Note: getCurrentSubscription is for apps that only allow one active
// subscription per user. If you need to support multiple active
// subscriptions, use listUserSubscriptions instead.
const {
productKey,
status,
currentPeriodEnd,
currentPeriodStart,
...
} = await polar.getCurrentSubscription(ctx, {
userId: user._id,
});
// Show available plans
<CheckoutLink
polarApi={api.example}
productIds={[products.premiumMonthly.id, products.premiumYearly.id]}
// Optional: turn off embedding to link to a checkout page
embed={false}
>
Upgrade to Premium
</CheckoutLink>
// Manage existing subscriptions
<CustomerPortalLink polarApi={api.example}>
Manage Subscription
</CustomerPortalLink>
Prerequisites
Convex App
You'll need a Convex App to use the component. Follow any of the Convex quickstarts to set one up.
Polar Account
- Create a Polar account
- Create an organization and generate an organization token with permissions:
products:readproducts:writesubscriptions:readsubscriptions:writecustomers:readcustomers:writecheckouts:readcheckouts:writecheckout_links:readcheckout_links:writecustomer_portal:readcustomer_portal:writecustomer_sessions:write
Installation
Install the component package:
npm install @convex-dev/polar
Create a convex.config.ts file in your app's convex/ folder and install the
component by calling app.use:
// convex/convex.config.ts
import { defineApp } from "convex/server";
import polar from "@convex-dev/polar/convex.config.js";
const app = defineApp();
app.use(polar);
export default app;
Set your Polar organization token:
npx convex env set POLAR_ORGANIZATION_TOKEN xxxxx
Usage
Set up Polar webhooks
The Polar component uses webhooks to keep subscription data in sync. You'll need to:
-
Create a webhook and webhook secret in the Polar dashboard, using your Convex site URL +
/polar/eventsas the webhook endpoint. It should look like this:https://verb-noun-123.convex.site/polar/eventsEnable the following events:
product.createdproduct.updatedsubscription.createdsubscription.updated
-
Set the webhook secret in your Convex environment:
npx convex env set POLAR_WEBHOOK_SECRET xxxxx
- Register the webhook handler in your
convex/http.ts:
import { httpRouter } from "convex/server";
import { polar } from "./example";
const http = httpRouter();
// Register the webhook handler at /polar/events
polar.registerRoutes(http as any);
export default http;
You can also provide typesafe handlers for any Polar webhook event via the
events option. Handlers receive the fully typed event payload based on the
event key:
polar.registerRoutes(http, {
// Optional custom path, default is "/polar/events"
path: "/polar/events",
events: {
"subscription.updated": async (ctx, event) => {
// event.data is typed as Subscription
if (event.data.customerCancellationReason) {
console.log("Customer cancelled:", event.data.customerCancellationReason);
}
},
"order.created": async (ctx, event) => {
// event.data is typed as Order
console.log("New order:", event.data.id);
},
},
});
- Be sure to run
npx convex devto start your Convex app with the Polar component enabled, which will deploy the webhook handler to your Convex instance.
Create products in Polar
Create a product in the Polar dashboard for each pricing plan that you want to offer. The product data will be synced to your Convex app automatically.
The component supports all Polar price types: fixed, free, custom (pay-what-you-want), seat-based, and metered. Products can also include benefits and free trial periods configured in the Polar dashboard or at checkout time.
Products created prior to using this component need to be synced with Convex
using the syncProducts function.
Initialize the Polar client
Create a Polar client in your Convex backend:
// convex/example.ts
import { Polar } from "@convex-dev/polar";
import { api, components } from "./_generated/api";
import { DataModel } from "./_generated/dataModel";
export const polar = new Polar<DataModel>(components.polar, {
// Required: provide a function the component can use to get the current user's ID and
// email - this will be used for retrieving the correct subscription data for the
// current user. The function should return an object with `userId` and `email`
// properties.
getUserInfo: async (ctx) => {
const user = await ctx.runQuery(api.example.getCurrentUser);
return {
userId: user._id,
email: user.email,
};
},
// Optional: Configure static keys for referencing your products.
// Alternatively you can use the `listAllProducts` function to get
// the product data and sort it out in your UI however you like
// (eg., by price, name, recurrence, etc.).
// Map your product keys to Polar product IDs (you can also use env vars for this)
// Replace these keys with whatever is useful for your app (eg., "pro", "proMonthly",
// whatever you want), and replace the values with the actual product IDs from your
// Polar dashboard
products: {
premiumMonthly: "product_id_from_polar",
premiumYearly: "product_id_from_polar",
premiumPlusMonthly: "product_id_from_polar",
premiumPlusYearly: "product_id_from_polar",
},
// Optional: Set Polar configuration directly in code
organizationToken: "your_organization_token", // Defaults to POLAR_ORGANIZATION_TOKEN env var
webhookSecret: "your_webhook_secret", // Defaults to POLAR_WEBHOOK_SECRET env var
server: "sandbox", // Optional: "sandbox" or "production", defaults to POLAR_SERVER env var
});
// Export API functions from the Polar client
export const {
changeCurrentSubscription,
cancelCurrentSubscription,
getConfiguredProducts,
listAllProducts,
listAllSubscriptions,
generateCheckoutLink,
generateCustomerPortalUrl,
} = polar.api();
Display products and prices
Use the exported getConfiguredProducts or listAllProducts function to display
your products and their prices:
getConfiguredProducts
// Simple example of displaying products and prices if you've configured
// products by key in the Polar constructor
function PricingTable() {
const products = useQuery(api.example.getConfiguredProducts);
if (!products) return null;
return (
<div>
{products.premiumMonthly && (
<div>
<h3>{products.premiumMonthly.name}</h3>
<p>
${(products.premiumMonthly.prices[0].priceAmount ?? 0) / 100}/month
</p>
</div>
)}
{products.premiumYearly && (
<div>
<h3>{products.premiumYearly.name}</h3>
<p>
${(products.premiumYearly.prices[0].priceAmount ?? 0) / 100}/year
</p>
</div>
)}
</div>
);
}
listAllProducts
// Simple example of displaying products and prices if you haven't configured
// products by key in the Polar constructor
function PricingTable() {
const products = useQuery(api.example.listAllProducts);
if (!products) return null;
// You can sort through products in the client as below, or you can use
// `polar.listAllProducts` in your own Convex query and return your desired
// products to display in the UI.
const proMonthly = products.find(
(p) => p.prices[0].recurringInterval === "month",
);
const proYearly = products.find(
(p) => p.prices[0].recurringInterval === "year",
);
return (
<div>
{proMonthly && (
<div>
<h3>{proMonthly.name}</h3>
<p>
${(proMonthly.prices[0].priceAmount ?? 0) / 100}/
{proMonthly.prices[0].recurringInterval}
</p>
</div>
)}
{proYearly && (
<div>
<h3>{proYearly.name}</h3>
<p>${(proYearly.prices[0].priceAmount ?? 0) / 100}/year</p>
</div>
)}
</div>
);
}
Each product includes:
id: The Polar product IDname: The product namedescription: Product descriptionisRecurring: Whether the product is a subscriptionrecurringInterval: Billing interval (e.g., "month", "year")trialInterval: Trial period interval (if configured)trialIntervalCount: Number of trial intervals (if configured)benefits: Array of product benefits (description, type, etc.)prices: Array of prices, each with anamountTypethat determines available fields:- fixed:
priceAmount(in cents),priceCurrency - free: No amount fields
- custom:
minimumAmount,maximumAmount,presetAmount,priceCurrency - seat_based:
seatTiers(array of{ minSeats, maxSeats, pricePerSeat }),priceCurrency - metered_unit:
unitAmount,capAmount,meterId,meter({ id, name }),priceCurrency
- fixed:
Add subscription UI components
Use the provided React components to add subscription functionality to your app:
import { CheckoutLink, CustomerPortalLink } from "@convex-dev/polar/react";
import { api } from "../convex/_generated/api";
// For new subscriptions
<CheckoutLink
// For our example, the api.example object includes the generateCheckoutLink
// function. You can also pass any object that includes this function.
polarApi={api.example}
productIds={[products.premiumMonthly.id, products.premiumYearly.id]}
// Optional: turn off embedding to link to a checkout page
embed={false}
>
Upgrade to Premium
</CheckoutLink>
// With a free trial
<CheckoutLink
polarApi={api.example}
productIds={[products.premiumMonthly.id]}
trialInterval="day"
trialIntervalCount={7}
>
Start 7-day free trial
</CheckoutLink>
// Lazy mode: generates checkout URL on click instead of on mount.
// Useful when rendering many checkout links to avoid rate limits.
<CheckoutLink
polarApi={api.example}
productIds={[products.premiumMonthly.id]}
lazy
>
Subscribe
</CheckoutLink>
// For managing existing subscriptions
<CustomerPortalLink
polarApi={{
generateCustomerPortalUrl: api.example.generateCustomerPortalUrl,
}}
>
Manage Subscription
</CustomerPortalLink>
Handle subscription changes
The Polar component provides functions to handle subscription changes for the current user.
Note: It is highly recommended to prompt the user for confirmation before changing their subscription this way!
// Change subscription
const changeSubscription = useAction(api.example.changeCurrentSubscription);
await changeSubscription({ productId: "new_product_id" });
// Cancel subscription (works for both active and trialing subscriptions)
const cancelSubscription = useAction(api.example.cancelCurrentSubscription);
await cancelSubscription({ revokeImmediately: true });
Access subscription data
Query subscription information in your app:
// convex/example.ts
// A query that returns a user with their subscription details
export const getCurrentUser = query({
handler: async (ctx) => {
const user = await ctx.db.query("users").first();
if (!user) throw new Error("No user found");
const subscription = await polar.getCurrentSubscription(ctx, {
userId: user._id,
});
return {
...user,
subscription,
isFree: !subscription,
isPremium:
subscription?.productKey === "premiumMonthly" ||
subscription?.productKey === "premiumYearly",
};
},
});
API Reference
Polar Client
The Polar class accepts a configuration object with:
getUserInfo: Function to get the current user's ID and emailproducts: (Optional) Map of arbitrarily named keys to Polar product IDsorganizationToken: (Optional) Your Polar organization token. Falls back toPOLAR_ORGANIZATION_TOKENenv varwebhookSecret: (Optional) Your Polar webhook secret. Falls back toPOLAR_WEBHOOK_SECRETenv varserver: (Optional) Polar server environment: "sandbox" or "production". Falls back toPOLAR_SERVERenv var
React Components
CheckoutLink
Props:
polarApi: Object containinggenerateCheckoutLinkfunctionproductIds: Array of product IDs to show in the checkoutchildren: React children (button content)embed: (Optional) Whether to embed the checkout. Defaults totrue.lazy: (Optional) Defer checkout URL generation until click. Useful when rendering many links. Defaults tofalse.theme: (Optional) Embedded checkout theme:"dark"or"light". Defaults to"dark".trialInterval: (Optional) Trial period interval:"day","week","month", or"year".trialIntervalCount: (Optional) Number of trial intervals (e.g.,7for 7 days).className: (Optional) CSS class namesubscriptionId: (Optional) ID of a subscription to upgrade. It must be on a free pricing.metadata: (Optional) Metadata for additional information to the checkoutlocale: (Optional) Locale for the checkout page (e.g.,"fr","de")
CustomerPortalLink
Props:
polarApi: Object containinggenerateCustomerPortalUrlfunctionchildren: React children (button content)className: (Optional) CSS class name
API Functions
The functions below are class methods on the Polar instance, used in your
Convex backend code. The corresponding React-facing names (exported via
polar.api()) are shown in the initialization section
above.
changeCurrentSubscription
Change an existing subscription to a new plan:
await changeSubscription({ productId: "new_product_id" });
cancelCurrentSubscription
Cancel an active or trialing subscription:
await cancelSubscription({ revokeImmediately: true });
getCurrentSubscription
Get the current user's active subscription. Returns null if no active
subscription exists. Expired trials are excluded.
const subscription = await polar.getCurrentSubscription(ctx, { userId });
listUserSubscriptions
For apps that support multiple active subscriptions per user. Returns active subscriptions, excluding ended subscriptions and expired trials.
const subscriptions = await polar.listUserSubscriptions(ctx, { userId });
listAllUserSubscriptions
Returns all subscriptions for a user, including ended and expired trials. Useful for displaying subscription history or distinguishing between "never subscribed" and "trial expired".
const allSubscriptions = await polar.listAllUserSubscriptions(ctx, { userId });
getProduct
Get a single product by its Polar product ID:
const product = await polar.getProduct(ctx, { productId });
getCustomerByUserId
Get the Polar customer record for a user:
const customer = await polar.getCustomerByUserId(ctx, userId);
listProducts
List all available products and their prices:
const products = await polar.listProducts(ctx);
registerRoutes
Register webhook handlers for the Polar component:
polar.registerRoutes(http, {
// Optional: customize the webhook endpoint path (defaults to "/polar/events")
path: "/custom/webhook/path",
// Optional: typesafe handlers for any Polar webhook event
events: {
"order.created": async (ctx, event) => {
// event.data is typed as Order
},
},
});
The events option accepts handlers for any of the 30+ Polar webhook event
types with full TypeScript inference on the event payload. Built-in handling
(persisting subscriptions and products) always runs automatically regardless
of which events you handle.
The webhook handler uses the webhookSecret from the Polar client configuration
or the POLAR_WEBHOOK_SECRET environment variable.
syncProducts
Sync existing products from Polar (must be run inside an action):
export const syncProducts = action({
args: {},
handler: async (ctx) => {
await polar.syncProducts(ctx);
},
});