### Install Dependencies and Run Locally
Source: https://github.com/get-convex/polar/blob/main/CONTRIBUTING.md
Installs project dependencies and starts the local development server.
```sh
npm i
npm run dev
```
--------------------------------
### Build One-Off Package
Source: https://github.com/get-convex/polar/blob/main/CONTRIBUTING.md
Cleans the project, installs dependencies, and creates a package for distribution.
```sh
npm run clean
npm ci
npm pack
```
--------------------------------
### CheckoutLink with Free Trial Configuration
Source: https://github.com/get-convex/polar/blob/main/_autodocs/02-react-components.md
Configures a free trial for a product by specifying `trialInterval` and `trialIntervalCount`. This example sets up a 7-day free trial.
```typescript
Start 7-Day Free Trial
```
--------------------------------
### Install Convex Polar
Source: https://github.com/get-convex/polar/blob/main/_autodocs/README.md
Install the Convex Polar package using npm.
```bash
npm install @convex-dev/polar
```
--------------------------------
### Basic Convex App Setup with Polar
Source: https://github.com/get-convex/polar/blob/main/_autodocs/README.md
Configure your Convex application to use the Polar component by importing and using it in your `convex.config.ts` file.
```typescript
// 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;
```
--------------------------------
### Example Product IDs Configuration
Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md
Define product IDs for different pricing tiers. Ensure these IDs match the ones created in your Polar account.
```typescript
products: {
starter: "prod_starter_monthly",
pro: "prod_pro_monthly",
proYearly: "prod_pro_yearly",
enterprise: "prod_enterprise",
}
```
--------------------------------
### Run listUserSubscriptions Query
Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md
Example of how to retrieve a list of all active subscriptions for a user. This query returns an array of subscription objects, each potentially including product information.
```typescript
const subscriptions = await ctx.runQuery(api.lib.listUserSubscriptions, {
userId: user._id
});
```
--------------------------------
### Install Polar Dependencies
Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md
Install the necessary Polar packages for your Convex project using npm.
```bash
npm install @convex-dev/polar @polar-sh/sdk @polar-sh/checkout
```
--------------------------------
### Run getCustomerByUserId Query
Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md
Example of how to execute the `getCustomerByUserId` query. This requires the `userId` of the user whose customer record is to be fetched.
```typescript
const customer = await ctx.runQuery(api.lib.getCustomerByUserId, {
userId: user._id
});
```
--------------------------------
### Run getSubscription Query
Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md
Example of calling the `getSubscription` query with a specific subscription ID. This is useful for retrieving details of a single subscription.
```typescript
const subscription = await ctx.runQuery(api.lib.getSubscription, {
id: "sub_123"
});
```
--------------------------------
### Convex Backend Polar API Setup
Source: https://github.com/get-convex/polar/blob/main/_autodocs/02-react-components.md
Set up the Polar API on your Convex backend. This involves initializing Polar with your components and defining user info retrieval.
```typescript
// convex/billing.ts
import { Polar } from "@convex-dev/polar";
import { components } from "./_generated/api";
const polar = new Polar(components.polar, {
products: {
premiumMonthly: "product_id_1",
premiumYearly: "product_id_2",
},
getUserInfo: async (ctx) => {
const user = await ctx.runQuery(api.auth.getCurrentUser);
return { userId: user._id, email: user.email };
},
});
export const {
generateCheckoutLink,
generateCustomerPortalUrl,
...
} = polar.api();
```
--------------------------------
### Webhook Handler for Subscription Events
Source: https://github.com/get-convex/polar/blob/main/_autodocs/07-usage-examples.md
This example shows how to register webhook handlers for subscription events like creation and updates. It demonstrates updating user tiers, sending emails, and revoking access based on subscription status changes.
```typescript
// convex/http.ts (partial)
polar.registerRoutes(http, {
events: {
"subscription.created": async (ctx, event) => {
const subscription = event.data;
// Find customer in database
const customer = await ctx.runQuery(api.lib.getCustomerByUserId, {
userId: subscription.customerId, // Or however you link customers
});
if (customer) {
// Update user tier
await ctx.runMutation(api.users.updateTier, {
userId: customer.userId,
tier: "premium",
});
// Send welcome email
await ctx.runAction(api.email.sendWelcomeEmail, {
userId: customer.userId,
productName: event.data.productId,
});
}
},
"subscription.updated": async (ctx, event) => {
const subscription = event.data;
if (subscription.status === "canceled") {
// Revoke premium access
const customer = await ctx.runQuery(api.lib.getCustomerByUserId, {
userId: subscription.customerId,
});
if (customer) {
await ctx.runMutation(api.users.updateTier, {
userId: customer.userId,
tier: "free",
});
// Notification
await ctx.runAction(api.email.sendSubscriptionCanceledEmail, {
userId: customer.userId,
});
}
}
},
},
});
```
--------------------------------
### Call syncProducts Action
Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md
Example of how to define and call the `syncProducts` action within your Convex application. This demonstrates the action's handler and how to invoke it from app code.
```typescript
export const syncProducts = action({
args: {},
handler: async (ctx) => {
await polar.syncProducts(ctx);
},
});
// In app code
await runAction(api.billing.syncProducts, {});
```
--------------------------------
### Run getCurrentSubscription Query
Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md
Example of fetching the current active subscription for a user. This includes the product information and handles cases where no subscription is active or the trial has ended.
```typescript
const subscription = await ctx.runQuery(api.lib.getCurrentSubscription, {
userId: user._id
});
if (subscription) {
console.log("Current plan:", subscription.product.name);
}
```
--------------------------------
### Initialize Polar Client in Convex
Source: https://github.com/get-convex/polar/blob/main/_autodocs/README.md
Initialize the Polar client in your Convex backend, providing product IDs and a user info retrieval function. This setup is typically done in a file like `convex/billing.ts`.
```typescript
// convex/billing.ts
import {
Polar
} from "@convex-dev/polar";
const polar = new Polar(components.polar, {
products: {
premiumMonthly: "prod_123",
},
getUserInfo: async (ctx) => ({
userId: user._id,
email: user.email,
}),
});
export const { generateCheckoutLink,
... } = polar.api();
```
--------------------------------
### Test Subscription Access
Source: https://github.com/get-convex/polar/blob/main/_autodocs/07-usage-examples.md
This example shows how to test subscription access by simulating user tiers and verifying document access levels. It uses the ConvexTest utility to run actions and interact with the database.
```typescript
// convex/test.example.ts
import { internal } from "./_generated/api";
import { ConvexTest } from "convex-test";
async function testSubscriptionAccess() {
const t = new ConvexTest();
// Create test user
const userId = await t.run(async (ctx) => {
return ctx.db.insert("users", {
email: "test@example.com",
tier: "free",
});
});
// Verify free plan restrictions
const freeDocuments = await t.run(internal.documents.listDocuments, {
userId,
});
// Simulate premium subscription
await t.run(async (ctx) => {
const customer = await ctx.db.insert("customers", {
id: "cus_test",
userId,
});
await ctx.db.insert("subscriptions", {
id: "sub_test",
customerId: customer,
productId: "prod_pro",
status: "active",
// ... other fields
});
});
// Verify premium access
const premiumDocuments = await t.run(internal.documents.listDocuments, {
userId,
});
console.assert(
premiumDocuments.length > freeDocuments.length,
"Premium should have more documents"
);
}
```
--------------------------------
### CustomerPortalLink in a Settings Page
Source: https://github.com/get-convex/polar/blob/main/_autodocs/02-react-components.md
Provides an example of integrating CustomerPortalLink within a larger settings page structure, including styling and setting a dynamic `returnUrl`.
```typescript
export function SettingsPage() {\n return (\n
\n
Account Settings
\n \n \n Subscription
\n
Edit Subscription
);
}
```
--------------------------------
### Polar Client API Reference
Source: https://github.com/get-convex/polar/blob/main/_autodocs/MANIFEST.txt
Documentation for the main Polar client API, including the constructor and over 15 public methods. This section details instance properties, type exports, and provides complete code examples for each method.
```APIDOC
## Polar Class Constructor
### Description
Initializes a new instance of the Polar client with specified parameters.
### Parameters
- **options** (object) - Required - Configuration options for the Polar client.
- **organizationId** (string) - Required - The unique identifier for the organization.
- **apiKey** (string) - Required - The API key for authentication.
- **webhookSecret** (string) - Optional - The secret used for verifying webhook signatures.
- **convexUrl** (string) - Optional - The URL for the Convex deployment.
### Request Example
```javascript
import { Polar } from '@convex-dev/polar';
const polar = new Polar({
organizationId: 'your-org-id',
apiKey: 'your-api-key',
webhookSecret: 'your-webhook-secret',
convexUrl: 'https://your-convex-deployment.convex.cloud'
});
```
## Public Methods
### Description
This section documents over 15 public methods available on the Polar client instance, covering various functionalities for interacting with the Polar service.
### Method Signature Examples
(Specific method signatures and details are documented in the `01-polar-client.md` file.)
- **getSubscription(subscriptionId: string): Promise**
- **createCheckoutSession(options: CheckoutSessionOptions): Promise
- **listProducts(): Promise
### Code Examples
(Complete code examples for each method are provided in the `01-polar-client.md` file.)
```
--------------------------------
### Initialize Polar API Functions
Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md
Call `polar.api()` to get an object with Convex-wrapped query and action functions. These functions automatically handle context binding.
```typescript
export const {
changeCurrentSubscription,
cancelCurrentSubscription,
getConfiguredProducts,
listAllProducts,
generateCheckoutLink,
generateCustomerPortalUrl,
} = polar.api();
```
--------------------------------
### Idempotent Webhook Handler for Subscription Creation
Source: https://github.com/get-convex/polar/blob/main/_autodocs/05-webhook-integration.md
This example demonstrates an idempotent operation for a 'subscription.created' event. It checks for an existing subscription before processing to prevent duplicate actions, ensuring safe retries.
```typescript
"subscription.created": async (ctx, event) => {
const subId = event.data.id;
// Instead of always inserting, check if exists
const existing = await ctx.runQuery(api.lib.getSubscription, { id: subId });
if (existing) {
console.log("Already processed:", subId);
return;
}
// Safe to process
await markUserAsPremium(event.data.customerId);
}
```
--------------------------------
### Get Current Subscription Details
Source: https://github.com/get-convex/polar/blob/main/README.md
Fetches the current subscription details for a given user. This is suitable for applications with a single active subscription per user. Ensure you have the necessary user context and Polar setup.
```typescript
const {
productKey,
status,
currentPeriodEnd,
currentPeriodStart,
...
} = await polar.getCurrentSubscription(ctx, {
userId: user._id,
});
```
--------------------------------
### Dynamically Load All Products
Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md
If you prefer not to configure keys statically, use `listAllProducts()` to fetch all available products. You can then filter or sort them as needed.
```typescript
const allProducts = await polar.listAllProducts(ctx);
// Filter/sort as needed
const monthlyPlans = allProducts.filter(
p => p.recurringInterval === "month"
);
const yearlyPlans = allProducts.filter(
p => p.recurringInterval === "year"
);
```
--------------------------------
### Get Current User Subscription
Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md
Gets the current active subscription for a user. Returns null if no active subscription exists or if the trial has expired. Includes productKey if the product is in the configured products map.
```typescript
async getCurrentSubscription(
ctx: QueryCtx | MutationCtx | ActionCtx,
{ userId }: { userId: string }
)
```
```typescript
const subscription = await polar.getCurrentSubscription(ctx, {
userId: user._id
});
if (subscription) {
console.log("Status:", subscription.status);
console.log("Product Key:", subscription.productKey);
console.log("Period Ends:", subscription.currentPeriodEnd);
}
```
--------------------------------
### Initialize Polar Client in Convex Backend
Source: https://github.com/get-convex/polar/blob/main/README.md
Initialize the Polar client with user info retrieval and product configuration.
```typescript
// 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, {
// 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();
```
--------------------------------
### Create Polar Client Instance
Source: https://github.com/get-convex/polar/blob/main/_autodocs/00-reference-index.md
Initialize the Polar client with necessary configurations, including product details and a user info retrieval function. Export the relevant API functions.
```typescript
const polar = new Polar(components.polar, {
products: { /* ... */ },
getUserInfo: async (ctx) => { /* ... */ },
});
export const { /* ... */ } = polar.api();
```
--------------------------------
### Get Product by ID
Source: https://github.com/get-convex/polar/blob/main/README.md
Retrieve a single product using its Polar product ID.
```typescript
const product = await polar.getProduct(ctx, { productId });
```
--------------------------------
### Get Customer by User ID
Source: https://github.com/get-convex/polar/blob/main/README.md
Retrieve the Polar customer record associated with a given user ID.
```typescript
const customer = await polar.getCustomerByUserId(ctx, userId);
```
--------------------------------
### getCurrentSubscription
Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md
Gets the current active subscription for a user. Returns null if no active subscription exists or if the trial has expired.
```APIDOC
## getCurrentSubscription(ctx, { userId })
### Description
Gets the current active subscription for a user. Returns null if no active subscription exists or if the trial has expired. Use this for apps that only allow one active subscription per user.
### Method
Not applicable (SDK method)
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
None
### Parameters
- **ctx** (Context) - Required - Convex query, mutation, or action context
- **userId** (string) - Required - The Convex user ID
### Returns
Promise<{ ...Subscription, productKey?: keyof Products, product: Product } | null>
### Example
```typescript
const subscription = await polar.getCurrentSubscription(ctx, {
userId: user._id
});
if (subscription) {
console.log("Status:", subscription.status);
console.log("Product Key:", subscription.productKey);
console.log("Period Ends:", subscription.currentPeriodEnd);
}
```
```
--------------------------------
### Initialize Polar Client
Source: https://github.com/get-convex/polar/blob/main/_autodocs/00-reference-index.md
Instantiate the Polar client with your Convex components, product information, user authentication, and API keys. Ensure environment variables for organization token and webhook secret are set.
```typescript
import { Polar } from "@convex-dev/polar";
const polar = new Polar(components.polar, {
products: { /* product keys to IDs */ },
getUserInfo: async (ctx) => { /* return userId and email */ },
organizationToken: process.env.POLAR_ORGANIZATION_TOKEN,
webhookSecret: process.env.POLAR_WEBHOOK_SECRET,
server: "production",
});
```
--------------------------------
### List All Products
Source: https://github.com/get-convex/polar/blob/main/README.md
Retrieve a list of all available products and their associated prices.
```typescript
const products = await polar.listProducts(ctx);
```
--------------------------------
### Get Current Subscription
Source: https://github.com/get-convex/polar/blob/main/README.md
Retrieve the current user's active subscription. Returns null if no active subscription exists. Expired trials are excluded.
```typescript
const subscription = await polar.getCurrentSubscription(ctx, { userId });
```
--------------------------------
### Polar Constructor
Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md
Initializes a new Polar client instance. This is the primary way to set up the Polar client with your application's configuration.
```APIDOC
## new Polar(component, config)
### Description
Initializes a new Polar client instance.
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
None
### Parameters
- **component** (ComponentApi) - Required - The Polar Convex component reference from `components.polar`
- **config.products** (Products) - Optional - Map of product keys to Polar product IDs for easy reference
- **config.getUserInfo** (Function) - Required - Async function that returns current user's ID and email from context
- **config.organizationToken** (string) - Optional - Polar API organization token
- **config.webhookSecret** (string) - Optional - Polar webhook secret for validating events
- **config.server** ("sandbox" | "production") - Optional - Polar API environment
### Request Example
```typescript
import { Polar } from "@convex-dev/polar";
import { api, components } from "./_generated/api";
import { DataModel } from "./_generated/dataModel";
const polar = new Polar(components.polar, {
products: {
premiumMonthly: "product_id_1",
premiumYearly: "product_id_2",
},
getUserInfo: async (ctx) => {
const user = await ctx.runQuery(api.example.getCurrentUser);
return { userId: user._id, email: user.email };
},
organizationToken: process.env.POLAR_ORGANIZATION_TOKEN,
webhookSecret: process.env.POLAR_WEBHOOK_SECRET,
server: "production",
});
```
```
--------------------------------
### listProducts
Source: https://github.com/get-convex/polar/blob/main/README.md
Lists all available products and their associated prices.
```APIDOC
## listProducts
List all available products and their prices:
```ts
const products = await polar.listProducts(ctx);
```
```
--------------------------------
### Polar Client Initialization
Source: https://github.com/get-convex/polar/blob/main/README.md
Configuration options for initializing the Polar client, including user info retrieval, product mapping, and authentication credentials.
```APIDOC
## Polar Client Configuration
The `Polar` class accepts a configuration object with the following properties:
- `getUserInfo`: Function to get the current user's ID and email.
- `products`: (Optional) Map of arbitrarily named keys to Polar product IDs.
- `organizationToken`: (Optional) Your Polar organization token. Falls back to `POLAR_ORGANIZATION_TOKEN` env var.
- `webhookSecret`: (Optional) Your Polar webhook secret. Falls back to `POLAR_WEBHOOK_SECRET` env var.
- `server`: (Optional) Polar server environment: "sandbox" or "production". Falls back to `POLAR_SERVER` env var.
```
--------------------------------
### Deploy New Version
Source: https://github.com/get-convex/polar/blob/main/CONTRIBUTING.md
Releases a new stable version of the project.
```sh
npm run release
```
--------------------------------
### CheckoutLink with Specified Locale
Source: https://github.com/get-convex/polar/blob/main/_autodocs/02-react-components.md
Sets the locale for the checkout page using the `locale` prop. This example sets the locale to French (`"fr"`).
```typescript
S'abonner
```
--------------------------------
### Query User and Subscription Details
Source: https://github.com/get-convex/polar/blob/main/README.md
Use this query to fetch a user's basic information along with their current subscription status. It categorizes the subscription as free or premium based on the product key.
```typescript
// 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",
};
},
});
```
--------------------------------
### Run getProduct Query
Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md
Demonstrates how to execute the `getProduct` query using a product ID. This function returns the product details or null if not found.
```typescript
const product = await ctx.runQuery(api.lib.getProduct, {
id: "product_123"
});
```
--------------------------------
### Get Product by Product ID
Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md
Retrieves a single product by its Polar product ID. Requires a Convex context and the product ID. Returns null if the product is not found.
```typescript
getProduct(
ctx: QueryCtx | MutationCtx | ActionCtx,
{ productId }: { productId: string }
)
```
```typescript
const product = await polar.getProduct(ctx, {
productId: "product_id_from_polar"
});
if (product) {
console.log("Product:", product.name, product.prices);
}
```
--------------------------------
### Basic CheckoutLink Usage
Source: https://github.com/get-convex/polar/blob/main/_autodocs/02-react-components.md
Renders a basic `CheckoutLink` component to upgrade to a premium plan. Ensure `polarApi` is correctly passed from your Convex API setup and `productIds` are valid.
```typescript
import { CheckoutLink } from "@convex-dev/polar/react";
import { api } from "../convex/_generated/api";
export function UpgradeButton({ products }) {
return (
Upgrade to Premium
);
}
```
--------------------------------
### Get Customer by User ID
Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md
Retrieves the Polar customer record associated with a Convex user ID. Requires a Convex query, mutation, or action context and the user ID.
```typescript
getCustomerByUserId(ctx: QueryCtx | MutationCtx | ActionCtx, userId: string)
```
```typescript
const customer = await polar.getCustomerByUserId(ctx, userId);
if (customer) {
console.log("Customer Polar ID:", customer.id);
}
```
--------------------------------
### Register Webhook Event Handlers
Source: https://github.com/get-convex/polar/blob/main/_autodocs/03-types-and-models.md
Example of registering webhook event handlers with Polar. This demonstrates how to listen for specific event types like `subscription.created` and `subscription.updated` and access their data.
```typescript
polar.registerRoutes(http, {
events: {
"subscription.created": async (ctx, event) => {
// event.type === "subscription.created"
// event.data is a Subscription
const subscription = event.data;
console.log("New subscription:", subscription.id);
},
"subscription.updated": async (ctx, event) => {
// event.type === "subscription.updated"
const subscription = event.data;
if (subscription.status === "canceled") {
console.log("Subscription canceled");
}
},
},
});
```
--------------------------------
### Configure Polar Billing and API
Source: https://github.com/get-convex/polar/blob/main/_autodocs/05-webhook-integration.md
Set up the Polar instance with product definitions and a user info retrieval function. This configuration is essential for Polar to manage billing-related operations.
```typescript
// convex/billing.ts
import { Polar } from "@convex-dev/polar";
import { api, components } from "./_generated/api";
const polar = new Polar(components.polar, {
products: {
premium: "product_id_1",
},
getUserInfo: async (ctx) => {
const user = await ctx.runQuery(api.auth.getCurrentUser);
return { userId: user._id, email: user.email };
},
});
export const { /* ... */ } = polar.api();
```
--------------------------------
### Display All Products with `listAllProducts`
Source: https://github.com/get-convex/polar/blob/main/README.md
This React component displays products and prices when products are not configured by key. It fetches all products, finds monthly and yearly plans, and renders their details. You can also sort products client-side or in a Convex query.
```tsx
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 (
{proMonthly && (
{proMonthly.name}
${(proMonthly.prices[0].priceAmount ?? 0) / 100}/
{proMonthly.prices[0].recurringInterval}
)}
{proYearly && (
{proYearly.name}
${(proYearly.prices[0].priceAmount ?? 0) / 100}/year
)}
);
}
```
--------------------------------
### Define getCurrentSubscription Query
Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md
Defines a query to get a user's active subscription, including associated product details. It excludes expired trials and returns null if no active subscription exists.
```typescript
export const getCurrentSubscription = query({
args: { userId: v.string() },
returns: v.union(
v.object({
...schema.tables.subscriptions.validator.fields,
product: schema.tables.products.validator,
}),
v.null(),
),
handler: async (ctx, args) => { ... }
});
```
--------------------------------
### Configure Products Statically
Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md
Map arbitrary keys to Polar product IDs for static configuration. Use these keys later in your app to retrieve product information.
```typescript
const polar = new Polar(components.polar, {
products: {
// Keys are yours to choose
basicMonthly: "prod_abc123",
basicYearly: "prod_def456",
proMonthly: "prod_ghi789",
proYearly: "prod_jkl012",
enterpriseCustom: "prod_mno345",
},
getUserInfo: /* ... */,
});
// Use in your app
const products = await polar.getConfiguredProducts(ctx);
console.log(products.basicMonthly);
console.log(products.proYearly);
```
--------------------------------
### Billing Logic with Polar
Source: https://github.com/get-convex/polar/blob/main/_autodocs/07-usage-examples.md
Initialize and use the Polar SDK to manage products, subscriptions, and user information. This includes defining product IDs and handling user data retrieval.
```typescript
import { Polar } from "@convex-dev/polar";
import { api, components } from "./_generated/api";
import { DataModel } from "./_generated/dataModel";
import { query } from "./_generated/server";
const polar = new Polar(components.polar, {
products: {
starterMonthly: "prod_starter_monthly",
starterYearly: "prod_starter_yearly",
proMonthly: "prod_pro_monthly",
proYearly: "prod_pro_yearly",
},
getUserInfo: async (ctx) => {
const user = await ctx.runQuery(api.auth.getCurrentUser);
return {
userId: user._id,
email: user.email,
};
},
organizationToken: process.env.POLAR_ORGANIZATION_TOKEN,
webhookSecret: process.env.POLAR_WEBHOOK_SECRET,
server: process.env.POLAR_SERVER as "sandbox" | "production",
});
// Export typesafe API functions
export const {
changeCurrentSubscription,
cancelCurrentSubscription,
getConfiguredProducts,
listAllProducts,
listAllSubscriptions,
generateCheckoutLink,
generateCustomerPortalUrl,
} = polar.api();
// Additional query for subscription details
export const getSubscriptionDetails = query({
args: {},
handler: async (ctx) => {
const user = await ctx.runQuery(api.auth.getCurrentUser);
const subscription = await polar.getCurrentSubscription(ctx, {
userId: user._id,
});
return {
subscription,
isPremium:
subscription?.productKey === "proMonthly" ||
subscription?.productKey === "proYearly",
};
},
});
export { polar };
```
--------------------------------
### Registering Webhook Routes
Source: https://github.com/get-convex/polar/blob/main/_autodocs/05-webhook-integration.md
This example demonstrates how to register webhook routes for Polar events using the `registerRoutes` method on your Convex HTTP router. You can specify a custom path and define handlers for specific events.
```APIDOC
## POST /polar/events
### Description
Registers webhook handlers for Polar events, allowing real-time synchronization of subscription and product data.
### Method
POST
### Endpoint
/polar/events (customizable via `path` option)
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
None (The actual event payload is sent in the request body by Polar)
### Request Example
(Polar sends event data in the request body, not shown here as it's from the external service)
### Response
#### Success Response (200)
- **status** (string) - Indicates successful processing of the webhook event.
#### Response Example
```json
{
"status": "success"
}
```
### registerRoutes Method Signature
```typescript
registerRoutes(
http: HttpRouter,
{
path,
events,
onSubscriptionCreated,
onSubscriptionUpdated,
onProductCreated,
onProductUpdated,
}?: {
path?: string;
events?: WebhookEventHandlers;
onSubscriptionCreated?: (ctx, event) => Promise;
onSubscriptionUpdated?: (ctx, event) => Promise;
onProductCreated?: (ctx, event) => Promise;
onProductUpdated?: (ctx, event) => Promise;
}
)
```
### registerRoutes Method Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| http | HttpRouter | Yes | — | Convex HTTP router instance |
| options.path | string | No | "/polar/events" | Webhook endpoint path |
| options.events | WebhookEventHandlers | No | — | Typesafe event handler map |
| options.onSubscriptionCreated | Function | No | — | **Deprecated**: Use events instead |
| options.onSubscriptionUpdated | Function | No | — | **Deprecated**: Use events instead |
| options.onProductCreated | Function | No | — | **Deprecated**: Use events instead |
| options.onProductUpdated | Function | No | — | **Deprecated**: Use events instead |
### Path Customization Example
```typescript
// convex/http.ts
import { httpRouter } from "convex/server";
import { polar } from "./billing";
const http = httpRouter();
polar.registerRoutes(http, {
path: "/webhooks/polar", // Custom path
events: {
"subscription.created": async (ctx, event) => {
// Handle subscription creation
console.log("Subscription created:", event);
},
"product.created": async (ctx, event) => {
// Handle product creation
console.log("Product created:", event);
}
},
});
export default http;
```
**Note**: The Webhook URL would then be `https://your-domain.convex.site/webhooks/polar`.
```
--------------------------------
### Expose Local Server with ngrok
Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md
Use ngrok to expose your local Convex development server to the internet for webhook testing. Update your webhook URL to the ngrok endpoint.
```bash
npx convex dev # Start Convex dev server
ngrok http 3210 # Expose to internet
# Update webhook URL to ngrok endpoint
```
--------------------------------
### Create Polar Client Instance
Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md
Instantiate the Polar client in your Convex project, providing user info and optional configurations.
```typescript
// convex/billing.ts
import { Polar } from "@convex-dev/polar";
import { api, components } from "./_generated/api";
import { DataModel } from "./_generated/dataModel";
const polar = new Polar(components.polar, {
// Required: provide user info
getUserInfo: async (ctx) => {
const user = await ctx.runQuery(api.auth.getCurrentUser);
return {
userId: user._id,
email: user.email,
};
},
// Optional: configure products by key
products: {
premiumMonthly: "prod_xxxxx",
premiumYearly: "prod_xxxxx",
},
// Optional: configure credentials in code
// (defaults to env vars if not provided)
organizationToken: process.env.POLAR_ORGANIZATION_TOKEN,
webhookSecret: process.env.POLAR_WEBHOOK_SECRET,
server: "production",
});
export const {
changeCurrentSubscription,
cancelCurrentSubscription,
getConfiguredProducts,
listAllProducts,
listAllSubscriptions,
generateCheckoutLink,
generateCustomerPortalUrl,
} = polar.api();
// For internal use
export { polar };
```
--------------------------------
### List Products (Active and Archived)
Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md
Lists all products, optionally including archived ones. Requires a Convex context. Defaults to only active products.
```typescript
listProducts(
ctx: QueryCtx | MutationCtx | ActionCtx,
{ includeArchived }: { includeArchived?: boolean } = {}
)
```
```typescript
const activeProducts = await polar.listProducts(ctx);
const allProducts = await polar.listProducts(ctx, { includeArchived: true });
```
--------------------------------
### CustomerPortalLink with Return URL
Source: https://github.com/get-convex/polar/blob/main/_autodocs/02-react-components.md
Shows how to use the `returnUrl` prop with CustomerPortalLink to specify a URL for redirection after leaving the customer portal.
```typescript
Manage Billing
```
--------------------------------
### Run Project Tests
Source: https://github.com/get-convex/polar/blob/main/CONTRIBUTING.md
Cleans previous builds, builds the project, performs type checking, linting, and runs tests.
```sh
npm run clean
npm run build
npm run typecheck
npm run lint
npm run test
```
--------------------------------
### Deploy Alpha Release
Source: https://github.com/get-convex/polar/blob/main/CONTRIBUTING.md
Releases an alpha version of the project.
```sh
npm run alpha
```
--------------------------------
### List Products
Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md
Lists all available products. Optionally include archived products by setting `includeArchived` to true. Defaults to only active products.
```typescript
export const listProducts = query({
args: { includeArchived: v.optional(v.boolean()) },
returns: v.array(
v.object({
...schema.tables.products.validator.fields,
priceAmount: v.optional(v.number()),
}),
),
handler: async (ctx, args) => { ... }
});
```
```typescript
const activeProducts = await ctx.runQuery(api.lib.listProducts, {});
const allProducts = await ctx.runQuery(api.lib.listProducts, {
includeArchived: true
});
```
--------------------------------
### Initialize Polar Client with DataModel
Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md
Instantiate the Polar client, providing a strongly typed DataModel for type safety. Ensure your configuration object adheres to TypeScript's expectations.
```typescript
import { Polar } from "@convex-dev/polar";
import { DataModel } from "./_generated/dataModel";
// Strongly typed with your custom DataModel
const polar = new Polar(components.polar, {
// TypeScript ensures config is correct
getUserInfo: async (ctx) => { /* ... */ },
products: { /* ... */ },
});
// Exported functions are typed for your project
export const {
generateCheckoutLink,
// ... other functions ...
} = polar.api();
```
--------------------------------
### listProducts
Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md
Lists all products, optionally including archived ones.
```APIDOC
## listProducts
### Description
Lists all products, optionally including archived ones.
### Method
query
### Endpoint
api.lib.listProducts
### Parameters
#### Arguments
- **includeArchived** (boolean) - Optional - Whether to include archived products
### Returns
Product[]
### Example
```typescript
const activeProducts = await ctx.runQuery(api.lib.listProducts, {});
const allProducts = await ctx.runQuery(api.lib.listProducts, {
includeArchived: true
});
```
```
--------------------------------
### Products Table Schema
Source: https://github.com/get-convex/polar/blob/main/_autodocs/00-reference-index.md
Outlines the schema for the 'products' table, detailing product attributes such as ID, name, description, pricing, and associated media. This is used for defining products and their pricing plans.
```typescript
{
id: string;
name: string;
description?: string;
organizationId: string;
isRecurring: boolean;
isArchived: boolean;
createdAt: string;
modifiedAt?: string;
recurringInterval?: string;
recurringIntervalCount?: number;
trialInterval?: string;
trialIntervalCount?: number;
metadata?: Record;
prices: Price[];
benefits?: Benefit[];
medias: Media[];
_id: Id<"products">;
_creationTime: number;
}
```
--------------------------------
### Initialize Polar Client
Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md
Initializes a new Polar client instance. Requires a Polar Convex component reference and configuration including user info retrieval, product mapping, and API credentials.
```typescript
constructor(
public component: ComponentApi,
private config: {
products?: Products;
getUserInfo: (ctx: QueryCtx | MutationCtx | ActionCtx) => Promise<{
userId: string;
email: string;
}>;
organizationToken?: string;
webhookSecret?: string;
server?: "sandbox" | "production";
},
)
```
```typescript
import { Polar } from "@convex-dev/polar";
import { api, components } from "./_generated/api";
import { DataModel } from "./_generated/dataModel";
const polar = new Polar(components.polar, {
products: {
premiumMonthly: "product_id_1",
premiumYearly: "product_id_2",
},
getUserInfo: async (ctx) => {
const user = await ctx.runQuery(api.example.getCurrentUser);
return { userId: user._id, email: user.email };
},
organizationToken: process.env.POLAR_ORGANIZATION_TOKEN,
webhookSecret: process.env.POLAR_WEBHOOK_SECRET,
server: "production",
});
```
--------------------------------
### Display Products and Prices
Source: https://github.com/get-convex/polar/blob/main/README.md
Functions to retrieve and display product information and pricing.
```APIDOC
## getConfiguredProducts
### Description
Retrieves configured products and their prices based on keys set in the Polar constructor.
### Usage
```tsx
const products = useQuery(api.example.getConfiguredProducts);
// ... render products
```
## listAllProducts
### Description
Retrieves all available products and their prices. Useful when products are not configured by key.
### Usage
```tsx
const products = useQuery(api.example.listAllProducts);
// ... find and render desired products
```
### Product Details
Each product object includes:
- `id`: The Polar product ID
- `name`: The product name
- `description`: Product description
- `isRecurring`: Whether the product is a subscription
- `recurringInterval`: Billing interval (e.g., "month", "year")
- `trialInterval`: Trial period interval (if configured)
- `trialIntervalCount`: Number of trial intervals (if configured)
- `benefits`: Array of product benefits
- `prices`: Array of prices with details on `amountType` (fixed, free, custom, seat_based, metered_unit).
```
--------------------------------
### Define and Use Typed Product Keys
Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md
Define an interface for your product keys and use it to strongly type the Polar client. This ensures product keys are correctly typed in queries and configurations.
```typescript
interface Products {
premiumMonthly: string;
premiumYearly: string;
}
const polar = new Polar(components.polar, {
products: {
premiumMonthly: "prod_123",
premiumYearly: "prod_456",
},
// ...
});
// Products properly typed in queries
const result = await polar.getCurrentSubscription(ctx, { userId });
if (result?.productKey === "premiumYearly") {
// TypeScript knows this is a valid key
}
```
--------------------------------
### Sync Products from Polar
Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md
Syncs existing products from Polar to the local database. Use this to backfill product data. Requires a Convex action context.
```typescript
async syncProducts(ctx: ActionCtx)
```
```typescript
export const syncProducts = action({
args: {},
handler: async (ctx) => {
await polar.syncProducts(ctx);
},
});
```
--------------------------------
### listProducts
Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md
Lists all products, with an option to include archived products.
```APIDOC
## listProducts(ctx, options?)
### Description
Lists all products (optionally including archived ones).
### Method
Not applicable (SDK method)
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
None
### Parameters
- **ctx** (Context) - Required - Convex query, mutation, or action context
- **options.includeArchived** (boolean) - Optional - Whether to include archived products (defaults to false)
### Returns
Promise
### Example
```typescript
const activeProducts = await polar.listProducts(ctx);
const allProducts = await polar.listProducts(ctx, { includeArchived: true });
```
```
--------------------------------
### createCustomerPortalSession
Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md
Creates a customer portal session URL for the user to manage their subscription.
```APIDOC
## createCustomerPortalSession(ctx, { userId, returnUrl? })
### Description
Creates a customer portal session URL for the user to manage their subscription.
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
- **userId** (string) - Required - Convex user ID
- **returnUrl** (string) - Optional - URL to return to after the customer exits the portal
### Request Example
```typescript
const { url } = await polar.createCustomerPortalSession(ctx, {
userId: user._id,
returnUrl: "https://example.com/account",
});
window.open(url, "_blank");
```
### Response
#### Success Response (200)
- **url** (string) - The URL for the customer portal session
```
--------------------------------
### Initialize Polar with Constructor Options
Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md
Initialize the Polar component, providing configuration options directly in the constructor. These options override environment variables.
```typescript
const polar = new Polar(components.polar, {
// These override env vars
organizationToken: "token_from_code",
webhookSecret: "secret_from_code",
server: "production",
});
// Falls back to:
// process.env.POLAR_ORGANIZATION_TOKEN
// process.env.POLAR_WEBHOOK_SECRET
// process.env.POLAR_SERVER (or "sandbox" default)
```
--------------------------------
### Create Customer Portal Session
Source: https://github.com/get-convex/polar/blob/main/_autodocs/01-polar-client.md
Generates a URL for the customer portal, allowing users to manage their subscriptions. Provide the user ID and an optional return URL for redirection after portal exit.
```typescript
async createCustomerPortalSession(
ctx: GenericActionCtx,
{ userId, returnUrl }: { userId: string; returnUrl?: string }
)
```
```typescript
const { url } = await polar.createCustomerPortalSession(ctx, {
userId: user._id,
returnUrl: "https://example.com/account",
});
window.open(url, "_blank");
```
--------------------------------
### Generate Checkout Link
Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md
When calling `generateCheckoutLink()`, provide required parameters like `productIds`, `origin`, and `successUrl`. Optional configurations include trial details, metadata, subscription ID, and locale.
```typescript
const { url } = await generateCheckoutLink({
productIds: ["prod_123"], // Required
origin: window.location.origin, // Required (for embedding)
successUrl: window.location.href, // Required
// Optional configurations
trialInterval: "day", // "day", "week", "month", "year"
trialIntervalCount: 7, // Number of intervals
metadata: { source: "pricing" }, // Custom metadata
subscriptionId: "sub_456", // For upgrades
locale: "fr", // Language: "en", "fr", "de", etc.
});
```
--------------------------------
### Basic CustomerPortalLink Usage
Source: https://github.com/get-convex/polar/blob/main/_autodocs/02-react-components.md
Demonstrates the basic usage of the CustomerPortalLink component. Ensure the `polarApi` prop is correctly passed with the `generateCustomerPortalUrl` function.
```typescript
import { CustomerPortalLink } from "@convex-dev/polar/react";\nimport { api } from "../convex/_generated/api";\n\nexport function AccountSettings() {\n return (\n \n Manage Subscription\n \n );\n}
```
--------------------------------
### Include Customer Metadata
Source: https://github.com/get-convex/polar/blob/main/_autodocs/06-configuration-and-setup.md
Include metadata when creating customers, such as user ID, tier, and signup date.
```typescript
const customer = await customersCreate(polar.polar, {
email: "user@example.com",
metadata: {
userId: user._id,
tier: "premium",
signupDate: new Date().toISOString(),
},
});
```
--------------------------------
### Define syncProducts Action
Source: https://github.com/get-convex/polar/blob/main/_autodocs/04-internal-api-reference.md
Defines the `syncProducts` action for fetching and syncing products from Polar. It requires a Polar access token and the server environment.
```typescript
export const syncProducts = action({
args: {
polarAccessToken: v.string(),
server: v.union(v.literal("sandbox"), v.literal("production")),
},
handler: async (ctx, args) => { ... }
});
```