### Set Up Database and Run Application
Source: https://github.com/freemius/freemius-js/blob/main/packages/saas-kit/CONTRIBUTING.md
Initialize the database using Docker and apply Prisma migrations. Then, install dependencies and start the development server. The application will be accessible at `http://localhost:3001`.
```bash
docker compose up -d
npx prisma migrate deploy
```
```bash
npm ci
npm run dev
```
--------------------------------
### Install Dependencies
Source: https://github.com/freemius/freemius-js/blob/main/README.md
Run this command after forking and cloning the repository to install project dependencies.
```bash
npm install
```
--------------------------------
### Copy Environment Example File
Source: https://github.com/freemius/freemius-js/blob/main/packages/saas-kit/CONTRIBUTING.md
Copy the example environment file to `.env` to set up your application's configuration. Fill in the required environment variables, including Freemius pricing IDs.
```bash
cp .env.example .env
```
--------------------------------
### Develop SaaS Kit
Source: https://github.com/freemius/freemius-js/blob/main/CONTRIBUTING.md
Start the development environment for the `@freemius/saas-kit` package. This command enables rapid iteration during development.
```bash
npm run dev --workspace=@freemius/saas-kit
```
--------------------------------
### Install Freemius Components with Bun
Source: https://github.com/freemius/freemius-js/blob/main/packages/saas-kit/README.md
Use this command to add Freemius components to your project using Bun. Ensure you have Bun installed.
```bash
bun x shadcn@latest add https://shadcn.freemius.com/all.json
```
--------------------------------
### Install Freemius Components with npx
Source: https://github.com/freemius/freemius-js/blob/main/packages/saas-kit/README.md
Use this command to add Freemius components to your project using npm. Ensure you have Node.js and npm installed.
```bash
npx shadcn@latest add https://shadcn.freemius.com/all.json
```
--------------------------------
### Install Freemius SDK and Dependencies
Source: https://github.com/freemius/freemius-js/blob/main/packages/sdk/README.md
Install the Freemius SDK along with Zod for schema validation. Requires Node.js 18+ or an Edge runtime with Web Crypto and standard fetch APIs.
```bash
npm install @freemius/sdk @freemius/checkout zod
```
--------------------------------
### Install Freemius Components with PNPM
Source: https://github.com/freemius/freemius-js/blob/main/packages/saas-kit/README.md
Use this command to add Freemius components to your project using PNPM. Ensure you have Node.js and PNPM installed.
```bash
pnpm dlx shadcn@latest add https://shadcn.freemius.com/all.json
```
--------------------------------
### Install Freemius Components with Yarn
Source: https://github.com/freemius/freemius-js/blob/main/packages/saas-kit/README.md
Use this command to add Freemius components to your project using Yarn. Ensure you have Node.js and Yarn installed.
```bash
yarn dlx shadcn@latest add https://shadcn.freemius.com/all.json
```
--------------------------------
### Develop SDK with Watch Mode
Source: https://github.com/freemius/freemius-js/blob/main/CONTRIBUTING.md
Start the development server for the `@freemius/sdk` package with watch mode enabled. Changes to the code will automatically trigger a rebuild.
```bash
npm run dev:sdk
```
--------------------------------
### Set up Webhook Listener with Node.js HTTP Server
Source: https://github.com/freemius/freemius-js/blob/main/packages/sdk/README.md
Listen for and securely process webhook events, such as license creation. This example demonstrates setting up a listener and integrating it with a Node.js HTTP server. Ensure the server is configured to handle POST requests to the '/webhook' endpoint.
```typescript
import { createServer } from 'node:http';
const listener = freemius.webhook.createListener();
listener.on('license.created', async ({ objects: { license } }) => {
// Persist or sync license state in your datastore
console.log('license.created', license.id);
});
const server = createServer(async (req, res) => {
if (req.url === '/webhook' && req.method === 'POST') {
await freemius.webhook.processNodeHttp(listener, req, res);
} else {
res.statusCode = 404;
res.end('Not Found');
}
});
server.listen(3000, () => {
console.log('Webhook listener active on :3000');
});
```
--------------------------------
### Freemius Webhook Listener Setup
Source: https://context7.com/freemius/freemius-js/llms.txt
Initializes a Freemius webhook listener to handle various events like subscription creation, license extension, cancellation, and expiration. Requires importing the freemius SDK instance.
```typescript
// app/api/webhook/route.ts - Webhook handler
import { freemius } from '@/lib/freemius';
const listener = freemius.webhook.createListener();
listener.on('subscription.created', async ({ objects: { user, license, subscription } }) => {
// New subscription - ensure entitlement exists
const purchase = await freemius.purchase.retrievePurchase(license.id!);
if (purchase) {
await db.entitlements.upsert(purchase.toEntitlementRecord());
}
});
listener.on('license.extended', async ({ data }) => {
// Subscription renewed - update expiration
await db.entitlements.updateExpiration(data.license_id, data.to);
});
listener.on('subscription.cancelled', async ({ data }) => {
// Mark as cancelled (keep access until expiration)
await db.entitlements.markCancelled(data.license_id);
});
listener.on('license.expired', async ({ data }) => {
// Access expired - revoke features
await db.entitlements.markExpired(data.license_id);
});
export async function POST(request: Request) {
return freemius.webhook.processFetch(listener, request);
}
```
--------------------------------
### Setup CheckoutProvider in Root Layout
Source: https://context7.com/freemius/freemius-js/llms.txt
Wrap your application with CheckoutProvider in your root layout file (e.g., app/layout.tsx or _app.tsx). This component requires checkout data fetched server-side and an API endpoint for processing.
```tsx
// app/layout.tsx or _app.tsx
import { CheckoutProvider } from '@/components/saas-kit/checkout-provider';
import { Freemius } from '@freemius/sdk';
// Server-side: Create checkout data
async function getCheckoutData() {
const freemius = new Freemius({
productId: Number(process.env.FREEMIUS_PRODUCT_ID),
apiKey: process.env.FREEMIUS_API_KEY!,
secretKey: process.env.FREEMIUS_SECRET_KEY!,
publicKey: process.env.FREEMIUS_PUBLIC_KEY!,
});
const session = await getSession(); // Your auth session
const checkout = await freemius.checkout.create({
user: session?.user,
isSandbox: process.env.NODE_ENV !== 'production'
});
return checkout.serialize();
}
export default async function RootLayout({ children }) {
const checkoutData = await getCheckoutData();
return (
{
// Custom sync logic
console.log('Purchase completed:', purchaseData);
}}
onBeforeSync={(purchaseData) => {
console.log('Starting sync for license:', purchaseData.purchase?.license_id);
}}
onAfterSync={(serverData) => {
console.log('Synced to server:', serverData);
// Redirect or update UI
}}
onError={(error) => {
console.error('Checkout error:', error);
}}
>
{children}
);
}
```
--------------------------------
### API Route for Customer Portal Data
Source: https://context7.com/freemius/freemius-js/llms.txt
Set up an API route to handle customer portal data retrieval and actions. This example demonstrates fetching portal data by email and handling subscription cancellations or purchase restorations.
```ts
import { Freemius } from '@freemius/sdk';
const freemius = new Freemius({ /* config */ });
export async function GET(request: Request) {
const session = await getSession();
if (!session?.user?.email) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const portalData = await freemius.customerPortal.retrieveDataByEmail({
email: session.user.email
});
return Response.json(portalData);
}
export async function POST(request: Request) {
const url = new URL(request.url);
const action = url.searchParams.get('action');
// Handle various portal actions
switch (action) {
case 'cancel_subscription':
const { subscriptionId, feedback, reasons } = await request.json();
const result = await freemius.api.subscription.cancel(
subscriptionId,
feedback,
reasons
);
return Response.json(result);
case 'restore_purchases':
const session = await getSession();
const purchases = await freemius.purchase.retrievePurchasesByEmail(
session.user.email
);
// Sync each purchase to your database
for (const purchase of purchases) {
await db.entitlements.upsert(purchase.toEntitlementRecord());
}
return Response.json(purchases.map(p => p.toData()));
default:
return Response.json({ error: 'Unknown action' }, { status: 400 });
}
}
```
--------------------------------
### Create Hosted Checkout URL and Overlay Options
Source: https://github.com/freemius/freemius-js/blob/main/packages/sdk/README.md
Use this to generate a hosted checkout URL for redirects or email links, or to retrieve options for embedding the checkout modal on your frontend. Ensure the `@freemius/checkout` package is installed for frontend usage.
```typescript
const checkout = await freemius.checkout.create();
checkout.setCoupon({ code: 'SAVE10' });
checkout.setTrial('paid');
const hostedUrl = checkout.getLink(); // Redirect user or generate email link
const overlayOptions = checkout.getOptions(); // Serialize & send to frontend for modal embed
```
--------------------------------
### Retrieve Subscription for a License
Source: https://context7.com/freemius/freemius-js/llms.txt
Gets the subscription details associated with a given license ID. Requires a valid license ID.
```typescript
const subscription = await freemius.api.license.retrieveSubscription(licenseId);
if (subscription) {
console.log(`Billing Cycle: ${subscription.billing_cycle}`);
console.log(`Next Payment: ${subscription.next_payment}`);
console.log(`Renewal Amount: $${subscription.renewal_amount}`);
}
```
--------------------------------
### Create Checkout Session API
Source: https://context7.com/freemius/freemius-js/llms.txt
Handles GET requests to create a new checkout session using the Freemius SDK. It retrieves user session and entitlement information to pre-fill checkout details.
```typescript
// app/api/checkout/route.ts - Checkout API
import { freemius } from '@/lib/freemius';
export async function GET(request: Request) {
const session = await getSession();
const entitlement = await getUserEntitlement(session?.user?.email);
const checkout = await freemius.checkout.create({
user: session?.user,
licenseId: entitlement?.fsLicenseId, // For upgrades
isSandbox: process.env.NODE_ENV !== 'production'
});
return Response.json(checkout.serialize());
}
```
--------------------------------
### Process Webhook in Next.js App Router
Source: https://context7.com/freemius/freemius-js/llms.txt
Provides an example of how to process incoming webhook requests within a Next.js App Router route handler (app/webhook/route.ts).
```typescript
// Next.js App Router (app/webhook/route.ts)
export async function POST(request: Request) {
return await freemius.webhook.processFetch(listener, request);
}
```
--------------------------------
### Retrieve Serializable Purchase Data
Source: https://context7.com/freemius/freemius-js/llms.txt
Get purchase data as a plain object, suitable for use in environments like Next.js server components where class instances might not be directly serializable.
```typescript
const purchaseData = await freemius.purchase.retrievePurchaseData(licenseId);
```
--------------------------------
### Initialize Freemius SDK Instance
Source: https://context7.com/freemius/freemius-js/llms.txt
Set up the server-side Freemius SDK instance using environment variables for product ID, API keys, and public keys. Ensure all necessary environment variables are defined.
```typescript
// lib/freemius.ts - Server-side SDK instance
import { Freemius } from '@freemius/sdk';
export const freemius = new Freemius({
productId: Number(process.env.FREEMIUS_PRODUCT_ID),
apiKey: process.env.FREEMIUS_API_KEY!,
secretKey: process.env.FREEMIUS_SECRET_KEY!,
publicKey: process.env.FREEMIUS_PUBLIC_KEY!,
});
```
--------------------------------
### SDK Initialization
Source: https://context7.com/freemius/freemius-js/llms.txt
Initialize the Freemius SDK with your product credentials. Ensure that your product ID, API key, secret key, and public key are securely stored as environment variables.
```APIDOC
## SDK Initialization
Initialize the Freemius SDK with your product credentials from the Freemius Developer Dashboard. The SDK requires product ID, API key, secret key, and public key stored securely as environment variables.
```typescript
import { Freemius } from '@freemius/sdk';
// Initialize with environment variables
const freemius = new Freemius({
productId: Number(process.env.FREEMIUS_PRODUCT_ID),
apiKey: process.env.FREEMIUS_API_KEY!,
secretKey: process.env.FREEMIUS_SECRET_KEY!,
publicKey: process.env.FREEMIUS_PUBLIC_KEY!,
});
// SDK provides access to these services:
// freemius.api - Direct API client for users, licenses, subscriptions, payments
// freemius.checkout - Checkout URL/options builder
// freemius.purchase - Purchase and subscription retrieval
// freemius.webhook - Webhook listener and processors
// freemius.pricing - Pricing data retrieval
// freemius.entitlement - Entitlement validation helpers
// freemius.customerPortal - Customer portal data and actions
```
```
--------------------------------
### Build Packages
Source: https://github.com/freemius/freemius-js/blob/main/RELEASE.md
Commands to build all packages, specific packages, or build the SDK in watch mode for development.
```bash
# Build all packages
npm run build
```
```bash
# Build specific package
npm run build:sdk
npm run build:saas-kit
```
```bash
# Build SDK in watch mode during development
npm run dev:sdk
```
--------------------------------
### Initialize Freemius SDK
Source: https://github.com/freemius/freemius-js/blob/main/packages/sdk/README.md
Initialize the Freemius SDK using your product ID, API key, secret key, and public key. Store these credentials in environment variables.
```env
FREEMIUS_PRODUCT_ID=12345
FREEMIUS_API_KEY=...
FREEMIUS_SECRET_KEY=...
FREEMIUS_PUBLIC_KEY=...
```
```typescript
import { Freemius } from '@freemius/sdk';
export const freemius = new Freemius({
productId: Number(process.env.FREEMIUS_PRODUCT_ID),
apiKey: process.env.FREEMIUS_API_KEY!,
secretKey: process.env.FREEMIUS_SECRET_KEY!,
publicKey: process.env.FREEMIUS_PUBLIC_KEY!,
});
```
--------------------------------
### Retrieve Active Subscriptions for a User
Source: https://context7.com/freemius/freemius-js/llms.txt
Get only the active subscriptions for a given user ID. This helps in managing ongoing services and renewals.
```typescript
const subscriptions = await freemius.purchase.retrieveSubscriptions(userId);
subscriptions.forEach(sub => {
console.log(`Subscription ${sub.subscriptionId}: $${sub.renewalAmount}/${sub.billingCycle}`);
});
```
--------------------------------
### Get Active Entitlement for a User
Source: https://github.com/freemius/freemius-js/blob/main/packages/sdk/README.md
Retrieve the active entitlement for a given user by querying their entitlements and then using the `getActive` function. This is useful for checking subscription status.
```typescript
async function getActiveEntitlement(userId: number) {
const entitlements = await db.entitlement.query({ userId, type: 'subscription' });
return freemius.entitlement.getActive(entitlements);
}
```
--------------------------------
### Build and Publish to npm
Source: https://github.com/freemius/freemius-js/blob/main/RELEASE.md
Executes the build process and publishes the packages to npm.
```bash
npm run release
```
--------------------------------
### Get User Entitlement Status
Source: https://context7.com/freemius/freemius-js/llms.txt
Retrieves the active entitlement for a given user email by querying the database and then using the Freemius SDK. This function is crucial for feature gating.
```typescript
// lib/entitlements.ts - Feature gating
export async function getUserEntitlement(email: string) {
const entitlements = await db.entitlements.findByEmail(email);
return freemius.entitlement.getActive(entitlements);
}
```
--------------------------------
### Build SaaS Kit Registry
Source: https://github.com/freemius/freemius-js/blob/main/CONTRIBUTING.md
Build the registry for the `@freemius/saas-kit` package. This command is typically used as part of the build process for the SaaS kit.
```bash
npm run build --workspace=@freemius/saas-kit
```
--------------------------------
### Create a Changeset
Source: https://github.com/freemius/freemius-js/blob/main/RELEASE.md
Use this command to create a new changeset file after making changes that require a version bump. It will prompt for package selection, change type, and a summary.
```bash
npx changeset
```
--------------------------------
### Handle Multiple License Events
Source: https://context7.com/freemius/freemius-js/llms.txt
Demonstrates handling multiple event types ('license.created', 'license.extended') with a single handler by passing an array of event names.
```typescript
// Handle multiple events with same handler
listener.on(['license.created', 'license.extended'], async (event) => {
console.log(`License event: ${event.type}`);
});
```
--------------------------------
### Create and Configure Freemius Checkout
Source: https://context7.com/freemius/freemius-js/llms.txt
Use this to create a basic or advanced checkout. Configure user details, plan, pricing, coupons, and appearance. The `isSandbox` option is useful for testing.
```typescript
const checkout = await freemius.checkout.create({
user: { email: 'customer@example.com', name: 'John Doe' },
planId: '1234',
isSandbox: process.env.NODE_ENV !== 'production'
});
const checkoutUrl = checkout.getLink();
console.log(`Redirect to: ${checkoutUrl}`);
const overlayOptions = checkout.getOptions();
```
```typescript
const advancedCheckout = await freemius.checkout.create();
advancedCheckout
.setUser({ email: 'user@example.com', firstName: 'Jane', lastName: 'Smith' }, true)
.setPlan('premium-plan-id')
.setPricing('pricing-id')
.setQuota(5) // Number of licenses
.setCoupon({ code: 'SAVE20', hideUI: false })
.setTrial('paid') // 'free', 'paid', or boolean
.setBillingCycle('annual', 'dropdown')
.setCurrency('auto', 'usd', true)
.setAppearance({
layout: 'horizontal',
formPosition: 'right',
fullscreen: true
})
.setSocialProofing({
showReviews: true,
showRefundBadge: true,
refundPolicyPosition: 'below_form'
})
.setDiscounts({
annual: true,
multisite: 'auto',
bundle: 'maximize'
})
.setLanguage('auto')
.setAffiliate(affiliateUserId)
.setImage('https://example.com/product-image.png');
const finalUrl = advancedCheckout.getLink();
const finalOptions = advancedCheckout.getOptions();
const serialized = advancedCheckout.serialize();
```
```typescript
const upgradeCheckout = await freemius.checkout.create({
licenseId: existingLicenseId // Automatically fetches authorization
});
const upgradeUrl = upgradeCheckout.getLink();
```
--------------------------------
### Retrieve and Process Pricing Data
Source: https://context7.com/freemius/freemius-js/llms.txt
Retrieve all pricing data for your product, including plans, features, and currency-specific pricing. Iterate through plans and their pricing details, and identify top-up plans.
```typescript
// Retrieve all pricing data
const pricing = await freemius.pricing.retrieve();
console.log(`Selling unit: ${pricing.sellingUnit.singular}/${pricing.sellingUnit.plural}`);
// Iterate through plans
pricing.plans.forEach(plan => {
console.log(`Plan: ${plan.title} (${plan.name})`);
console.log(` Description: ${plan.description}`);
console.log(` Featured: ${plan.is_featured}`);
console.log(` Features:`);
plan.features?.forEach(f => console.log(` - ${f.title}: ${f.value}`));
// Pricing per currency
plan.pricing?.forEach(p => {
console.log(` ${p.currency}: $${p.monthly_price}/mo, $${p.annual_price}/yr, $${p.lifetime_price} lifetime`);
});
});
// Find top-up plan (one-off purchases)
const topupPlan = pricing.topupPlan;
if (topupPlan) {
console.log(`Top-up plan: ${topupPlan.title}`);
}
// Retrieve with specific top-up plan ID
const pricingWithTopup = await freemius.pricing.retrieve(topupPlanId);
```
--------------------------------
### Reset Local Database
Source: https://github.com/freemius/freemius-js/blob/main/packages/saas-kit/CONTRIBUTING.md
Use this command to reset the local database to its initial state, which is useful during development.
```bash
npx prisma migrate reset
```
--------------------------------
### Handle License Creation Event
Source: https://context7.com/freemius/freemius-js/llms.txt
Registers a handler for the 'license.created' event. This handler logs new license details and syncs them to the database.
```typescript
listener.on('license.created', async ({ objects: { license, user }, data }) => {
console.log(`New license ${data.license_id} created`);
console.log(`User: ${user?.email}, Expires: ${data.expiration}`);
// Sync to your database
await db.licenses.create({
licenseId: license.id,
planId: license.plan_id,
userId: user?.id,
expiration: license.expiration
});
});
```
--------------------------------
### Retrieve Full Purchase Information
Source: https://context7.com/freemius/freemius-js/llms.txt
Fetch complete details about a purchase using its license ID. This includes user, license, and subscription data, useful for syncing with your database.
```typescript
const purchaseInfo = await freemius.purchase.retrievePurchase(licenseId);
if (purchaseInfo) {
console.log(`Customer: ${purchaseInfo.email}`);
console.log(`Plan: ${purchaseInfo.planId}, Pricing: ${purchaseInfo.pricingId}`);
console.log(`Billing Cycle: ${purchaseInfo.billingCycle}`);
console.log(`Is Active: ${purchaseInfo.isActive}`);
console.log(`Renewal Amount: $${purchaseInfo.renewalAmount} ${purchaseInfo.currency}`);
console.log(`Next Renewal: ${purchaseInfo.renewalDate}`);
if (purchaseInfo.isSubscription()) {
console.log('This is a subscription');
if (purchaseInfo.isAnnual()) console.log('Billed annually');
if (purchaseInfo.isMonthly()) console.log('Billed monthly');
} else if (purchaseInfo.isOneOff()) {
console.log('This is a one-time purchase');
}
const entitlementRecord = purchaseInfo.toEntitlementRecord({
userId: 'your-internal-user-id', // Add custom fields
createdBy: 'webhook'
});
await db.entitlements.insert(entitlementRecord);
}
```
--------------------------------
### License API
Source: https://context7.com/freemius/freemius-js/llms.txt
Retrieve license details, associated subscriptions, and generate checkout upgrade authorization tokens for existing license holders.
```APIDOC
## License API
### Description
Retrieve license details, associated subscriptions, and generate checkout upgrade authorization tokens for existing license holders.
### Methods
#### Retrieve License by ID
##### Endpoint
`GET /api/licenses/{licenseId}` (Conceptual endpoint based on usage)
##### Parameters
- **licenseId** (string) - Required - The unique identifier of the license.
##### Response Example
```json
{
"secret_key": "your_license_key",
"plan_id": "plan_123",
"pricing_id": "pricing_456",
"quota": 100,
"expiration": "2024-12-31T23:59:59Z",
"is_cancelled": false
}
```
#### Retrieve Multiple Licenses
##### Endpoint
`GET /api/licenses` (Conceptual endpoint based on usage)
##### Query Parameters
- **is_cancelled** (boolean) - Optional - Filter licenses by cancellation status.
- **count** (integer) - Optional - Number of licenses to retrieve.
- **offset** (integer) - Optional - Offset for pagination.
##### Response Example
```json
[
{
"secret_key": "license_key_1",
"plan_id": "plan_123",
"pricing_id": "pricing_456",
"quota": 100,
"expiration": "2024-12-31T23:59:59Z",
"is_cancelled": false
}
]
```
#### Retrieve Subscription Associated with a License
##### Endpoint
`GET /api/licenses/{licenseId}/subscription` (Conceptual endpoint based on usage)
##### Parameters
- **licenseId** (string) - Required - The unique identifier of the license.
##### Response Example
```json
{
"billing_cycle": "monthly",
"next_payment": "2024-07-15T00:00:00Z",
"renewal_amount": 29.99
}
```
#### Generate Checkout Upgrade Authorization
##### Endpoint
`POST /api/licenses/{licenseId}/checkout-upgrade-authorization` (Conceptual endpoint based on usage)
##### Parameters
- **licenseId** (string) - Required - The unique identifier of the license.
##### Response Example
```json
{
"auth_token": "your_checkout_auth_token"
}
```
```
--------------------------------
### Create and Configure Webhook Listener
Source: https://context7.com/freemius/freemius-js/llms.txt
Initializes a webhook listener with an error handler. Optionally, API verification can be used instead of signature headers.
```typescript
// Create webhook listener
const listener = freemius.webhook.createListener({
onError: async (error) => {
console.error('Webhook processing error:', error);
// Send to error tracking service
},
// Optional: Use API verification instead of signature header
// authenticationMethod: WebhookAuthenticationMethod.ApiVerification
});
```
--------------------------------
### Retrieve License Details
Source: https://context7.com/freemius/freemius-js/llms.txt
Fetches a specific license by its ID and logs its details. Requires a valid license ID.
```typescript
const license = await freemius.api.license.retrieve(licenseId);
if (license) {
console.log(`License Key: ${license.secret_key}`);
console.log(`Plan ID: ${license.plan_id}, Pricing ID: ${license.pricing_id}`);
console.log(`Quota: ${license.quota}, Expiration: ${license.expiration}`);
console.log(`Is Cancelled: ${license.is_cancelled}`);
}
```
--------------------------------
### Retrieve Multiple Licenses with Filters
Source: https://context7.com/freemius/freemius-js/llms.txt
Fetches a paginated list of licenses, allowing filtering by properties like 'is_cancelled'.
```typescript
const licenses = await freemius.api.license.retrieveMany(
{ is_cancelled: false },
{ count: 50, offset: 0 }
);
```
--------------------------------
### Use useCheckout Hook to Open Checkout Modal
Source: https://context7.com/freemius/freemius-js/llms.txt
Utilize the useCheckout hook within any component to trigger the checkout modal. Pass custom parameters to configure the checkout experience, such as plan, pricing, trial status, billing cycle, and coupons.
```tsx
'use client';
import { useCheckout } from '@/components/saas-kit/hooks/checkout';
import { Button } from '@/components/ui/button';
export function UpgradeButton({ planId, pricingId }) {
const checkout = useCheckout();
const handleUpgrade = () => {
checkout.open({
plan_id: planId,
pricing_id: pricingId,
trial: false,
// Override any checkout options
billing_cycle: 'annual',
coupon: 'UPGRADE20'
});
};
return (
);
}
```
```tsx
// Start a free trial
export function TrialButton({ planId }) {
const checkout = useCheckout();
return (
);
}
```
```tsx
// Paid trial with card required
export function PaidTrialButton({ planId }) {
const checkout = useCheckout();
return (
);
}
```
--------------------------------
### Fetch Pricing Metadata
Source: https://github.com/freemius/freemius-js/blob/main/packages/sdk/README.md
Retrieve pricing metadata, including plans and currencies, to build custom pricing tables on your website. This function should be called on the backend.
```typescript
async function fetchPricing() {
return await freemius.pricing.retrieve();
}
```
--------------------------------
### Process Webhook in Node.js HTTP Server
Source: https://context7.com/freemius/freemius-js/llms.txt
Shows how to integrate the webhook listener with a standard Node.js HTTP server, handling POST requests to the '/webhook' endpoint.
```typescript
// Node.js HTTP server
import { createServer } from 'http';
const server = createServer(async (req, res) => {
if (req.url === '/webhook' && req.method === 'POST') {
await freemius.webhook.processNodeHttp(listener, req, res);
} else {
res.statusCode = 404;
res.end('Not Found');
}
});
server.listen(3000, () => console.log('Webhook listener on :3000'));
```
--------------------------------
### Listener Management Methods
Source: https://context7.com/freemius/freemius-js/llms.txt
Demonstrates methods for inspecting and managing registered event handlers on the webhook listener, including retrieving event types, handler counts, and removing handlers.
```typescript
// Listener management
console.log(`Registered events: ${listener.getRegisteredEventTypes()}`);
console.log(`Handler count for license.created: ${listener.getHandlerCount('license.created')}`);
listener.off('license.created', myHandler); // Remove specific handler
listener.removeAll('license.created'); // Remove all handlers for event type
```
--------------------------------
### Generate Checkout Upgrade Authorization
Source: https://context7.com/freemius/freemius-js/llms.txt
Creates an authorization token for existing license holders to upgrade their subscription via checkout. Requires a valid license ID.
```typescript
// Generate checkout upgrade authorization for existing customer
const authToken = await freemius.api.license.retrieveCheckoutUpgradeAuthorization(licenseId);
// Use authToken in checkout builder to pre-authorize upgrade
```
--------------------------------
### Apply Changesets and Update Versions
Source: https://github.com/freemius/freemius-js/blob/main/RELEASE.md
Applies all pending changeset files to update package versions, CHANGELOG.md files, and remove consumed changeset files.
```bash
npx changeset version
```
--------------------------------
### Process Purchase and Update Entitlements API
Source: https://context7.com/freemius/freemius-js/llms.txt
Handles POST requests to process purchases, retrieve purchase details from Freemius, update user information in the database, and store entitlement records. Requires a valid 'action' query parameter.
```typescript
export async function POST(request: Request) {
const url = new URL(request.url);
const action = url.searchParams.get('action');
if (action === 'process_purchase') {
const data = await request.json();
const licenseId = data.purchase?.license_id || data.trial?.license_id;
const purchase = await freemius.purchase.retrievePurchase(licenseId);
if (!purchase) {
return Response.json({ error: 'Purchase not found' }, { status: 404 });
}
// Get or create user in your system
const session = await getSession();
const user = await db.users.upsert({
email: purchase.email,
name: `${purchase.firstName} ${purchase.lastName}`
});
// Store entitlement
await db.entitlements.upsert(purchase.toEntitlementRecord({
userId: user.id
}));
return Response.json(purchase.toData());
}
return Response.json({ error: 'Invalid action' }, { status: 400 });
}
```
--------------------------------
### Generate OpenAPI Types for SDK
Source: https://github.com/freemius/freemius-js/blob/main/CONTRIBUTING.md
Generate OpenAPI types specifically for the `@freemius/sdk` package. This command is useful for maintaining type safety with API definitions.
```bash
npm run openapi:generate --workspace=@freemius/sdk
```
--------------------------------
### Handle Subscription Creation Event
Source: https://context7.com/freemius/freemius-js/llms.txt
Registers a handler for the 'subscription.created' event. This handler logs new subscription details, including user email and renewal amount.
```typescript
listener.on('subscription.created', async ({ objects: { user, subscription, license }, data }) => {
console.log(`New subscription ${data.subscription_id} for license ${data.license_id}`);
console.log(`User: ${user.email}`);
console.log(`Amount: $${subscription.renewal_amount}/${subscription.billing_cycle}`);
});
```
--------------------------------
### Retrieve User Information
Source: https://context7.com/freemius/freemius-js/llms.txt
Retrieve user details by email or ID. You can also fetch their billing information, active subscriptions, licenses, and payment history.
```typescript
// Retrieve user by email
const user = await freemius.api.user.retrieveByEmail('customer@example.com');
if (user) {
console.log(`User ID: ${user.id}, Name: ${user.first} ${user.last}`);
}
```
```typescript
// Retrieve user by ID
const userById = await freemius.api.user.retrieve(123456);
```
```typescript
// Get user's billing information
const billing = await freemius.api.user.retrieveBilling(user.id!);
console.log(`Company: ${billing?.business_name}, Country: ${billing?.country_code}`);
```
```typescript
// Retrieve user's active subscriptions with discounts
const subscriptions = await freemius.api.user.retrieveSubscriptions(user.id!, {
filter: 'active'
});
subscriptions.forEach(sub => {
console.log(`Subscription ${sub.id}: $${sub.renewal_amount}/${sub.billing_cycle}`);
sub.discounts?.forEach(d => console.log(` Discount: ${d.discount}%`));
});
```
```typescript
// Get user's active licenses
const licenses = await freemius.api.user.retrieveLicenses(user.id!, { type: 'active' });
licenses.forEach(license => {
console.log(`License ${license.id}: Plan ${license.plan_id}, Expires: ${license.expiration}`);
});
```
```typescript
// Get user's payment history with pagination
const payments = await freemius.api.user.retrievePayments(user.id!, {},
{
count: 10,
offset: 0
});
payments.forEach(payment => {
console.log(`Payment ${payment.id}: $${payment.gross} on ${payment.created}`);
});
```
```typescript
// Download invoice PDF
const invoiceBlob = await freemius.api.user.retrieveInvoice(user.id!, paymentId);
if (invoiceBlob) {
// Save or send the PDF blob
const buffer = await invoiceBlob.arrayBuffer();
}
```
```typescript
// Update billing information
const updatedBilling = await freemius.api.user.updateBilling(user.id!, {
business_name: 'Acme Corp',
address_street: '123 Main St',
address_city: 'New York',
address_zip: '10001',
country_code: 'US'
});
```
--------------------------------
### Subscription API
Source: https://context7.com/freemius/freemius-js/llms.txt
Manage subscriptions including retrieval, cancellation, and applying renewal discount coupons.
```APIDOC
## Subscription API
### Description
Manage subscriptions including retrieval, cancellation, and applying renewal discount coupons.
### Methods
#### Retrieve Subscription by ID
##### Endpoint
`GET /api/subscriptions/{subscriptionId}` (Conceptual endpoint based on usage)
##### Parameters
- **subscriptionId** (string) - Required - The unique identifier of the subscription.
##### Response Example
```json
{
"id": "sub_abc",
"is_active": true,
"gateway": "stripe",
"initial_amount": 49.99,
"renewal_amount": 49.99
}
```
#### Retrieve Multiple Subscriptions
##### Endpoint
`GET /api/subscriptions` (Conceptual endpoint based on usage)
##### Query Parameters
- **is_active** (boolean) - Optional - Filter subscriptions by active status.
- **count** (integer) - Optional - Number of subscriptions to retrieve.
##### Response Example
```json
[
{
"id": "sub_abc",
"is_active": true,
"gateway": "stripe",
"initial_amount": 49.99,
"renewal_amount": 49.99
}
]
```
#### Cancel Subscription
##### Endpoint
`DELETE /api/subscriptions/{subscriptionId}` (Conceptual endpoint based on usage)
##### Parameters
- **subscriptionId** (string) - Required - The unique identifier of the subscription.
- **feedback** (string) - Optional - Customer's feedback for cancellation.
- **reason_ids** (array of strings) - Optional - IDs of cancellation reasons.
##### Request Body Example
```json
{
"feedback": "Switching to a competitor product",
"reason_ids": ["too_expensive", "missing_features"]
}
```
##### Response Example
```json
{
"id": "sub_abc",
"message": "Subscription cancelled successfully."
}
```
#### Apply Renewal Coupon
##### Endpoint
`POST /api/subscriptions/{subscriptionId}/apply-renewal-coupon` (Conceptual endpoint based on usage)
##### Parameters
- **subscriptionId** (string) - Required - The unique identifier of the subscription.
- **couponId** (string) - Required - The identifier of the coupon to apply.
- **log_auto_renew** (boolean) - Required - Whether to log auto-renewal.
##### Request Body Example
```json
{
"couponId": "SUMMER20",
"log_auto_renew": true
}
```
##### Response Example
```json
{
"id": "sub_abc",
"renewal_amount": 39.99,
"message": "Coupon applied successfully."
}
```
```
--------------------------------
### Retrieve All Purchases for a User
Source: https://context7.com/freemius/freemius-js/llms.txt
Fetch a list of all purchases associated with a specific user ID. Useful for displaying a user's purchase history.
```typescript
const userPurchases = await freemius.purchase.retrievePurchases(userId);
userPurchases.forEach(p => {
console.log(`License ${p.licenseId}: ${p.planId} - Active: ${p.isActive}`);
});
```
--------------------------------
### Define and Use Entitlement Data Structure
Source: https://context7.com/freemius/freemius-js/llms.txt
Define your entitlement data structure, ensuring it includes required Freemius fields. Then, retrieve user entitlements from your database and use helper functions to find active subscriptions and calculate their age.
```typescript
// Define your entitlement data structure (must include these fields)
interface MyEntitlement {
fsLicenseId: string;
fsPlanId: string;
fsPricingId: string;
fsUserId: string;
type: 'subscription' | 'oneoff';
expiration: Date | string | null;
createdAt: Date | string;
isCanceled: boolean;
// Your custom fields
userId: string;
tier: string;
}
// Get entitlements from your database
const userEntitlements = await db.entitlements.findByUserId(userId);
// Find the single active subscription entitlement
const activeEntitlement = freemius.entitlement.getActive(userEntitlements);
if (activeEntitlement) {
console.log(`Active plan: ${activeEntitlement.fsPlanId}`);
console.log(`Expires: ${activeEntitlement.expiration}`);
} else {
console.log('No active subscription found');
}
// Get all active entitlements (if you support multiple subscriptions)
const allActive = freemius.entitlement.getActives(userEntitlements);
allActive?.forEach(e => console.log(`Active: ${e.fsPlanId}`));
// Calculate subscription age for loyalty features
const monthsSubscribed = freemius.entitlement.getElapsedMonth(activeEntitlement);
const yearsSubscribed = freemius.entitlement.getElapsedYear(activeEntitlement);
if (yearsSubscribed >= 1) {
console.log(`Loyal customer! ${yearsSubscribed} years subscribed`);
// Grant loyalty benefits
}
// Get Freemius user info from entitlement (for checkout/portal)
const fsUser = freemius.entitlement.getFsUser(activeEntitlement, 'user@example.com');
// { email, id: fsUserId, primaryLicenseId: fsLicenseId }
```
--------------------------------
### Custom Pricing Table with Control
Source: https://context7.com/freemius/freemius-js/llms.txt
Implement a custom pricing table using `PricingTable` for more control over plan display and checkout behavior. Fetches pricing data using `usePricingData` hook. Supports 'free', 'paid', or `false` for the `trial` prop.
```tsx
'use client';
import { Subscribe } from '@/components/saas-kit/subscribe';
import { PricingTable, PricingTableItem } from '@/components/saas-kit/pricing-table';
import { usePricingData } from '@/components/saas-kit/hooks/data';
// Custom pricing table with more control
export function CustomPricingPage() {
const { data, isLoading, error } = usePricingData(null, true);
if (isLoading) return
Loading pricing...
;
if (error) return
Error loading pricing: {error.message}
;
return (
console.log('Checkout opened')}
/>
);
}
```
--------------------------------
### Retrieve Purchase Data by License ID
Source: https://github.com/freemius/freemius-js/blob/main/packages/sdk/README.md
Fetch detailed purchase information using a license ID. This function is intended for backend use to resolve purchase data or validate entitlement status. It returns purchase details or throws an error if the purchase is not found.
```typescript
async function retrievePurchase(licenseId: number) {
const purchase = await freemius.purchase.retrievePurchase(licenseId);
if (!purchase) throw new Error('Purchase not found');
return purchase;
}
const purchase = await retrievePurchase(123456);
if (purchase) {
db.entitlement.insert(purchase.toEntitlementRecord());
}
```
--------------------------------
### Display Pricing Plans with Subscribe Component
Source: https://context7.com/freemius/freemius-js/llms.txt
Use the Subscribe component for a simple display of pricing plans with automatic checkout integration. It fetches pricing data automatically. Track checkout initiation with the `onCheckout` callback.
```tsx
'use client';
import { Subscribe } from '@/components/saas-kit/subscribe';
import { PricingTable, PricingTableItem } from '@/components/saas-kit/pricing-table';
import { usePricingData } from '@/components/saas-kit/hooks/data';
// Simple subscribe component (fetches pricing automatically)
export function PricingPage() {
return (
);
}
```
--------------------------------
### Handle License Cancellation Event
Source: https://context7.com/freemius/freemius-js/llms.txt
Registers a handler for the 'license.cancelled' event. This handler logs the cancellation and marks the license as cancelled in the database.
```typescript
listener.on('license.cancelled', async ({ objects: { license }, data }) => {
console.log(`License ${data.license_id} cancelled`);
await db.licenses.update(data.license_id, { isCancelled: true });
});
```
--------------------------------
### Clean Build Artifacts
Source: https://github.com/freemius/freemius-js/blob/main/CONTRIBUTING.md
Remove generated build files and artifacts from the project. This is useful for ensuring a clean build environment.
```bash
npm run clean
```
--------------------------------
### Individual Pricing Card Component
Source: https://context7.com/freemius/freemius-js/llms.txt
Render a single pricing plan as a card using the `PricingTableItem` component. This is useful for highlighting a featured plan. The `plan` object structure is defined within the component.
```tsx
'use client';
import { Subscribe } from '@/components/saas-kit/subscribe';
import { PricingTable, PricingTableItem } from '@/components/saas-kit/pricing-table';
import { usePricingData } from '@/components/saas-kit/hooks/data';
// Individual pricing card
export function FeaturedPlanCard() {
const plan = {
id: 'pro-plan',
pricing_id: 'pro-monthly',
title: 'Professional',
description: 'Best for growing teams',
price: '$49',
featured: true,
features: [
{ title: 'Unlimited projects', value: '' },
{ title: 'Priority support', value: '24/7' },
{ title: 'Advanced analytics', value: '' },
{ title: 'Team collaboration', value: 'Up to 10 members' }
]
};
return (
console.log('Pro plan selected')}
/>
);
}
```
--------------------------------
### Run Linting
Source: https://github.com/freemius/freemius-js/blob/main/CONTRIBUTING.md
Perform code linting to enforce coding standards and identify potential issues. The `lint:fix` command attempts to automatically resolve linting errors.
```bash
npm run lint
```
```bash
npm run lint:fix
```
--------------------------------
### Run Type Checking
Source: https://github.com/freemius/freemius-js/blob/main/CONTRIBUTING.md
Execute TypeScript type checking for the entire project. This script ensures code quality and catches type-related errors.
```bash
npm run typecheck
```
--------------------------------
### Retrieve Subscription Details
Source: https://context7.com/freemius/freemius-js/llms.txt
Fetches a specific subscription by its ID and logs its details. Requires a valid subscription ID.
```typescript
// Retrieve subscription by ID
const subscription = await freemius.api.subscription.retrieve(subscriptionId);
if (subscription) {
console.log(`Status: ${subscription.is_active ? 'Active' : 'Inactive'}`);
console.log(`Gateway: ${subscription.gateway}`);
console.log(`Initial Amount: $${subscription.initial_amount}`);
console.log(`Renewal Amount: $${subscription.renewal_amount}`);
}
```
--------------------------------
### Find Purchases by Email
Source: https://context7.com/freemius/freemius-js/llms.txt
Search for purchases using a customer's email address. Provides methods to retrieve all purchases or just subscriptions by email.
```typescript
const purchasesByEmail = await freemius.purchase.retrievePurchasesByEmail('customer@example.com');
const subscriptionsByEmail = await freemius.purchase.retrieveSubscriptionsByEmail('customer@example.com');
```
--------------------------------
### API Route for Checkout Processing
Source: https://context7.com/freemius/freemius-js/llms.txt
Implement a POST API route to handle checkout processing. This endpoint receives purchase data, retrieves full purchase information using the Freemius SDK, and syncs it to your database.
```ts
// API route for checkout processing (app/api/checkout/route.ts)
import { Freemius } from '@freemius/sdk';
const freemius = new Freemius({ /* config */ });
export async function POST(request: Request) {
const url = new URL(request.url);
const action = url.searchParams.get('action');
if (action === 'process_purchase') {
const purchaseData = await request.json();
const licenseId = purchaseData.purchase?.license_id || purchaseData.trial?.license_id;
// Retrieve full purchase info and sync to database
const purchase = await freemius.purchase.retrievePurchase(licenseId);
if (purchase) {
await db.entitlements.upsert(purchase.toEntitlementRecord());
return Response.json(purchase.toData());
}
}
return Response.json({ error: 'Invalid action' }, { status: 400 });
}
```
--------------------------------
### Retrieve Multiple Subscriptions with Filters
Source: https://context7.com/freemius/freemius-js/llms.txt
Fetches a paginated list of subscriptions, allowing filtering by properties like 'is_active'.
```typescript
// Retrieve multiple subscriptions with filters
const activeSubscriptions = await freemius.api.subscription.retrieveMany(
{ is_active: true },
{ count: 100 }
);
```
--------------------------------
### Handle License Extension Event
Source: https://context7.com/freemius/freemius-js/llms.txt
Registers a handler for the 'license.extended' event. It logs the extension details and updates the expiration date in the database.
```typescript
listener.on('license.extended', async ({ objects: { license }, data }) => {
console.log(`License ${data.license_id} extended from ${data.from} to ${data.to}`);
if (data.is_renewal) {
console.log('Extended due to subscription renewal');
}
await db.licenses.update(data.license_id, { expiration: data.to });
});
```
--------------------------------
### Create Reusable Webhook Request Processor
Source: https://context7.com/freemius/freemius-js/llms.txt
Creates a reusable function to process webhook requests, which can be used in any environment compatible with the Fetch API.
```typescript
// Create reusable request processor
const processWebhook = freemius.webhook.createRequestProcessor(listener);
// Use in any Fetch API compatible environment
const response = await processWebhook(request);
```
--------------------------------
### Handle Subscription Cancellation Event
Source: https://context7.com/freemius/freemius-js/llms.txt
Registers a handler for the 'subscription.cancelled' event. It logs the subscription ID and any cancellation reasons provided.
```typescript
listener.on('subscription.cancelled', async ({ objects: { subscription }, data }) => {
console.log(`Subscription ${data.subscription_id} cancelled`);
console.log(`Reasons: ${data.reason_ids.join(', ')}`);
if (data.reason) console.log(`Other reason: ${data.reason}`);
});
```