Polar Integration
Polar is a good fit for SaaS products, open-source sponsorships, digital products, and entitlement-aware app features.
Install from the CLI
farm add integration polar --ui
Config-first setup
import { polar } from "@farm.js/integrations/polar";export const integrations = { billing: polar({ accessToken: process.env.POLAR_ACCESS_TOKEN, billing: { resolveOwner(ctx) { const userId = ctx.req.get<string>("user.id"); return userId ? { id: userId, kind: "user" } : null; }, }, }),};
POLAR_WEBHOOK_SECRET is read from the environment when webhook routes are configured.
With no instance, Farm constructs the Polar SDK from accessToken and server. The token can be
passed directly or read from POLAR_ACCESS_TOKEN.
Choose SDK ownership
Let Farm construct Polar
Use the config-first example above for the common path. Farm owns SDK construction, while the application still supplies billing ownership, products, meters, routes, and webhook behavior.
Provide an application-owned instance
import { Polar } from "@polar-sh/sdk";import { polar } from "@farm.js/integrations/polar";const polarClient = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN!, server: "sandbox",});export const billing = polar({ instance: polarClient, server: "sandbox", billing: { resolveOwner: () => null, },});
Farm uses the supplied SDK directly, so the integration does not require accessToken. server
still describes the integration environment and defaults from POLAR_SERVER. When both an instance
and token are supplied, the instance wins.
Usage
const checkout = await api.billing.checkout.post({ body: { productId: "pro", customerEmail: user.email, successPath: "/dashboard", },});
Database-backed billing
Polar callbacks can read and write relational records through ctx.args.db, so webhook snapshots, subscription state, and customer entitlement checks share the same database-agnostic integration layer. This path is separate from KV storage.
What Polar adds
| Area | Details |
|---|---|
| Products | Public product metadata for pricing and account screens. |
| Checkout | Polar checkout sessions for one-time and subscription products. |
| Portal | Customer sessions for billing management. |
| Billing status | Current customer, plan, features, limits, and active product state. |
| Usage | Meter and entitlement helpers for usage-aware products. |
| Webhooks | Subscription and order events that update local billing state. |
Common callers
const products = await api.billing.products.get();const status = await api.billing.status.get();const checkout = await api.billing.checkout.post({ body: { productId: "pro", successPath: "/dashboard", cancelPath: "/pricing", },});
Portal flow
const portal = await api.billing.portal.post({ body: { returnTo: "/settings/billing", },});if (portal.data?.redirectTo) { window.location.href = portal.data.redirectTo;}
When Polar is a good fit
Polar works especially well when the product is developer-facing, open-source, sponsor-backed, or selling digital access. The Farm integration keeps the same caller shape as other billing providers, so moving between Stripe, Autumn, and Polar does not force your app UI to learn a new local API style.
Production notes
- Set
POLAR_ACCESS_TOKEN,POLAR_WEBHOOK_SECRET,POLAR_SERVER, andAPP_BASE_URL. - Use sandbox/server config for local testing and production config for live billing.
- Store the external customer ID with the billing owner so portal and status reads stay stable.
- Test checkout return URLs, portal return URLs, webhook signatures, and entitlement checks.