Convex Polar Component 
Add subscriptions and billing to your Convex app with Polar.
// Add subscriptions to your app
const user = useQuery(api.example.getCurrentUser);
// Show available plans
<CheckoutLink
polarApi={api.example}
productIds={[products.premiumMonthly.id, products.premiumYearly.id]}
>
Upgrade to Premium
</CheckoutLink>
// Manage existing subscriptions
<CustomerPortalLink polarApi={api.example}>
Manage Subscription
</CustomerPortalLink>
Check out the example app for a complete example.
Prerequisites
Polar Account
- Create a Polar account
- Create an organization and generate an organization token with permissions:
products:read,products:write,subscriptions:read,subscriptions:write,customers:read,customers:write,checkouts:read,checkouts:write,checkout_links:read,checkout_links:write,customer_portal:read,customer_portal:write,customer_sessions:write
- Create a product in the Polar dashboard for each pricing plan that you want to offer
Note: You can have one price per plan, so a plan with monthly and yearly pricing requires two products in Polar.
Note: The Convex Polar component is currently built to support recurring subscriptions, and may not work as expected with one-time payments. Please open an issue or reach out on Discord if you run into any issues.
Convex App
You'll need a Convex App to use the component. Follow any of the Convex quickstarts to set one up.
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";
const app = defineApp();
app.use(polar);
export default app;
Set your Polar organization token:
npx convex env set POLAR_ORGANIZATION_TOKEN xxxxx
Usage
1. 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(components.polar, {
// Optional: Configure static keys for referencing your products
// Map your product keys to Polar product IDs (you can also use env vars for this)
// Replace these with whatever your products are (eg., "pro", "pro_monthly", whatever you want)
products: {
premiumMonthly: "product_id_from_polar",
premiumYearly: "product_id_from_polar",
premiumPlusMonthly: "product_id_from_polar",
premiumPlusYearly: "product_id_from_polar",
},
// Provide a function the component can use to get the current user's ID and email
getUserInfo: async (ctx) => {
const user = await ctx.runQuery(api.example.getCurrentUser);
return {
userId: user._id,
email: user.email,
};
},
// Optional: Configure Polar settings directly in code
// organizationToken: "your_organization_token", // Optional: Falls back to POLAR_ORGANIZATION_TOKEN env var
// webhookSecret: "your_webhook_secret", // Optional: Falls back to POLAR_WEBHOOK_SECRET env var
// server: "sandbox", // Optional: "sandbox" or "production", falls back to POLAR_SERVER env var
});
// Export the API functions
export const {
changeCurrentSubscription,
cancelCurrentSubscription,
getProducts,
} = polar.api();
export const {
generateCheckoutLink,
generateCustomerPortalUrl,
} = polar.checkoutApi();
export const _ = query(
2. Set up 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.
- 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;
3. Display products and prices
Use the exported getProducts function to display your products and their prices:
// React component
const products = useQuery(api.example.getProducts);
// Simple example of displaying products and prices
function PricingTable() {
const products = useQuery(api.example.getProducts);
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>
);
}
Each product includes:
id: The Polar product IDname: The product nameprices: Array of prices with:priceAmount: Price in centspriceCurrency: Currency code (e.g., "USD")recurringInterval: "month" or "year"
4. 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
polarApi={{
generateCheckoutLink: api.example.generateCheckoutLink,
}}
productIds={[products.premiumMonthly.id, products.premiumYearly.id]}
>
Upgrade to Premium
</CheckoutLink>
// For managing existing subscriptions
<CustomerPortalLink
polarApi={{
generateCustomerPortalUrl: api.example.generateCustomerPortalUrl,
}}
>
Manage Subscription
</CustomerPortalLink>
5. 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
const cancelSubscription = useAction(api.example.cancelCurrentSubscription);
await cancelSubscription({ revokeImmediately: true });
6. 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",
};
},
});
Example App Features
The example app demonstrates:
- Free and paid subscription tiers
- Monthly and yearly billing options
- Upgrade/downgrade between plans
- Subscription management portal
- Usage limits based on subscription tier
- Prorated billing for plan changes
API Reference
Polar Client
The Polar class accepts a configuration object with:
products: Map of product keys to Polar product IDsgetUserInfo: Function to get the current user's ID and emailorganizationToken: (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 checkoutclassName: Optional CSS class namechildren: React children (button content)
CustomerPortalLink
Props:
polarApi: Object containinggenerateCustomerPortalUrlfunctionclassName: Optional CSS class namechildren: React children (button content)
API Functions
changeCurrentSubscription
Change an existing subscription to a new plan:
await changeSubscription({ productId: "new_product_id" });
cancelCurrentSubscription
Cancel an existing subscription:
await cancelSubscription({ revokeImmediately: true });
getCurrentSubscription
Get the current user's subscription details:
const subscription = await polar.getCurrentSubscription(ctx, { userId });
getProducts
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",
});
The webhook handler uses the webhookSecret from the Polar client configuration or the POLAR_WEBHOOK_SECRET environment variable.