Polar, a merchant of record for software builders
Polar sells your digital products in its own name, and it, not you, answers to tax authorities for VAT and sales tax. The free plan costs 5 percent plus 50 cents per transaction, and three paid plans go down to 3.4 percent plus 30 cents at 400 dollars a month. The server is licensed under Apache 2.0 and the TypeScript SDK under MIT, at version 0.49.0 released on 20 July 2026.
What Polar is, what it is not, and why you confuse it with polars
Start with the mix-up that costs people fifteen minutes. Typing "polar" into a search engine returns two entirely different projects. The one described here is the payments platform at polar.sh. The other is polars, a dataframe library written in Rust and used mostly from Python, published on PyPI at version 1.43.2 under MIT. They share five letters and nothing else.
Polar is a merchant of record, meaning the formal seller. That distinction decides everything else. An ordinary payment processor, Stripe in its standard setup for example, moves money and stops there. The sales contract binds your company to the end customer, so VAT OSS registration, nexus thresholds across US states and monthly filings stay your duty. A merchant of record steps between you and the customer as a reseller. The invoice carries its tax number, it charges and remits the tax, and you receive a payout and a single settlement with a single counterparty.
The product scope is wider than a checkout. Polar handles subscriptions with trials and proration, usage based billing with your own event meters, seat sales, prepaid credits and discounts. On top of that sits a benefits mechanism attached to a product. In the SDK the list of benefit types is closed and reads: custom, discord, github_repository, downloadables, license_keys, meter_credit, feature_flag and slack_shared_channel. In practice that means a buyer automatically receives an invitation to a private GitHub repository, a Discord role, a license key or a downloadable file of up to 10 GB, with no logic written on your side.
What Polar does not do. It is not a payment gateway for physical goods and not an invoicing system for consulting work. It also does not replace your own database, because order and subscription identifiers are better kept locally, in Supabase for instance, than fetched from the vendor on every request. It does not send your product emails either, beyond purchase confirmations, so notifications will still be built on something like Resend.
The license checked against three sources
For platforms of this kind the client license and the server license are two separate matters, and confusing them leads to bad decisions. I checked three independent places.
The first source is the LICENSE file in the repository. In polarsource/polar, the server repository, sits the full text of the Apache License 2.0. GitHub shows a tab labelled Apache-2.0 for that repository, it has roughly 10.2 thousand stars and is not archived. The TypeScript SDK lives in a separate repository, polarsource/polar-js, where the LICENSE file carries the MIT text with the notice "Copyright (c) 2026 Polar Software Inc.".
The second source is the license field in package registries. On npm, @polar-sh/sdk at 0.49.0 says MIT. On PyPI, polar-sdk at 0.32.0 says MIT and carries the classifier License :: OSI Approved :: MIT License.
The third source is the content of the published package, because a registry declaration is sometimes fiction. The npm archive for 0.49.0 weighs 1.67 MB, close to 20 MB unpacked, and holds 9693 files including 2152 .js files, a src directory with TypeScript sources and a dist directory in two flavours, CommonJS and ESM. The LICENSE file is inside. The safety check of asking whether the package contains any code at all passes here. The Python side looks similar: polar_sdk-0.32.0-py3-none-any.whl weighs 909,811 bytes, holds 881 .py files plus polar_sdk-0.32.0.dist-info/licenses/LICENSE, and its METADATA states License: MIT.
Discrepancies do exist, and they concern the helper packages. @polar-sh/nextjs at 0.9.6 and @polar-sh/better-auth at 1.8.4 carry no license field in package.json at all, so the npm registry shows no license for them. Unpacking the @polar-sh/nextjs archive shows that a LICENSE file is in fact present and contains the Apache 2.0 text. Meanwhile @polar-sh/checkout at 0.4.1 and @polar-sh/adapter-utils at 0.4.6 declare Apache-2.0 explicitly. The result is that a single dependency tree gives you MIT at the core, Apache 2.0 in the adapters and two packages with no declaration, which an automated license audit will flag as unknown. If your company keeps such a list, enter Apache 2.0 by hand based on the file inside the package rather than on the metadata.
Apache 2.0 on the server is good news, because SSPL or AGPL show up in this category and rule out part of commercial usage. The practical value of self-hosting is small here, though, because the product is not the code but the merchant of record status. Running the repository on your own server will not make anyone else remit your VAT.
An SDK below version 1.0 and what that means in practice
The number 0.49.0 is neither an accident nor modesty. In semantic versioning anything below 1.0 releases the author from any backward compatibility promise: a breaking change may arrive in a minor release. And it did. The package README carries an explicit warning that starting with versions above 0.6.0 the SDK generator changed and the new version is not backward compatible with earlier ones.
The release history shows the pace. From 0.1.0 on 13 October 2023 until today, 161 versions have shipped. Running alongside the 0.x line is a line heading for one point zero: the next tag on npm points at 1.0.0-alpha.17 from 19 August 2026. That means version 1.0 is being prepared but is still an alpha at the time of writing, and what you install in production is the pre-one line.
The SDK is generated by Speakeasy from an OpenAPI specification, as the source file headers show. The consequence is that every API change reaches the generated code without a human filter. Dependencies are modest: zod in the range ^3.25.65 || ^4.0.0 and standardwebhooks at ^1.0.0. The Python build requires Python 3.9.2 or newer and pulls httpx, pydantic, jsonpath-python and standardwebhooks.
The operational conclusion is simple. Pin an exact version instead of a range, keep SDK calls behind your own thin adapter layer and read the changelog before every upgrade. It is the same discipline you apply to larger version jumps in Next.js, except the risk is higher here because the major number has not started yet.
Pricing: plans, surcharges and payouts
Pricing is published in two places, in the documentation at docs.polar.sh and on the marketing site. I checked both and this time the numbers agree, which with payment vendors is not the rule.
| Plan | Monthly fee | Per transaction | Support |
|---|---|---|---|
| Starter | 0 USD | 5 percent plus 50 cents | standard |
| Pro | 20 USD | 3.8 percent plus 40 cents | prioritized |
| Growth | 100 USD | 3.6 percent plus 35 cents | prioritized |
| Scale | 400 USD | 3.4 percent plus 30 cents | prioritized plus Slack and SSO |
A legacy rate sits alongside it. Organizations created before 27 May 2026 stay on the Early Member tariff, that is 4 percent plus 40 cents with a 0.5 percent surcharge on subscription payments and no monthly fee. The catch is stated plainly: moving to a paid plan retires Early Member permanently, and moving back down lands you on Starter at the new 5 percent plus 50 cents. New organizations created after that date start on Starter, even when opened by an older account.
There are two surcharges and only two. Plus 1.5 percent for cards issued outside the United States, and plus 0.5 percent for subscription payments on the Early Member tariff only. Starter, Pro, Growth and Scale carry no separate subscription fee, and that is a real difference against the competition.
Payouts are settled by Stripe and Polar states it adds no markup of its own. The rates are 2 dollars for every month in which a payout occurred, plus 0.25 percent and 25 cents per single payout, plus currency conversion of 0.25 percent inside the European Union and up to 1 percent elsewhere. A dispute with the customer's bank costs 15 dollars regardless of the outcome. When you refund a customer, the original transaction fee does not come back to you.
I checked the breakeven arithmetic separately, because the vendor states it as finished numbers: 1,379 dollars of monthly sales for Pro, 5,634 for Growth and 19,048 for Scale. Those values only come out under the assumption that the average transaction is around 40 dollars, which the page states nowhere. For Pro the rate difference is 1.2 points and the fixed fee difference is 10 cents, so the equation of 0.012 times sales plus 0.10 times transaction count equal to 20 yields 1,379 dollars only at 34.5 transactions a month. The same 40 dollar factor reproduces the other two thresholds to the dollar. If you sell a product at 9 dollars, your own threshold sits somewhere else entirely and you should compute it yourself.
Polar against Lemon Squeezy on a concrete amount
This is the question that actually drives the choice. Lemon Squeezy takes 5 percent plus 50 cents, belongs to Stripe and has itself announced a successor plus a migration of users to Stripe Managed Payments. Polar takes the same on the free plan but differs in the structure of surcharges.
| Item | Polar Starter | Lemon Squeezy |
|---|---|---|
| Base fee | 5 percent plus 50 cents | 5 percent plus 50 cents |
| International cards | plus 1.5 percent | plus 1.5 percent |
| Subscription payment | no surcharge | plus 0.5 percent |
| PayPal payment | not supported | plus 1.5 percent |
| Lower rates for a monthly fee | yes, from 20 USD a month | no such option |
| Payout outside the US | 2 USD per month plus 0.25 percent and 25 cents plus conversion | 1 percent of the payout |
| Bank dispute | 15 USD | 15 USD |
| Owner | independent company | Stripe |
Take a 29 dollar subscription sold to a customer in Poland, with 23 percent VAT and a card issued outside the United States. The transaction value is 35.67 dollars. On Polar Starter the fee is 1.78 plus 0.50 plus 0.54, so 2.82 dollars. On Lemon Squeezy the 0.18 dollar subscription surcharge is added, so it comes to 3.00 dollars. The difference on a single transaction is 18 cents.
Over a month it grows, because payouts join in. At twenty such subscriptions, that is 580 dollars of base revenue, Polar takes 56.37 dollars in fees plus roughly 4.87 dollars for one payout to an account inside the European Union, so 61.24 dollars in total, or 10.6 percent. Lemon Squeezy takes 59.93 dollars in fees plus 5.20 dollars of payout cost, so 65.13 dollars, or 11.2 percent. The gain from moving is under four dollars a month and does not justify a migration on its own.
The picture changes with scale. At two hundred subscriptions, that is 5,800 dollars a month, the Growth plan costs 433.82 dollars in fees plus 100 dollars of subscription plus about 29 dollars of payout cost, so 562.90 dollars in total, or 9.7 percent. Lemon Squeezy under the same conditions costs 651.35 dollars, or 11.2 percent. The difference is 88 dollars a month and over a thousand a year. Only here does the calculation make sense.
The reverse has to be said too. At 580 dollars a month the Pro plan would cost 70.73 dollars instead of 61.24 on Starter, because a 20 dollar subscription does not pay for itself at that volume. A paid plan below the threshold is money lost, and the threshold depends on average basket value, not on turnover alone.
Integration in practice
Installation and environment choice. Polar offers a separate test environment and you switch to it with one parameter, without changing any URLs in the code.
npm install @polar-sh/sdk
npm install @polar-sh/nextjs
export POLAR_ACCESS_TOKEN="polar_oat_..."
export POLAR_WEBHOOK_SECRET="whsec_..."Creating a checkout session. The products field is required and takes an array of identifiers, the rest is optional. Schema defaults are allowDiscountCodes set to true and requireBillingAddress set to false.
import { Polar } from '@polar-sh/sdk'
const polar = new Polar({
accessToken: process.env.POLAR_ACCESS_TOKEN,
server: 'sandbox'
})
const checkout = await polar.checkouts.create({
products: ['5b2a0f4c-3f11-4a1e-9c33-2f0a4d8b1f77'],
successUrl: 'https://yourapp.com/thanks?checkout_id={CHECKOUT_ID}',
customerEmail: 'john@example.com',
externalCustomerId: 'user_8213',
allowDiscountCodes: true,
requireBillingAddress: false,
metadata: { plan: 'pro', source: 'pricing-page' }
})
console.log(checkout.url, checkout.clientSecret, checkout.totalAmount)Receiving events. The signature is verified by the Standard Webhooks specification, and validateEvent returns a parsed event or throws WebhookVerificationError. Event names use a dot form, among them order.paid, subscription.active, subscription.past_due, checkout.updated and customer.state_changed.
import { validateEvent, WebhookVerificationError } from '@polar-sh/sdk/webhooks'
export async function POST(request: Request) {
const body = await request.text()
const headers = Object.fromEntries(request.headers)
try {
const event = validateEvent(body, headers, process.env.POLAR_WEBHOOK_SECRET!)
if (event.type === 'order.paid') {
await grantAccess(event.data.customerId, event.data.productId)
}
return new Response('', { status: 202 })
} catch (error) {
if (error instanceof WebhookVerificationError) {
return new Response('', { status: 403 })
}
throw error
}
}License keys for a locally installed application. The validate method takes a required key and organizationId, and optionally activationId, benefitId, customerId, incrementUsage and conditions. The response carries among others status, usage, limitUsage, limitActivations, validations, lastValidatedAt and expiresAt.
const license = await polar.licenseKeys.validate({
key: 'POLAR-1A2B-3C4D-5E6F',
organizationId: 'e2ff1d55-84e2-4b47-9d9e-8b8a1c0f2a11',
incrementUsage: 1,
conditions: { major_version: 3 }
})
if (license.status !== 'granted') {
throw new Error('License inactive')
}
console.log(license.usage, license.limitUsage, license.expiresAt)If your sign-in already runs on Better Auth, the @polar-sh/better-auth package at 1.8.4 plugs in as a plugin and lists peer dependencies of @polar-sh/sdk in the ^0.47.0 range, better-auth in the ^1.4.12 range and zod. The @polar-sh/nextjs adapter at 0.9.6 requires Next in the ^15.0.0 || ^16.0.0 range and offers ready made Checkout, CustomerPortal and Webhooks helpers to mount on routes. Deploying to Vercel needs nothing beyond environment variables.
Common mistakes
Projecting revenue from the headline rate. Selling subscriptions into Europe, the real cost on the free plan lands around 10.6 percent rather than 5 percent, because the international card surcharge and the payout cost join in. For a forecast, use the number computed for your own case.
Buying a paid plan too early. A 20 dollar subscription pays for itself only above a threshold that, at an average basket of 40 dollars, sits at 1,379 dollars of monthly sales. Below that line the paid plan is more expensive than the free one.
Treating the breakeven thresholds as universal. The numbers given by the vendor assume an average transaction of about 40 dollars. With a cheaper product the fixed fee weighs more and the threshold moves upward.
Relying on the license field during a dependency audit. Two adapter packages declare no license in their metadata, even though the Apache 2.0 file sits inside the package. An automated tool will flag them as unknown and somebody will have to settle it by hand.
Installing the SDK with a version range. With a major number of zero, a breaking change may arrive in a minor release, which already happened when the generator changed after 0.6.0. Pin an exact version.
Assuming that Apache 2.0 on the server lets you escape the vendor. You can run the code yourself, but the merchant of record role cannot be copied, because it is a contract and a set of tax registrations, not a repository.
Ignoring the cost of disputes. Fifteen dollars per bank dispute on a 19 dollar product means that two disputes per hundred transactions eat the entire profit from five sales.
FAQ
Is Polar cheaper than Lemon Squeezy?
On the free plan only slightly, because both platforms charge 5 percent plus 50 cents and the same 1.5 percent surcharge for international cards, with the difference coming from the absent 0.5 percent subscription surcharge and cheaper payouts. At 580 dollars a month the saving is about 4 dollars. At 5,800 dollars on the Growth plan the gap grows to roughly 88 dollars a month.
What does version 0.49.0 mean for integration stability?
A major number of zero releases the author from any backward compatibility promise, so a breaking change may arrive in an ordinary minor release. That happened when the generator changed after 0.6.0. The 1.0.0-alpha.17 line exists but is an alpha under the next tag at the time of writing, while latest still publishes the 0.x line.
What license does the Polar server use?
The polarsource/polar repository carries the full Apache License 2.0 text in its LICENSE file. That is a permissive license with a patent clause, so without the restrictions typical of AGPL or SSPL. The TypeScript SDK is MIT, while some helper packages are Apache 2.0 or carry no declaration in npm metadata.
Does Polar support selling access to a private repository?
Yes, in the SDK the benefit type is called github_repository and after purchase the customer automatically receives an invitation to the chosen private repository. Alongside it you get license keys, downloadable files of up to 10 GB, Discord roles, feature flags and credits toward a usage meter.
What is the risk of committing to Polar for years?
The company is younger and smaller than Stripe or Paddle, the repository has around 10.2 thousand stars and the community is correspondingly smaller, so you will find fewer ready answers to an unusual problem. An SDK before version 1.0 adds to that. Keep order identifiers on your side and hide vendor calls behind your own interface, so that swapping the payment layer takes a day rather than a quarter.