);
}
```
## Retrieving Active Subscriptions of a User[](#retrieving-active-subscriptions-of-a-user "Direct link to Retrieving Active Subscriptions of a User")
For most SaaS use cases, you might only be interested in active subscriptions. There are two ways to retrieve only active subscriptions:
1. Use the `retrieveSubscriptions` method if you already have the Freemius User ID.
2. Use the `retrieveSubscriptionsByEmail` method if you only have the user's email address.
```
const subscriptionByUserId =
await freemius.purchase.retrieveSubscriptions(78910);
console.log('Subscriptions by User ID:', subscriptionByUserId);
const subscriptionsByEmail =
await freemius.purchase.retrieveSubscriptionsByEmail('jane@example.com');
console.log('Purchases by Email:', subscriptionsByEmail);
```
## Retrieving All Purchases for a User[](#retrieving-all-purchases-for-a-user "Direct link to Retrieving All Purchases for a User")
If you want to retrieve all purchases made by a user, you have two options:
1. If you already have the Freemius User ID available, use the `retrievePurchases` method.
2. If you only have the user's email address, use the `retrievePurchasesByEmail` method.
```
const purchasesByUserId = await freemius.purchase.retrievePurchases(1234);
console.log('Purchases by User ID:', purchasesByUserId);
const purchasesByEmail =
await freemius.purchase.retrievePurchasesByEmail('jane@example.com');
console.log('Purchases by Email:', purchasesByEmail);
```
This will retrieve all purchases associated with the user, including both one-off purchases and subscriptions. You can also pass pagination parameters to handle large datasets.
## Storing the Purchase Data on your local Database[](#storing-the-purchase-data-on-your-local-database "Direct link to Storing the Purchase Data on your local Database")
Regardless of how you choose to build the integration, you might want to store the purchase data in your own database. This approach provides easy access to determine whether a user has an active subscription, without needing to call the Freemius API on every request.
Integration Guide
Do not forget to check out our [Integration Guide](https://freemius.com/help/help/documentation/saas-sdk/js-sdk/integration/.md) for more details on how to integrate the SDK with your SaaS application that covers the recommended approach to store purchase information and work with entitlements.
The rest of the guide will show some code snippets that you can use as a reference for custom implementations.
The recommended data structure for storing purchase data is as follows:
```
export interface PurchaseEntitlementData {
/**
* The unique identifier of the user in the Freemius system.
*/
fsLicenseId: string;
/**
* The unique identifier of the plan in the Freemius system.
*/
fsPlanId: string;
/**
* The unique identifier of the pricing in the Freemius system.
*/
fsPricingId: string;
/**
* The unique identifier of the user in the Freemius system.
*/
fsUserId: string;
/**
* The type of entitlement, which can be either 'subscription' or 'oneoff'.
*/
type: PurchaseEntitlementType;
/**
* The expiration date of the entitlement. If `null`, the entitlement does not expire.
* If you are passing a string, then it must of the format "YYYY-MM-DD HH:mm:ss" in UTC.
*/
expiration: Date | null | string;
/**
* The date when the entitlement was created. This is useful for record-keeping and auditing purposes.
*/
createdAt: Date | string;
/**
* Indicates whether the entitlement has been canceled.
*/
isCanceled: boolean;
}
```
We do not have any strong recommendations regarding your choice of database or ORM. However, the `PurchaseInfo` object returned by the SDK has a `toEntitlementRecord` method that converts the object into a plain data structure that is easy to store in any database.
Here is an example of how you might store it in a [Prisma model](https://www.prisma.io/docs/orm/overview/introduction/what-is-prisma):
```
enum FsEntitlementType {
subscription
oneoff
}
// For the sake of this example, we are using a single active subscription license per user.
// But the schema can handle multiple licenses per user.
model UserFsEntitlement {
id String @id @default(cuid())
userId String
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
// The following matches the PurchaseDBData from Freemius Node SDK.
// You can also use these fields as BigInt if you prefer, and in that case you will need to convert the String to BigInt in your application.
fsLicenseId String @unique
fsPlanId String
fsPricingId String
fsUserId String
type FsEntitlementType
expiration DateTime?
isCanceled Boolean
createdAt DateTime
// Add the index to optimize queries filtering by type (as needed for the JS SDK).
@@index(type)
@@map("user_fs_entitlement")
}
```
In your application code, you can upsert the purchase data as follows:
```
// Retrieve the local user from your database
const localUserId = getLocalUserIdSomehow();
// Fetch the purchase from Freemius
const fsPurchase = await freemius.purchase.retrievePurchase(123456);
// Upsert the purchase data into your database
await prisma.userFsEntitlement.upsert({
where: {
fsLicenseId: fsPurchase.licenseId,
},
update: fsPurchase.toEntitlementRecord(),
create: fsPurchase.toEntitlementRecord({ userId: user.id }),
});
```
You will notice that we are storing the `expiration` and `isCanceled` fields. When retrieving the user's purchase data from your database, you can use these fields to determine whether the user has an active subscription.
```
const entitlement = await prisma.userFsEntitlement.findUnique({
where: { fsLicenseId: fsPurchase.licenseId },
});
const hasEntitlement =
entitlement &&
(!entitlement.expiration || entitlement.expiration > new Date()) &&
!entitlement.isCanceled;
```
However, our SDK also provides a convenience method.
```
const entitlement = await freemius.entitlement.getActive(
prisma.userFsEntitlement.findMany({ where: { userId, type: 'subscription' } })
);
// Will be `null` if not valid.
const hasEntitlement = null !== entitlement;
```
Next, you can use this `hasEntitlement` boolean to gate access to premium features in your SaaS application.
If you have a more advanced integration where features depend on the subscribed plan, you can use the `entitlement.fsPricingId` to determine which plan the user is subscribed to.
---
# Create API Endpoints needed for the React Starter Kit
After properly [integrating licensing and entitlements](https://freemius.com/help/help/documentation/saas-sdk/js-sdk/integration/.md) into your JavaScript application, you can now explore how to create the necessary API endpoints for the [React Starter Kit](https://freemius.com/help/help/documentation/saas-sdk/react-starter/.md).
The React Starter Kit allows you to quickly set up Checkouts, Paywalls, and the Customer Portal in your React application. However, to accomplish this, you need to create a few API endpoints that will handle communication between your frontend and the Freemius servers.
## Setting Up the Checkout Endpoint[](#setting-up-the-checkout-endpoint "Direct link to Setting Up the Checkout Endpoint")

You need to create an endpoint at `/api/checkout` to handle Checkout processing. The responsibilities of this endpoint include:
1. Provide a backend API for the [Overlay Checkout](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md) to process the purchase immediately.
2. Provide a redirection endpoint for the [Hosted Checkout](https://freemius.com/help/help/documentation/checkout/integration/hosted-checkout/.md#redirection-after-a-successful-purchase) to process the purchase and redirect the user back to our application.
3. Provide API endpoint for data retrieval for various [Pricing Table](https://freemius.com/help/help/documentation/saas-sdk/react-starter/components/.md#subscription-plans-table) components.
For this, you will need a method named `processRedirect` that calls the relevant functions from the SDK to retrieve authenticated purchase information and update the user's entitlement in your database. Place this method in the same `user-entitlement.ts` file you created earlier.
./src/lib/user-entitlement.ts
```
import { CheckoutRedirectInfo } from '@freemius/sdk';
export async function processRedirect(
info: CheckoutRedirectInfo
): Promise {
const purchaseInfo = await freemius.purchase.retrievePurchase(
info.license_id
);
if (purchaseInfo) {
await processPurchaseInfo(purchaseInfo);
}
}
```
Once you have that, you can create the actual endpoint. Below is an example of how to do this in a serverless function using the Fetch API. You can adapt it to your framework's routing and request handling mechanism.
```
/**
* This route handles the Purchase actions and sync actions coming from the Freemius React Starter Kit.
*/
import { freemius } from '@/lib/freemius'; // our freemius instance
import { processPurchaseInfo, processRedirect } from '@/lib/user-entitlement';
export default {
async fetch(request: Request) {
return await freemius.checkout.request.process(
{
onPurchase: processPurchaseInfo,
proxyUrl: process.env.PUBLIC_AP_URL!,
onRedirect: processRedirect,
afterProcessUrl: process.env.PUBLIC_AP_URL! + '/billing',
},
request
);
},
};
```
This endpoint will now handle both Overlay Checkout and Hosted Checkout redirections. Here are the details about the options passed to the `freemius.checkout.request.process` method:
* `onPurchase`: A callback function that is called when a purchase is made through the Overlay Checkout. Pass your `processPurchaseInfo` function to handle the purchase information and update the user's license in your database. The [React Starter Kit](https://freemius.com/help/help/documentation/saas-sdk/react-starter/.md) calls this endpoint with the appropriate data when a purchase is made through the Overlay Checkout.
* `proxyUrl`: The public URL of your application. Read more about it [here](https://freemius.com/help/help/documentation/saas-sdk/js-sdk/checkout/.md#fixing-proxy-urls).
* `onRedirect`: A callback function that is called when a user is redirected back to your application after a purchase through the [Hosted Checkout](https://freemius.com/help/help/documentation/checkout/integration/hosted-checkout/.md#redirection-after-a-successful-purchase).
* `afterProcessUrl`: The URL where the user will be redirected after the purchase has been processed. In this case, users are redirected to the `/billing` page of your application.
### Managing Responses After Redirection[](#managing-responses-after-redirection "Direct link to Managing Responses After Redirection")
The SDK also allows you to customize the response after processing the Hosted Checkout redirection. You can do this by returning a `Promise` from the `onRedirect` callback function. Here is an example:
```
export async function processRedirect(
info: CheckoutRedirectInfo
): Promise {
const purchaseInfo = await freemius.purchase.retrievePurchase(
info.license_id
);
if (purchaseInfo) {
await processPurchaseInfo(purchaseInfo);
}
// Redirect the user to the billing page after processing the purchase
return Response.redirect(process.env.PUBLIC_AP_URL! + '/billing', 302);
}
```
Now, you do not need to specify the `afterProcessUrl` option in the `freemius.checkout.request.process` method since you are handling the redirection yourself. Using the same approach, you can also render a custom HTML response or return JSON data if needed.
## Setting Up the Customer Portal Endpoint[](#setting-up-the-customer-portal-endpoint "Direct link to Setting Up the Customer Portal Endpoint")

You will need another endpoint at `/api/portal` to handle Customer Portal access. The [React Starter Kit](https://freemius.com/help/help/documentation/saas-sdk/react-starter/.md) includes an embeddable Customer Portal component, allowing you to display the Customer Portal directly within your application without redirecting users to a different page.
The component requires a backend API to retrieve data and process various actions. The responsibilities of this endpoint include:
1. Retrieve the currently logged in user's purchase data.
2. Provide secure links for various actions such as:
1. Downloading invoices.
2. Updating payment methods.
3. Canceling subscriptions.
4. Updating billing information.
3. Provide a way to restore purchases if needed.
To implement this endpoint, you need a method in your application that informs the Freemius JS SDK about the currently logged-in user. Create a method named `getFsUser` that returns the user's email address, Freemius User ID, and other relevant information. This is important because the Customer Portal needs to know which user's data to retrieve and manage.
Place this method in the same `user-entitlement.ts` file, and use the `getUserEntitlement` method you created earlier to obtain the user's entitlement information.
Your application will typically have its own authentication and user management system, so the exact code may differ. In this example, the application uses [better-auth](https://www.better-auth.com/), and the [methods](https://www.better-auth.com/docs/basic-usage#server-side-authentication) provided by it.
./src/lib/user-entitlement.ts
```
import { auth } from '@/lib/auth';
import { UserRetriever } from '@freemius/sdk';
/**
* Get the Freemius user for the current session.
*
* This is used by the Freemius SDK to identify the user.
*
* @returns The Freemius user or null if the user is not logged in.
*/
export const getFsUser: UserRetriever = async () => {
const session = await auth.api.getSession({
headers: await headers(),
});
const entitlement = session
? await getUserEntitlement(session.user.id)
: null;
const email = session?.user.email ?? undefined;
return freemius.entitlement.getFsUser(entitlement, email);
};
```
Now you can create the actual endpoint. Below is an example of how to do this in a serverless function using the Fetch API. You can adapt it to your framework's routing and request handling mechanism.
```
import { freemius } from '@/lib/freemius';
import { getFsUser, processPurchaseInfo } from '@/lib/user-entitlement';
export default {
async fetch(request: Request) {
return await freemius.customerPortal.request.process(
{
getUser: getFsUser,
portalEndpoint: process.env.NEXT_PUBLIC_APP_URL! + '/api/portal',
isSandbox: process.env.NODE_ENV !== 'production',
onRestore: freemius.customerPortal.createRestorer(processPurchaseInfo),
},
request
);
},
};
```
The following options are available for the `freemius.customerPortal.request.process` method:
* `getUser`: A function that returns the currently logged-in Freemius user. The SDK uses this to identify which user's data to retrieve and manage. Pass your `getFsUser` function here.
* `portalEndpoint`: The public URL of your Customer Portal endpoint. The SDK uses this to create authenticated links for various actions within the Customer Portal.
* `isSandbox`: A boolean indicating whether the application is running in a sandbox (development) environment. This is useful for testing purposes.
* `onRestore`: A callback function that is called when a user attempts to restore their purchases. Here, we use the helper method `freemius.customerPortal.createRestorer` to create a restorer function that uses our existing `processPurchaseInfo` method to update the user's license in our database.
This covers everything you need to set up the required API endpoints to use the React Starter Kit in your application. You can now proceed to implement the frontend components as described in the [React Starter Kit guide](https://freemius.com/help/help/documentation/saas-sdk/react-starter/.md).
Framework Guide
If you are using a specific framework, please check our dedicated [Framework Guides](https://freemius.com/help/help/documentation/saas-sdk/framework/.md) for more tailored instructions and examples.
### How the Purchase Restorer Works[](#how-the-purchase-restorer-works "Direct link to How the Purchase Restorer Works")

If the `getFsUser` function returns only an email address (for example, in cases where you do not have any Freemius license or purchase information in your local database), the Customer Portal will display a "Restore Purchases" button. When the user clicks this button, the `onRestore` callback function is triggered.
This function accepts a callback that can process an array of `PurchaseInfo` objects. Behind the scenes, the SDK will query the [Freemius API](https://freemius.com/help/help/documentation/saas-sdk/js-sdk/purchases/.md#retrieving-all-purchases-for-a-user) to fetch all purchases associated with the provided email address.
Using this information, you can then update your local database with the relevant license and purchase details. In our example, we use the existing `processPurchaseInfo` method to handle this.
Since `processPurchaseInfo` handles a single `PurchaseInfo` object, we use the helper method `freemius.customerPortal.createRestorer`, which takes care of iterating over the array of purchases and calling our method for each one.
## What's Next?[](#whats-next "Direct link to What's Next?")
Please head over to the [React Starter Kit guide](https://freemius.com/help/help/documentation/saas-sdk/react-starter/.md) to learn how to use the React components provided in the kit to create Checkouts, Paywalls, and the Customer Portal in your application.
Or continue with this series to learn more about different functionalities of the SDK.
---
# Creating Webhook Listeners with Freemius JS SDK
The Freemius SDK provides a robust system to listen and process webhook events from the Freemius platform. This guide explains how to set up, configure, and process webhooks in various environments, including Next.js, Node.js, and serverless platforms.
note
Check out the [installation guide](https://freemius.com/help/help/documentation/saas-sdk/js-sdk/installation/.md) if you haven't set up the SDK yet.
## Create a Webhook Listener[](#create-a-webhook-listener "Direct link to Create a Webhook Listener")
To create a webhook listener, use the `freemius.webhook.createListener` method.
```
const listener = freemius.webhook.createListener();
```
You can create multiple listeners if you want to handle different sets of events separately. However for most use cases, a single listener is sufficient.
## Register Event Handlers[](#register-event-handlers "Direct link to Register Event Handlers")
The `listener` object exposes an `on` method to register event handlers for specific Freemius events. Each handler receives the event data as a parameter.
```
listener.on('license.created', async ({ objects: { license } }) => {
await syncLicenseFromWebhook(license);
console.log('License created:', license);
});
listener.on(
'subscription.renewal.failed',
async ({ objects: { subscription } }) => {
await sendRenewalFailureEmail(subscription);
console.log('Subscription renewal failed:', subscription);
}
);
```
The payload of the callback function will be strictly typed based on the event type, providing you with full TypeScript support. You can register multiple handlers for the same event if needed.
tip
You can use TypeScript IntelliSense to explore all available methods on the `listener` object.
Handling Multiple Events
For events that has same payload, you can pass an array of event names to register the same handler for multiple events:
```
listener.on(
[
'license.created',
'license.extended',
'license.shortened',
'license.updated',
'license.cancelled',
'license.expired',
'license.plan.changed',
],
async ({ objects: { license } }) => {
await syncLicenseFromWebhook(license);
console.log('License updated/activated/deactivated:', license);
}
);
```
## Process Webhook Requests[](#process-webhook-requests "Direct link to Process Webhook Requests")
Once the listener is set up and handlers are registered, you need to process incoming webhook requests. The SDK provides two methods for this: `processFetch` for environments that support the Fetch API (like Next.js or Cloudflare Workers), and `processNodeHttp` for Node.js HTTP servers.
#### Using the Fetch API (e.g., Next.js, Cloudflare Workers)[](#using-the-fetch-api-eg-nextjs-cloudflare-workers "Direct link to Using the Fetch API (e.g., Next.js, Cloudflare Workers)")
The `processFetch` method processes requests in environments that support the Fetch API:
```
export async function POST(request: Request): Promise {
return await webhookService.processFetch(listener, request);
}
```
You can use it in Next.js API routes, serverless functions, or any environment that supports the Fetch API.
Easy Next.js Integration
In Next.js, you can reduce boilerplate by using the `createRequestProcessor` method:
```
const processor = freemius.webhook.createRequestProcessor(listener);
export { processor as POST };
```
For a more framework-specific example, see the [full Next.js integration guide](https://freemius.com/help/help/documentation/saas-sdk/framework/nextjs/.md).
#### Using Node.js HTTP[](#using-nodejs-http "Direct link to Using Node.js HTTP")
The `processNodeHttp` method processes requests in a Node.js HTTP server:
```
import { createServer } from 'http';
const server = createServer(async (req, res) => {
if (req.url === '/webhook') {
await webhookService.processNodeHttp(listener, req, res);
} else {
res.statusCode = 404;
res.end('Not Found');
}
});
server.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
```
## Authentication of Webhook Requests[](#authentication-of-webhook-requests "Direct link to Authentication of Webhook Requests")
The SDK automatically verifies the [authenticity of incoming webhook requests](https://freemius.com/help/help/documentation/saas/events-webhooks/.md#validating-webhook-signatures) using the signature sent in the `x-signature` header. This ensures that both the integrity and origin of the requests are validated.
However, in some environments—especially managed serverless platforms—the platform may not provide direct access to the raw request body, which is required for signature verification. In such cases, you can use the alternative authentication method by verifying the request against the Freemius API.
```
import { WebhookAuthenticationMethod } from '@freemius/sdk';
// This will use the Freemius API to authenticate the webhook request
const listener = freemius.webhook.createListener({
authenticationMethod: WebhookAuthenticationMethod.Api,
});
```
When using this authentication method, we recommend implementing additional security measures, such as providing a secret token in the webhook URL and validating it in your listener.
Here is an example of how to implement this:
```
export async function POST(request: Request): Promise {
const url = new URL(request.url);
const secretToken = url.searchParams.get('token');
// Validate the secret token
if (secretToken !== process.env.WEBHOOK_SECRET_TOKEN) {
return new Response('Unauthorized', { status: 401 });
}
return await webhookService.processFetch(listener, request);
}
```
Make sure to configure the webhook in the Freemius Dashboard using the URL with the token:
```
https://your-domain.com/webhook?token=your_secret_token
```
Note that the alternative authentication method may introduce additional latency due to the API call; use it only when necessary.
---
# React Starter Kit
The React Starter Kit gives software makers a head start with production-grade billing, subscription, and customer portal features—so you can launch, monetize, and scale your SaaS in minutes, not months.
Built with shadcn/ui and Tailwind CSS, this starter kit provides a various components for your SaaS application.
* **Checkout**: With the `CheckoutProvider` component, you can easily add the Freemius Checkout at any point in your React application.
* **Pricing Tables**: The `Subscribe` and `Topup` components allow you to display dynamic pricing tables and handle subscriptions and top-ups seamlessly.
* **Customer Portal**: The `CustomerPortal` component provides a secure way for your customers to manage their subscriptions and billing information.

All actions performed by the components are securely handled by the Freemius JS/TS SDK on your backend, ensuring that sensitive operations are not exposed to the client side.
---
# Various Components and Features
The React Starter Kit includes the following components. Please note that in all `import` statements, we assume you have set up the `@` [path alias](https://ui.shadcn.com/docs/components-json#aliases) in your project.
## Checkout Components[](#checkout-components "Direct link to Checkout Components")
You can use the following components to monetize your application with subscriptions or one-off purchases from Freemius.

### Checkout Provider[](#checkout-provider "Direct link to Checkout Provider")
All Checkout-related components, as well as the Customer Portal component, must be wrapped in the `CheckoutProvider` component, which provides the necessary context.
The component requires two props:
* The [endpoint](https://freemius.com/help/help/documentation/saas-sdk/react-starter/installation/.md#setting-up-the-required-api-endpoints) from which to obtain the checkout data.
* The [configuration options](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#arguments) to pass to Freemius Checkout.
```
import { type CheckoutOptions } from '@freemius/checkout';
import { CheckoutProvider } from '@/react-starter/components/checkout-provider';
const endpoint = process.env.PUBLIC_URL + '/api/checkout';
// @note - The `checkout` object must not change between renders to avoid unnecessary re-renders of the context provider.
const checkout: CheckoutSerialized = {
options: { product_id: process.env.PUBLIC_FREEMIUS_PRODUCT_ID! },
link: `https://checkout.freemius.com/${process.env.PUBLIC_FREEMIUS_PRODUCT_ID}/`,
};
export default function App() {
return (
{/* Your app components */}
);
}
```
Using the [JavaScript SDK](https://freemius.com/help/help/documentation/saas-sdk/js-sdk/checkout/.md#generating-checkout-options), you can also create the `checkout` object on your backend and pass it to your frontend.
In the example below, we show how to use `freemius.checkout` to create options on the server with user data.
```
import { freemius } from '@/lib/freemius';
import { auth } from '@/lib/auth';
export async function getServerSideProps() {
const session = await auth.api.getSession({
headers: await headers(),
});
const checkout = await freemius.checkout.create({
user: session?.user,
isSandbox: process.env.NODE_ENV !== 'production',
});
return {
props: {
checkout: checkout.serialize(),
},
};
}
```
To use Checkout anywhere in your application, you can leverage the `useCheckout` hook, which returns a [Freemius Checkout](https://github.com/Freemius/freemius-checkout-js/?tab=readme-ov-file#instantiate-the-class) object.
```
import { useCheckout } from '@/react-starter/hooks/checkout';
export default function SomeComponent() {
const checkout = useCheckout();
return ;
}
```
Customization
The `CheckoutProvider` component accepts various props to customize its behavior. See this [implementation example](https://github.com/Freemius/freemius-js/blob/main/packages/saas-kit/src/components/checkout-with-confetti-provider.tsx), which displays confetti on successful checkout.
Once the checkout is successful, the component will automatically send the post-purchase data to the backend, and your API handler can process it accordingly.

note
All of the components below must be used within the `CheckoutProvider` component.
### Paywall[](#paywall "Direct link to Paywall")
The `Paywall` component allows you to easily place a feature behind a paywall. The idea is that the UI responsible for the feature is not blocked, but when the user tries to use it, the paywall is displayed.
With the `Paywall` component, you can display an attractive pricing table tailored to subscriptions or one-off purchases. The component will appear in an overlay, allowing the user to either subscribe or top up their account.

Here's how to use it:
```
import { Button } from '@/components/ui/button';
import { Paywall, usePaywall } from '@/react-starter/components/paywall';
export default function PaywallDemo() {
const { state, showNoActivePurchase, showInsufficientCredits, hidePaywall } =
usePaywall();
return (
<>
>
);
}
```
While the example above shows how to trigger the paywall, a more real-world use case can be [found here](https://github.com/Freemius/freemius-js/blob/main/packages/saas-kit/src/app/chat/ai-app.tsx).
### Subscription Plans Table[](#subscription-plans-table "Direct link to Subscription Plans Table")
The `Subscribe` component allows you to display a dynamic pricing table for subscription plans. It fetches the available plans from the backend and manages the subscription process.

```
import { Subscribe } from '@/react-starter/components/subscribe';
export default function PricingPage() {
return ;
}
```
### One-off Purchase Table[](#one-off-purchase-table "Direct link to One-off Purchase Table")
If you are selling consumable units (such as credits) or one-off purchases, you can use the `Topup` component to display a dynamic pricing table for those.

```
import { Topup } from '@/react-starter/components/topup';
export default function PricingPage() {
return ;
}
```
### Creating Custom Checkout Buttons[](#creating-custom-checkout-buttons "Direct link to Creating Custom Checkout Buttons")
After wrapping your application with the `CheckoutProvider`, you can create custom checkout buttons using the `useCheckout` hook. The example below shows two buttons: one opens the overlay in trial mode, and the other opens it in purchase mode:
```
export default function PurchasePage() {
const checkout = useCheckout();
return (
-
);
}
```
Full list of options that can be passed to the `checkout.open()` method can be found [here](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md).

For a more comprehensive example like the screenshot above, refer to this [example implementation](https://github.com/Freemius/freemius-js/blob/main/packages/saas-kit/src/app/custom-checkout/checkout-demo.tsx).
## Customer Portal Component[](#customer-portal-component "Direct link to Customer Portal Component")
The `CustomerPortal` component provides a secure way for your customers to manage their subscriptions and billing information. It offers the following features:
1. Display current active subscriptions.
2. Manage payment methods.
3. Upgrade or downgrade subscriptions.
4. Cancel subscriptions. The mechanism includes a cancellation survey and feedback form, and also provides an option to reduce churn by offering a discount.
5. Manage billing information.
6. Download invoices.

Cancellation Coupon
To reduce churn, we recommend offering a [discount coupon](https://freemius.com/help/help/documentation/marketing-automation/special-coupons-discounts/.md#subscription-cancellation-coupon). The Customer Portal will automatically offer to apply the coupon when a user tries to cancel their subscription.
Using the component is straightforward. It only requires an [`endpoint`](https://freemius.com/help/help/documentation/saas-sdk/react-starter/installation/.md#setting-up-the-required-api-endpoints) prop, which is the API endpoint that serves the Customer Portal data.
```
import { CustomerPortal } from '@/react-starter/components/customer-portal';
import { CheckoutProvider } from '@/react-starter/components/checkout-provider';
import { type CheckoutOptions } from '@freemius/checkout';
const checkoutEndpoint = process.env.PUBLIC_URL + '/api/checkout';
const checkoutOptions: CheckoutOptions = {
product_id: process.env.PUBLIC_FREEMIUS_PRODUCT_ID!,
};
const portalEndpoint = process.env.PUBLIC_URL + '/api/portal';
export default function AccountPage() {
return (
);
}
```
Please note that you must wrap the `CustomerPortal` component within the `CheckoutProvider` component to provide the necessary context.
### Subscription & Upgrade Flow[](#subscription--upgrade-flow "Direct link to Subscription & Upgrade Flow")
The **Current Subscription** section displays the currently active subscription for the user.

Single Subscription Per User
Freemius allows you to [limit a single active subscription per user](https://freemius.com/help/help/documentation/saas/saas-integration/.md#restricting-or-relaxing-single-subscription-per-user), providing a better experience for your customers.
The UI allows the user to upgrade or downgrade the subscription to another plan.

### Cancellation Flow[](#cancellation-flow "Direct link to Cancellation Flow")
When the user clicks the **Cancel subscription** button, a wizard is displayed to guide the user through the cancellation process. It starts with a disclaimer:

If a discount coupon is configured for cancellations, the user is offered the option to apply it:

Finally, the user is asked to provide feedback on why they are cancelling:

The last step is optional, and the user can skip the feedback and cancel the subscription directly.
Once the cancellation is confirmed, the subscription is canceled immediately, but the license remains active until the end of the current billing cycle.
The UI also displays the canceled subscription with the end date:

### Billing Information[](#billing-information "Direct link to Billing Information")
The component includes a built-in billing information UI.

Your customers can update their billing information directly from the Customer Portal by clicking the **Update** button.

The changes are reflected in their invoice.
### Payments and Invoices[](#payments-and-invoices "Direct link to Payments and Invoices")
The last section of the portal displays all the payments the customer has made.

They can click the **Invoice** button to download the invoice for each payment.
---
# Installing the Starter Kit and Integrating with JS SDK
The Freemius React Starter Kit is built with [shadcn/ui](https://ui.shadcn.com/) and [Tailwind CSS](https://tailwindcss.com/). It provides a variety of components to help you quickly set up billing, subscriptions, and customer portal features in your SaaS application.
## Installing the React Starter Kit Components[](#installing-the-react-starter-kit-components "Direct link to Installing the React Starter Kit Components")
To get started, make sure you have already installed and configured shadcn/ui in your application. Detailed instructions are available [here](https://ui.shadcn.com/docs/installation).
Once you have shadcn/ui set up, you can install the React Starter Kit components using the package manager of your choice.
* npm
* Yarn
* pnpm
* Bun
```
npx shadcn@latest add https://shadcn.freemius.com/all.json
```
```
yarn dlx shadcn@latest add https://shadcn.freemius.com/all.json
```
```
pnpm dlx shadcn@latest add https://shadcn.freemius.com/all.json
```
```
bun x shadcn@latest add https://shadcn.freemius.com/all.json
```
This command will install all necessary components and dependencies. Since the components are based on shadcn/ui, you will receive the actual component files, which you can customize as needed. The installed files can be found in the `react-starter` directory of your project.
```
react-starter/
├─ components/
├─ hooks/
├─ icons/
└─ utils/
```
The installation process will also add the [Freemius JavaScript SDK](https://freemius.com/help/help/documentation/saas-sdk/js-sdk/installation/.md) and the [Freemius Checkout JS](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md) libraries as dependencies.
Decoupling Application Logic
Currently, various pieces of application logic are written directly in the `hooks` and some of the `components`. In the future, we will release a more decoupled version in which the application logic will be separated from the UI components. This will allow you to use your own components while still leveraging the underlying functionality provided by the React Starter Kit.
## Setting Up the Required API Endpoints[](#setting-up-the-required-api-endpoints "Direct link to Setting Up the Required API Endpoints")
The React Starter Kit components rely on two specific API endpoints—`/api/checkout` and `/api/portal`—to function properly. These endpoints are used to load data and perform actions securely on the backend.
The [Freemius JavaScript SDK](https://freemius.com/help/help/documentation/saas-sdk/js-sdk/.md) makes it very easy to set up these endpoints. Please follow the instructions in the [JS SDK documentation](https://freemius.com/help/help/documentation/saas-sdk/js-sdk/starter-kit-api-endpoints/.md) to complete the initial setup.
Framework Integration
If you are using a specific framework, such as Next.js, please refer to our [Framework Integration](https://freemius.com/help/help/documentation/saas-sdk/framework/.md) guide for tailored instructions.
You are not required to create the endpoints at the exact paths mentioned above; however, if you choose to use different paths, you will need to update the `CheckoutProvider` and `CustomerPortal` components to point to the correct endpoints.
## Using the Components[](#using-the-components "Direct link to Using the Components")
Now, various [components](https://freemius.com/help/help/documentation/saas-sdk/react-starter/components/.md) are available for you to use in your application. You can import and use them as needed.
```
import { type CheckoutSerialized } from '@freemius/checkout';
import { CheckoutProvider } from '@/react-starter/components/checkout-provider';
import { Subscribe } from '@/react-starter/components/subscribe';
const endpoint = process.env.PUBLIC_URL + '/api/checkout';
const checkout: CheckoutSerialized = {
options: { product_id: process.env.PUBLIC_FREEMIUS_PRODUCT_ID! },
link: `https://checkout.freemius.com/${process.env.PUBLIC_FREEMIUS_PRODUCT_ID}/`,
};
export default function PricingPage() {
return (
);
}
```
---
# Changing the Text or Language of the Components
All React Starter Kit components use a `LocaleProvider` context to manage the text they display. You can find the provider in the `react-starter/utils/locale.tsx` file ([source code](https://github.com/Freemius/freemius-js/blob/main/packages/saas-kit/registry/saas-kit/utils/locale.tsx)).
We recommend the following approach to change the text or language of the components:
## Wrap your app with the `LocaleProvider`[](#wrap-your-app-with-the-localeprovider "Direct link to wrap-your-app-with-the-localeprovider")
```
import {
LocaleProvider,
type LocaleType,
defaultLocale,
} from '@/react-starter/utils/locale';
const overrides: Partial = {
checkout: {
...defaultLocale.checkout,
processing: () => <>Processing... Please wait.>,
},
};
export default function App() {
return (
{/* Your app components */}
);
}
```
This way, you can use the default locale and override only the strings or fragments you want to change.
tip
You will notice that most locale strings are JSX elements (e.g., `React.ReactNode`), not just plain strings. This allows for greater flexibility, such as adding links or formatting.
## Using a Translation Library[](#using-a-translation-library "Direct link to Using a Translation Library")
Because the locale strings are JSX elements, you can easily integrate any translation library of your choice. Here are examples with the popular [lingui](https://lingui.dev/tutorials/react/) and [react-i18next](https://react.i18next.com/getting-started) libraries.
* lingui
* react-18next
```
import { Trans } from '@lingui/react/macro';
import {
LocaleProvider,
type LocaleType,
defaultLocale,
} from '@/react-starter/utils/locale';
const overrides: Partial = {
checkout: {
...defaultLocale.checkout,
processing: () => Processing... Please wait.,
},
};
```
Now you can use the `lingui` CLI tools to [extract](https://lingui.dev/ref/cli#extract) and manage your translations.
For the react-i18next example, you can use the `useTranslation` hook to get the `t` function and translate the strings. Note that we use the `useMemo` hook to avoid unnecessary re-renders.
```
import { useTranslation } from 'react-i18next';
import * as React from 'react';
import {
LocaleProvider,
type LocaleType,
defaultLocale,
} from '@/react-starter/utils/locale';
export default function App() {
const { t } = useTranslation();
const overrides: Partial = React.useMemo(
() => ({
checkout: {
...defaultLocale.checkout,
processing: () => <>{t('Processing... Please wait.')}>,
},
}),
[t, defaultLocale]
);
return (
{/* Your app components */}
);
}
```
## Editing the Component Source Code[](#editing-the-component-source-code "Direct link to Editing the Component Source Code")
warning
This approach is not recommended, as it will make it more difficult to update the components in the future.
If, for some reason, you cannot use the `LocaleProvider`, you can edit the source code of the components directly. The starter kit is based on [shadcn/ui](https://ui.shadcn.com/), so you will receive the actual component files that you can customize as needed.
---
# Setup for SaaS and Apps
Freemius makes it easy to monetize and license SaaS products, desktop apps, browser extensions, and other software.
This section walks you through the full setup: creating products and plans, and integrating Freemius into your software, pricing, and billing.
For apps and downloadable software, you’ll also learn how license keys are generated and managed to protect your product and give customers a smooth experience.
Whether you’re launching your first SaaS or migrating from another provider, this section gives you the steps to get started with confidence.
---
# App Integration
This guide covers integrating an application that includes a license activation flow. Examples include desktop apps (macOS or Windows), Chrome extensions, Electron apps, and more.
## Initial Steps[](#initial-steps "Direct link to Initial Steps")

1. [Sign up for Freemius](https://dashboard.freemius.com/register/)
2. Create a new App product
3. Configure your [plans and pricing](https://freemius.com/help/help/documentation/wordpress/setup-product-pricing-plans-refunds/.md) in the Plans section
tip
The unit label is customizable. [Learn more](https://freemius.com/help/help/documentation/saas/customize-license-unit-label/.md)
## Checkout Integration[](#checkout-integration "Direct link to Checkout Integration")
There are multiple ways to integrate Checkout within your marketing page or directly within your app.
### Marketing or Pricing Page[](#marketing-or-pricing-page "Direct link to Marketing or Pricing Page")
Use the Freemius [Checkout JavaScript SDK](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md) or [Buy Button](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md) to embed the checkout. The flow is as follows:
1. Buyers purchase from your website.
2. Freemius sends them a license key.
3. They use the license key to activate your app.
### In-App Purchase[](#in-app-purchase "Direct link to In-App Purchase")
If your app is web-based, you can use the same [Buy Button API](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md). We also provide an [NPM package](https://www.npmjs.com/package/@freemius/checkout) to load the Checkout.
For other cases, you can open the [Freemius Checkout directly](https://freemius.com/help/help/documentation/checkout/integration/hosted-checkout/.md). To generate checkout links, go to the Plans page and click on the "**Get Checkout**" button. Here’s an example link:
```
https://checkout.freemius.com/app/{product_id}/plan/{plan_id}/
```
### Testing Checkout Integration[](#testing-checkout-integration "Direct link to Testing Checkout Integration")
Freemius allows you to make sandbox purchases. If you just want to make a purchase:

1. Go to Plans and click on "Get Checkout" button.
2. Select the Sandbox link.
3. Fill the form and use any [test card](https://freemius.com/help/help/documentation/checkout/integration/testing/.md#testing-credit-cards) or [test PayPal account](https://freemius.com/help/help/documentation/checkout/integration/testing/.md#testing-paypal-accounts).
4. Make sure to use an email address you have access to.
5. Once the purchase is made, you will receive an email with a license key.
If you want to test sandbox payments within your application, you can use the following logic to generate the sandbox token.
```
import crypto from 'crypto';
const productId = '<>';
const planId = '<>';
const productPublicKey = '<>';
const productSecretKey = '<>';
const timestamp = Math.floor(Date.now() / 1000);
const sandboxToken = crypto
.createHash('md5')
.update(
`${timestamp}${productId}${productSecretKey}${productPublicKey}checkout`
)
.digest('hex');
// If using direct links
const checkoutLink = `https://checkout.freemius.com/app/${productId}/plan/${planId}/?sandbox=${sandboxToken}&s_ctx_ts=${timestamp}`;
console.log(checkoutLink);
// If using the Buy Button API
fsCheckout.open({
sandbox: {
token: sandboxToken,
ctx: timestamp,
},
});
```
### Integrating Users[](#integrating-users "Direct link to Integrating Users")
If you already have the user's email address from your app or website, pass the following parameters in the Buy Button SDK:
1. `user_email`: The user's email address
2. `readonly_user`: true
For a direct checkout link, add these parameters as query strings:
```
https://checkout.freemius.com/app/{product_id}/plan/{plan_id}/?user_email={email}&readonly_user=true
```
The checkout will enforce the provided email address when the user makes a purchase.
## License Activation[](#license-activation "Direct link to License Activation")

Upon purchase, Freemius sends the buyer a license key via email.
* If using in-app Checkout integration, you can auto-activate the license after purchase.
* Otherwise, provide a UI for users to enter their license key manually.
Once the key is entered, follow [this guide](https://freemius.com/help/help/documentation/saas/integrating-license-key-activation/.md) to learn how to activate and manage licenses from your app.
* [License activation](https://freemius.com/help/help/documentation/saas/integrating-license-key-activation/.md#license-activation)
* [License deactivation](https://freemius.com/help/help/documentation/saas/integrating-license-key-activation/.md#license-deactivation)
* [License validation](https://freemius.com/help/help/documentation/saas/integrating-license-key-activation/.md#license-validation)
## Synchronizing Application State with Freemius[](#synchronizing-application-state-with-freemius "Direct link to Synchronizing Application State with Freemius")
When a license is activated, Freemius creates an `Install` entity and increases the license’s activation counter by 1. The `Install` entity contains several useful properties that you can update from your application to keep it synchronized with Freemius. Keeping these details up to date allows Freemius to display accurate analytics in the Developer Dashboard.
### Updatable Properties[](#updatable-properties "Direct link to Updatable Properties")
You can update the following properties of the `Install` entity:
* **`version`** – The version of your application.
* **`title`** – A descriptive identifier for the activation, such as `user-foo@example.com - Windows 10`.
* **`country_code`** – A two-letter ISO 3166-1 alpha-2 country code.
* **`language`** – The language in which your application is used, e.g., `en-GB`.
* **`platform_version`** – The platform version, preferably in semantic versioning format. For example, in the case of Chrome: `134.0.6998`.
* **`sdk_version`** – If your app uses an SDK, you can track its version here using semantic versioning.
* **`programming_language_version`** – If your app depends on the client’s programming language version (e.g., Node.js for a CLI app), specify it here.
* **`is_beta`** - Indicates whether the user has agreed to install a beta version of your app. Use this to determine whether to push beta or stable updates.
### Updating Install Properties via API[](#updating-install-properties-via-api "Direct link to Updating Install Properties via API")
To update these properties, you need the `install_id` and `install_api_token` received when the license was first [activated from your app](https://freemius.com/help/help/documentation/saas/integrating-license-key-activation/.md#license-activation).
Make a `PUT` request to the Freemius API with a JSON body containing the updated properties. Use the `install_api_token` as the Bearer authentication token.
```
PUT /v1/installs/{install_id}.json
Host: api.freemius.com
Content-Type: application/json
Authorization: Bearer {install_api_token}
{
"version": "1.2.3",
"title": "user-foo@example.com - Windows 10",
"country_code": "US",
"language": "en-US",
"platform_version": "134.0.6998",
"sdk_version": "2.1.0",
"programming_language_version": "18.16.0"
}
```
To avoid hitting rate limits, ensure responsible API usage. We recommend updating the Install entity **once a day** or only when a significant event occurs, such as a new version installation.
## Handling License Upgrades[](#handling-license-upgrades "Direct link to Handling License Upgrades")
Freemius allows your users to upgrade their plan or quota at any time. The upgrade process will go through Freemius Checkout again, which collects up-to-date information and, once successful, updates the existing license.
There are two methods for generating license upgrade links:
### Upgrade link using the License Key[](#upgrade-link-using-the-license-key "Direct link to Upgrade link using the License Key")
If your app stores the license key, generating the upgrade link is straightforward. The URL structure is:
```
https://checkout.freemius.com/product/{product_id}/plan/{new_plan_id}/?license_key={license_key}&pricing_id={pricing_id}
```
You can use either the same or a different plan ID when generating the URL. Make sure to URL-encode any query parameters. You can find the plan and pricing IDs in the **Developer Dashboard** → **Plans** page.
### Upgrade link via the Freemius API[](#upgrade-link-via-the-freemius-api "Direct link to Upgrade link via the Freemius API")
To get upgrade links from Freemius API, you'll need to make one authenticated request. Our recommended approach is:
1. Have your app send a request to your server with the license information to generate the upgrade link.
2. Your server makes an API call to Freemius and sends the upgrade URL back to your app.
3. Your app then opens the upgrade link in a browser or an integrated modal.
Below is the API endpoint and an example of how to call it:
```
POST /v1/products/{product_id}/licenses/{license_id}/checkout/link.json HTTP/1.1
Host: api.freemius.com
Content-Type: application/json
Accept: application/json
Authorization: Bearer 123
Content-Length: 127
{
"plan_id": "planID",
"billing_cycle": "annual",
"quota": 1,
"currency": "usd"
}
```
The API will return an object with a `url` property — the link to [Freemius Checkout](https://freemius.com/help/help/documentation/checkout/integration/hosted-checkout/.md) — and a `settings` property containing the [modal Checkout configuration](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md).
Once the upgrade is complete, you can leverage the webhook mechanism to synchronize the license.
## Webhook Integration (Optional)[](#webhook-integration-optional "Direct link to Webhook Integration (Optional)")
For more advanced workflows, you can integrate with Freemius' [Webhook](https://freemius.com/help/help/documentation/saas/events-webhooks/.md). There are an endless number of things you can do with webhooks; here are a few examples:
### Auto activate license[](#auto-activate-license "Direct link to Auto activate license")
1. A user makes a purchase from your application.
2. Your webhook receives a `license.created` event with the license and buyer details.
3. Your webhook sends a push to the relevant connected application.
4. The application gets and activates the license.
### Sync license with application[](#sync-license-with-application "Direct link to Sync license with application")
1. Your webhook receives a `license.plan.changed`, `license.extended`, `license.shortened`, `license.expired`, `license.deleted`, `license.cancelled` event.
2. It sends a push to the relevant connected application.
3. The application synchronises the license.
### Send payment notification[](#send-payment-notification "Direct link to Send payment notification")
1. Your webhook receives a `payment.created` event.
2. It sends a relevant push notification to your app.
3. Your app shows a notification to your user that a payment was received.
Here’s some sample integration code that you can use to make your own workflow.
```
import crypto from 'crypto';
import express from 'express';
import bodyParser from 'body-parser';
const app = express();
const PRODUCT_SECRET_KEY = '';
app.post('/webhook', bodyParser.raw({ type: '*/*' }), (req, res) => {
const input = req.body.toString('utf8');
const hash = crypto
.createHmac('sha256', PRODUCT_SECRET_KEY)
.update(input)
.digest('hex');
const signature = (req.headers['x-signature'] as string) || '';
// Verify the signature and authenticity of the webhook event.
const isValid = crypto.timingSafeEqual(Buffer.from(hash, 'hex'), Buffer.from(signature, 'hex'));
if (!isValid) {
res.status(200).send();
return;
}
const fsEvent = JSON.parse(input);
processEvent(fsEvent);
});
app.listen(3000, () => {
console.log('Server is listening on port 3000');
});
function processEvent(fsEvent) {
let appInstances;
switch (fsEvent.type) {
case 'license.created':
appInstances = myApp().findByExternalUser(fsEvent.objects.user);
// Send push to the app to auto activate the license
myApp().pushService(appInstances).sendActivationNotice( fsEvent.objects.license );
break;
// Synchronize license
case 'license.plan.changed':
case 'license.extended':
case 'license.shortened':
case 'license.expired':
case 'license.deleted':
case 'license.cancelled':
appInstances = myApp().findByExternalLicenseId(fsEvent.objects.license.id);
myApp().pushService(appInstances).sendSyncSignal();
break;
case 'payment.created':
appInstances = myApp().findByExternalLicenseId(fsEvent.data.license_id);
// Send push to the app to update the payment status
myApp().pushService(appInstances).sendPaymentNotice(fsEvent.objects.payment);
break;
}
}
```
Using a JavaScript Environment?
Do check our [JS SDK Webhooks Documentation](https://freemius.com/help/help/documentation/saas-sdk/js-sdk/webhooks/.md) for an easier integration.
## Distributing the Software[](#distributing-the-software "Direct link to Distributing the Software")
Your customers will need to download your software to use it. Upload the downloadable app files to your preferred hosting service. In the Freemius Dashboard, add [custom download links](https://freemius.com/help/help/documentation/saas/custom-download-links/.md) to the product.
Once a customer purchases your product, they will receive notifications with the download link.
## Other Integrations[](#other-integrations "Direct link to Other Integrations")
* For Customer Portal, see [this guide](https://freemius.com/help/help/documentation/users-account-management/.md).
* To customize the style of the Checkout please follow [this guide](https://freemius.com/help/help/documentation/checkout/customization/applying-css-customization/.md).

You can always navigate to the **Setup Checklist** page to find out what you need to do next to set up your application. If you have any questions, please feel free to contact our support.
---
# Custom Download Links for Apps & Products
If you're selling downloadable applications (for example, desktop apps or Chrome extensions), you might want to provide download links to your customers after they purchase your product. Freemius allows you to add such links directly in your product settings.
## Configure Custom Download Links[](#configure-custom-download-links "Direct link to Configure Custom Download Links")
1. Log in to your Freemius [Developer Dashboard](https://dashboard.freemius.com/) and navigate to your product.
2. Go to the **Settings** page.
3. Select the **Download Links** tab.

Here, you can set up as many download links as needed. For each link, you can specify a label and the download URL.
## Where Download Links Appear[](#where-download-links-appear "Direct link to Where Download Links Appear")
Your customers will see these download links in the following locations:
### Purchase Emails[](#purchase-emails "Direct link to Purchase Emails")
The links appear automatically in several transactional emails:
1. New subscription confirmation email
2. Lifetime or one-off purchase confirmation email
3. Trial start email
Here is an example of how the links will look in the new subscription email:

In addition to listing the links, the email will also show the license key below the links, so your customers can easily copy the license key and use it to activate their app.
### Customer Portal[](#customer-portal "Direct link to Customer Portal")
The [**Downloads**](https://freemius.com/help/help/documentation/users-account-management/downloads/.md) section in the customer portal displays the custom download links you configured in the product settings.

Here's how the links are displayed based on the number of links you have configured:
* **Multiple links**: The system displays a dropdown menu labeled "Download" with all available links.
* **Single link**: The system displays the link label as the download button.
## Supported Product Types[](#supported-product-types "Direct link to Supported Product Types")
Custom download links are available exclusively for apps. If you have a SaaS product and would like this feature, please contact our support team with your specific use case.
---
# Customize License Unit Label
SaaS and Applications have specific words to reference the units of measure (primary entitlement) for the digital product being sold, e.g.
* A SaaS product might sell “Seats”.
* Applications might sell “Activations”.
Freemius empowers SaaS and Application makers to customize the license unit label accordingly. To configure this:
1. Log in to the [Developer Dashboard](https://dashboard.freemius.com).
2. Navigate to the relevant product’s ***Settings*** page.
3. Locate the **Selling Unit** Label option and input the singular form of the unit (e.g., “Seat” or “Activation”). Our system will handle the rest.

The new label will be reflected in the checkout when customers are making purchases in the checkout dropdown.

Similarly, in the purchase confirmation email sent to the customer after a successful checkout.

note
Our system will always include the custom unit label in emails for Applications that require license activation by default. However, for SaaS products, this depends on whether you have chosen to [expose the license key to users](https://freemius.com/help/help/documentation/saas/integrating-license-key-activation/.md#revealing-license-keys-to-customers).
---
# Automate Your Workflows with Events & Webhooks
At Freemius, we offer a robust **webhooks mechanism** that allows Makers to stay in sync with important platform activities. Webhooks enable real-time communication between Freemius and your backend systems whenever key actions occur—such as a new sale, a license deactivation, or a refund.
This system empowers you to automate workflows, sync data, trigger notifications, and more.
## What is an Event?[](#what-is-an-event "Direct link to What is an Event?")
An **event** is a specific, predefined action or change that takes place within the Freemius platform.
Events reflect meaningful occurrences in your product’s lifecycle—like a user completing a purchase, a subscription being canceled, or a license being activated. Each event is uniquely named (e.g., `subscription.canceled`, `license.activated`) and carries structured metadata relevant to that action.
Events are the **triggers** that initiate webhook callouts. Whenever a monitored event occurs, Freemius generates a corresponding callout to your configured endpoint.
## What is a Webhook?[](#what-is-a-webhook "Direct link to What is a Webhook?")
A **webhook** is a URL endpoint that you configure to receive HTTP `POST` requests from Freemius when certain events occur. When an event is triggered, Freemius performs a **webhook callout**—sending a signed payload with all relevant event data to your webhook endpoint. This enables your system to programmatically react to the event, such as updating a database, triggering an internal workflow, or notifying your team.
In short:
* The **webhook** is the URL endpoint you provide.
* The **webhook callout** is the actual HTTP request Freemius makes to that endpoint.
## How to create a Webhook?[](#how-to-create-a-webhook "Direct link to How to create a Webhook?")
1. Go to the **Webhooks** section and open **Listeners**.
2. Click the **Add Webhook** button.
3. A popup with a form will appear. Add your custom URL that will receive the event callback.
4. Select whether to receive all event types or specific ones on the Callback URL.
5. Choose to immediately make the webhook active or deactivate for a future time.
[](/help/videos/freemius-create-events-webhook.mp4)
tip
The Webhook can be edited later to change the url and other options. See all the [available webhooks](#types-of-events)
## How to Test Event's Payload?[](#how-to-test-events-payload "Direct link to How to Test Event's Payload?")
If you'd like to understand the payload of a specified `event.type` follow these steps:
1. Head to the **Webhooks** section and open **Events**.
2. Filter the events based on that `event.type`.
3. Copy the ID of the 1st event shown in the filtered view.
4. Leverage the API to [fetch the event's data](https://docs.freemius.com/api/events).
5. The result's schema from the API is identical to the payload's schema you'll get by the webhook once `event.type` will be processed.
## Validating Webhook Signatures[](#validating-webhook-signatures "Direct link to Validating Webhook Signatures")
To ensure the authenticity of incoming webhook requests, Freemius includes a signature in the `x-signature` header of each request. You can validate this signature using your product's secret key.
Here are the steps to validate the signature:
1. Get the `x-signature` header from the incoming request.
2. Get the raw request body (the payload).
3. Compute the HMAC SHA256 hash of the raw body using your product's secret key.
4. Compare the computed hash with the signature from the header.
Here are some code examples in different languages:
* Node.js
* PHP
```
import crypto from 'crypto';
const PRODUCT_SECRET_KEY = '';
// IMPORTANT: Ensure you have access to the raw request body
const rawBody = req.rawBody;
const signature = req.headers['x-signature'];
const hash = crypto.createHmac('sha256', PRODUCT_SECRET_KEY).update(rawBody).digest('hex');
let isValid = false;
try {
isValid = crypto.timingSafeEqual(Buffer.from(hash, 'hex'), Buffer.from(signature, 'hex'));
} catch {
// Ignore errors
}
if (isValid) {
// Process the webhook
} else {
// Invalid signature
}
```
```
const PRODUCT_SECRET_KEY = '';
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$rawBody = file_get_contents('php://input');
$calculatedSignature = hash_hmac('sha256', $rawBody, PRODUCT_SECRET_KEY);
$isValid = hash_equals($calculatedSignature, $signature);
if ($isValid) {
// Process the webhook
} else {
// Invalid signature
}
```
Using a JavaScript Environment?
Do check our [JS SDK Webhooks Documentation](https://freemius.com/help/help/documentation/saas-sdk/js-sdk/webhooks/.md) for an easier integration.
## Types of Events[](#types-of-events "Direct link to Types of Events")
This is a list of all the types of events we currently send. We may add more at any time, so you shouldn't rely on only these types existing in your code.
You'll notice that these events follow a pattern: `resource.event`. Our goal is to design a consistent system that makes things easier to anticipate and code against.
**NOTE:** Events that occur on "sub" resources like `payment.dispute.created` do not trigger the parent's `update` event.
### Affiliate Program[](#affiliate-program "Direct link to Affiliate Program")
`affiliate.approved`[](#affiliate-approved "Direct link to affiliate.approved")
Occurs whenever an affiliate is approved.
`affiliate.blocked`[](#affiliate-blocked "Direct link to affiliate.blocked")
Occurs whenever an affiliate is blocked.
`affiliate.created`[](#affiliate-created "Direct link to affiliate.created")
Occurs whenever an affiliate is created.
`affiliate.deleted`[](#affiliate-deleted "Direct link to affiliate.deleted")
Occurs whenever an affiliate is deleted.
`affiliate.payout.pending`[](#affiliate-payout-pending "Direct link to affiliate.payout.pending")
Occurs once per month when our system calculates affiliate payouts. Triggered only for affiliates eligible for a payout after crossing the $100 threshold.
`affiliate.paypal.updated`[](#affiliate-paypal-updated "Direct link to affiliate.paypal.updated")
Occurs whenever there’s an update of an affiliate's PayPal details.
`affiliate.rejected`[](#affiliate-rejected "Direct link to affiliate.rejected")
Occurs whenever an affiliate is rejected.
`affiliate.suspended`[](#affiliate-suspended "Direct link to affiliate.suspended")
Occurs whenever an affiliate is suspended.
`affiliate.unapproved`[](#affiliate-unapproved "Direct link to affiliate.unapproved")
Occurs whenever an affiliate state is changed from approved to pending.
`affiliate.updated`[](#affiliate-updated "Direct link to affiliate.updated")
Occurs whenever there’s an update of an affiliate's details.
### Card[](#card "Direct link to Card")
`card.created`[](#card-created "Direct link to card.created")
Occurs whenever a new credit or debit card is added to a user’s account.
`card.updated`[](#card-updated "Direct link to card.updated")
Occurs whenever a user’s credit or debit card details have been updated.
### Cart[](#cart "Direct link to Cart")
`cart.abandoned`[](#cart-abandoned "Direct link to cart.abandoned")
Occurs whenever a cart session has been abandoned.
`cart.completed`[](#cart-completed "Direct link to cart.completed")
Occurs whenever a ‘sale’ has been completed, but the payment hasn’t necessarily been completed yet.
`cart.created`[](#cart-created "Direct link to cart.created")
Occurs whenever a cart session has been initiated.
`cart.recovered`[](#cart-recovered "Direct link to cart.recovered")
Occurs whenever an abandoned cart session has been recovered and converted into a ‘sale’.
`cart.recovery.deactivated`[](#cart-recovery-deactivated "Direct link to cart.recovery.deactivated")
Occurs when the cart recovery feature has been deactivated in the Developer Dashboard.
`cart.recovery.email_1_sent`[](#cart-recovery-email_1_sent "Direct link to cart.recovery.email_1_sent")
Occurs when the first cart recovery email has been sent.
`cart.recovery.email_2_sent`[](#cart-recovery-email_2_sent "Direct link to cart.recovery.email_2_sent")
Occurs when the second cart recovery email has been sent.
`cart.recovery.email_3_sent`[](#cart-recovery-email_3_sent "Direct link to cart.recovery.email_3_sent")
Occurs when the third (and final) cart recovery email has been sent.
`cart.recovery.reactivated`[](#cart-recovery-reactivated "Direct link to cart.recovery.reactivated")
Occurs when the cart recovery feature has been reactivated in the Developer Dashboard.
`cart.recovery.subscribed`[](#cart-recovery-subscribed "Direct link to cart.recovery.subscribed")
Occurs when the prospect re-subscribes to the cart abandonment recovery campaign.
`cart.recovery.unsubscribed`[](#cart-recovery-unsubscribed "Direct link to cart.recovery.unsubscribed")
Occurs when the prospect unsubscribes from the cart abandonment recovery campaign.
`cart.updated`[](#cart-updated "Direct link to cart.updated")
Occurs when the cart contents have been updated.
### Coupons & Discounts[](#coupons--discounts "Direct link to Coupons & Discounts")
`coupon.created`[](#coupon-created "Direct link to coupon.created")
Occurs whenever a coupon is created.
`coupon.deleted`[](#coupon-deleted "Direct link to coupon.deleted")
Occurs whenever a coupon is deleted.
`coupon.updated`[](#coupon-updated "Direct link to coupon.updated")
Occurs whenever a coupon is updated.
### Buyer Email Tracking[](#buyer-email-tracking "Direct link to Buyer Email Tracking")
`email.clicked`[](#email-clicked "Direct link to email.clicked")
Occurs when a link is clicked by a user. Email link click tracking is only included in cart abandonment recovery and trial-related emails.
`email.opened`[](#email-opened "Direct link to email.opened")
Occurs when an email has been opened by a user. Email open tracking is only included in cart abandonment recovery, trial-related, and subscription renewal reminder emails.
### Installation (Downloadable Software)[](#installation-downloadable-software "Direct link to Installation (Downloadable Software)")
In the context of downloadable software (like plugins or desktop apps), an installation represents a specific copy of your product that has been installed and is running on a particular device or site. The following webhook events let you track changes to each installation—covering its state, version, plan, platform details, and more.
`install.activated`[](#install-activated "Direct link to install.activated")
Occurs whenever a user is opted-in, and after reactivation of a product.
`install.deactivated`[](#install-deactivated "Direct link to install.deactivated")
Occurs whenever a product's installation is being deactivated.
`install.deleted`[](#install-deleted "Direct link to install.deleted")
Occurs whenever a product is being uninstalled.
`install.connected`[](#install-connected "Direct link to install.connected")
Occurs whenever a user opts in for sharing basic device/website info after previously opting out from it.
`install.disconnected`[](#install-disconnected "Direct link to install.disconnected")
Occurs whenever a user opts out from sharing basic device/website info after previously sharing it.
`install.installed`[](#install-installed "Direct link to install.installed")
Occurs whenever a product is being installed (triggered before `install.activated`).
`install.language.updated`[](#install-language-updated "Direct link to install.language.updated")
Occurs whenever an installation language is updated.
`install.plan.changed`[](#install-plan-changed "Direct link to install.plan.changed")
Occurs whenever an installation plan is changed.
`install.plan.downgraded`[](#install-plan-downgraded "Direct link to install.plan.downgraded")
Occurs whenever an installation plan is downgraded to the default plan.
`install.platform.version.updated`[](#install-platform-version-updated "Direct link to install.platform.version.updated")
Occurs whenever an installation platform version is updated (e.g., WP version; iOS version).
`install.premium.activated`[](#install-premium-activated "Direct link to install.premium.activated")
Occurs whenever the paid product version is activated.
`install.premium.deactivated`[](#install-premium-deactivated "Direct link to install.premium.deactivated")
Occurs whenever the paid product version is deactivated.
`install.programming_language.version.updated`[](#install-programming_language-version-updated "Direct link to install.programming_language.version.updated")
Occurs whenever an installation PHP version is updated.
`install.sdk.version.updated`[](#install-sdk-version-updated "Direct link to install.sdk.version.updated")
Occurs whenever a product installation is updated with a new Freemius SDK version.
`install.title.updated`[](#install-title-updated "Direct link to install.title.updated")
Occurs whenever an installation’s device/site title is updated.
`install.trial.cancelled`[](#install-trial-cancelled "Direct link to install.trial.cancelled")
Occurs when a trial is cancelled.
`install.trial.expired`[](#install-trial-expired "Direct link to install.trial.expired")
Occurs when a trial is expired.
`install.trial_expiring_notice.sent`[](#install-trial_expiring_notice-sent "Direct link to install.trial_expiring_notice.sent")
Occurs when an email has been sent to the user notifying them the trial period is about to end.
`install.trial.extended`[](#install-trial-extended "Direct link to install.trial.extended")
Occurs whenever a trial is manually extended.
`install.trial.plan.updated`[](#install-trial-plan-updated "Direct link to install.trial.plan.updated")
Occurs whenever a trial plan is updated.
`install.trial.started`[](#install-trial-started "Direct link to install.trial.started")
Occurs when a free trial is started on an existing product install.
`install.uninstalled`[](#install-uninstalled "Direct link to install.uninstalled")
Occurs whenever the product is uninstalled.
`install.updated`[](#install-updated "Direct link to install.updated")
Occurs whenever an installation data is updated (in addition to specific install update events).
`install.url.updated`[](#install-url-updated "Direct link to install.url.updated")
Occurs whenever an installation’s home URL is updated.
`install.version.downgrade`[](#install-version-downgrade "Direct link to install.version.downgrade")
Occurs whenever the product is downgraded to a lower version (not related to the plan).
`install.version.upgraded`[](#install-version-upgraded "Direct link to install.version.upgraded")
Occurs whenever the product is upgrade to a newer version (not related to the plan).
### Licensing[](#licensing "Direct link to Licensing")
`license.activated`[](#license-activated "Direct link to license.activated")
Occurs whenever a license is activated (also triggered automatically after a successful plan upgrade).
`license.cancelled`[](#license-cancelled "Direct link to license.cancelled")
Occurs when a license is cancelled.
`license.created`[](#license-created "Direct link to license.created")
Occurs whenever a license is created.
`license.deactivated`[](#license-deactivated "Direct link to license.deactivated")
Occurs whenever a license is deactivated.
`license.deleted`[](#license-deleted "Direct link to license.deleted")
Occurs when a license has been deleted in the Developer Dashboard.
`license.expired`[](#license-expired "Direct link to license.expired")
Occurs when a license expires.
`license.expired_notice.sent`[](#license-expired_notice-sent "Direct link to license.expired_notice.sent")
Occurs when a license expired email notice has been sent to the user.
`license.extended`[](#license-extended "Direct link to license.extended")
Occurs whenever a license is extended by the developer from the dashboard.
`license.ownership.changed`[](#license-ownership-changed "Direct link to license.ownership.changed")
Occurs whenever a license ownership is changed by the developer from the dashboard.
`license.quota.changed`[](#license-quota-changed "Direct link to license.quota.changed")
Occurs whenever a license quota is changed by the developer from the dashboard.
`license.renewal_reminder.sent`[](#license-renewal_reminder-sent "Direct link to license.renewal_reminder.sent")
Occurs when a license renewal reminder email has been sent to the user.
`license.shortened`[](#license-shortened "Direct link to license.shortened")
Occurs when a license has been shortened in the User/Developer Dashboard.
`license.trial_expiring_notice.sent`[](#license-trial_expiring_notice-sent "Direct link to license.trial_expiring_notice.sent")
Occurs when a courtesy trial expiration reminder email has been sent to the user.
`license.updated`[](#license-updated "Direct link to license.updated")
Occurs whenever a license is updated (in addition to specific license update events).
### Payment[](#payment "Direct link to Payment")
`payment.created`[](#payment-created "Direct link to payment.created")
Occurs whenever a successful payment is created.
`payment.refund`[](#payment-refund "Direct link to payment.refund")
Occurs whenever a payment refund is processed.
`payment.dispute.created`[](#payment-dispute-created "Direct link to payment.dispute.created")
Occurs whenever we notified about a payment disputed by a customer.
`payment.dispute.closed`[](#payment-dispute-closed "Direct link to payment.dispute.closed")
Occurs whenever a payment dispute is closed by refunding the disputed payment.
`payment.dispute.lost`[](#payment-dispute-lost "Direct link to payment.dispute.lost")
Occurs whenever a payment dispute is closed in favor of the customer.
`payment.dispute.won`[](#payment-dispute-won "Direct link to payment.dispute.won")
Occurs whenever a payment dispute is closed in your/seller’s favor.
### Product Team[](#product-team "Direct link to Product Team")
`member.created`[](#member-created "Direct link to member.created")
Occurs whenever a developer is added as a team member.
`member.deleted`[](#member-deleted "Direct link to member.deleted")
Occurs whenever a member is removed from a team.
`member.updated`[](#member-updated "Direct link to member.updated")
Occurs whenever a team member role is updated.
### Plans, Pricing and Features[](#plans-pricing-and-features "Direct link to Plans, Pricing and Features")
`plan.created`[](#plan-created "Direct link to plan.created")
Occurs whenever a plan is created.
`plan.deleted`[](#plan-deleted "Direct link to plan.deleted")
Occurs whenever a plan is deleted.
`plan.lifetime.purchase`[](#plan-lifetime-purchase "Direct link to plan.lifetime.purchase")
Occurs whenever a user purchases a lifetime package (doesn’t matter which plan).
`plan.updated`[](#plan-updated "Direct link to plan.updated")
Occurs whenever a plan details are updated.
`pricing.created`[](#pricing-created "Direct link to pricing.created")
Occurs whenever a new pricing is added to a plan.
`pricing.deleted`[](#pricing-deleted "Direct link to pricing.deleted")
Occurs whenever one of the plan pricing is deleted.
`pricing.updated`[](#pricing-updated "Direct link to pricing.updated")
Occurs whenever a plan pricing is updated.
### Reviews[](#reviews "Direct link to Reviews")
`review.created`[](#review-created "Direct link to review.created")
Occurs when a new review has been created.
`review.deleted`[](#review-deleted "Direct link to review.deleted")
Occurs when a review has been deleted in the Developer Dashboard.
`review.requested`[](#review-requested "Direct link to review.requested")
Occurs when a new review request email has been sent to the user.
`review.updated`[](#review-updated "Direct link to review.updated")
Occurs when a new review has been updated in the Developer Dashboard.
### Subscription[](#subscription "Direct link to Subscription")
`subscription.cancelled`[](#subscription-cancelled "Direct link to subscription.cancelled")
Occurs whenever a subscription is cancelled.
`subscription.created`[](#subscription-created "Direct link to subscription.created")
Occurs whenever a subscription is created.
`subscription.renewal_reminder.sent`[](#subscription-renewal_reminder-sent "Direct link to subscription.renewal_reminder.sent")
Occurs whenever an annual renewal reminder email is sent (30 days before the automatic renewal).
`subscription.renewal_reminder.opened`[](#subscription-renewal_reminder-opened "Direct link to subscription.renewal_reminder.opened")
Occurs whenever an annual renewal reminder email is opened.
`subscription.renewal.failed`[](#subscription-renewal-failed "Direct link to subscription.renewal.failed")
Occurs whenever a renewal payment processing is failed.
`subscription.renewal.failed.last`[](#subscription-renewal-failed-last "Direct link to subscription.renewal.failed.last")
Occurs when the latest subscription renewal attempt has failed.
`subscription.renewal.failed_email.sent`[](#subscription-renewal-failed_email-sent "Direct link to subscription.renewal.failed_email.sent")
Occurs whenever a failure renewal processing email is sent to the buyer.
`subscription.renewal.retry`[](#subscription-renewal-retry "Direct link to subscription.renewal.retry")
Occurs whenever a renewal payment retry is processed.
`subscription.renewals.discounted`[](#subscription-renewals-discounted "Direct link to subscription.renewals.discounted")
Occurs whenever a special subscription cancellation promo is applied.
### Store[](#store "Direct link to Store")
`store.created`[](#store-created "Direct link to store.created")
Occurs when a new store has been created (e.g. when a new Freemius account has been created).
`store.dashboard_url.updated`[](#store-dashboard_url-updated "Direct link to store.dashboard_url.updated")
Occurs when the store’s dashboard URL is updated.
`store.plugin.added`[](#store-plugin-added "Direct link to store.plugin.added")
Occurs when a product is added to a store.
`store.plugin.removed`[](#store-plugin-removed "Direct link to store.plugin.removed")
Occurs when a product is removed from a store.
`store.url.updated`[](#store-url-updated "Direct link to store.url.updated")
Occurs when a store’s URL is updated.
### User[](#user "Direct link to User")
`user.beta_program.opted_in`[](#user-beta_program-opted_in "Direct link to user.beta_program.opted_in")
Occurs when a user has opted into a product's beta program.
`user.beta_program.opted_out`[](#user-beta_program-opted_out "Direct link to user.beta_program.opted_out")
Occurs when a user has opted out of a product's beta program.
`user.billing.updated`[](#user-billing-updated "Direct link to user.billing.updated")
Occurs whenever a customer billing information is updated (e.g. address, VAT ID, company name).
`user.billing.tax_id.updated`[](#user-billing-tax_id-updated "Direct link to user.billing.tax_id.updated")
Occurs whenever the tax ID associated with the user’s billing is changed.
`user.card.created`[](#user-card-created "Direct link to user.card.created")
Occurs whenever a new card is added to a user’s account.
`user.created`[](#user-created "Direct link to user.created")
Occurs whenever a new user is created.
`user.email.changed`[](#user-email-changed "Direct link to user.email.changed")
Occurs whenever a user update their email address.
`user.email.verified`[](#user-email-verified "Direct link to user.email.verified")
Occurs when a user email is verified (usually via email confirmation).
`user.email_status.bounced`[](#user-email_status-bounced "Direct link to user.email_status.bounced")
Occurs when a transactional email sent to a user bounces, which also changes the user’s email\_status property to 'bounced'.
`user.email_status.delivered`[](#user-email_status-delivered "Direct link to user.email_status.delivered")
Occurs when a transactional email sent to a user is successfully delivered and only if the user’s previous deliverability state (aka the email\_status property) was not empty before.
`user.email_status.dropped`[](#user-email_status-dropped "Direct link to user.email_status.dropped")
Occurs when a transactional email sent to a user is dropped, which also changes the user’s email\_status property to 'dropped'.
`user.marketing.opted_in`[](#user-marketing-opted_in "Direct link to user.marketing.opted_in")
Occurs when a user has opted to receive marketing material (emails).
`user.marketing.opted_out`[](#user-marketing-opted_out "Direct link to user.marketing.opted_out")
Occurs when a user has opted out of a receiving marketing material (emails).
`user.marketing.reset`[](#user-marketing-reset "Direct link to user.marketing.reset")
Occurs when a user’s marketing status has been reset.
`user.name.changed`[](#user-name-changed "Direct link to user.name.changed")
Occurs whenever a user update their name.
`user.support.contacted`[](#user-support-contacted "Direct link to user.support.contacted")
Occurs when a user submits a support ticket via the contact form in the Customer Portal.
`user.trial.started`[](#user-trial-started "Direct link to user.trial.started")
Occurs when a user registers for a trial.
### Webhook[](#webhook "Direct link to Webhook")
`webhook.created`[](#webhook-created "Direct link to webhook.created")
Occurs whenever a webhook is created.
`webhook.deleted`[](#webhook-deleted "Direct link to webhook.deleted")
Occurs whenever a webhook is deleted.
`webhook.updated`[](#webhook-updated "Direct link to webhook.updated")
Occurs whenever a webhook is updated.
### WordPress[](#wordpress "Direct link to WordPress")
`addon.free.downloaded`[](#addon-free-downloaded "Direct link to addon.free.downloaded")
Occurs whenever a free version of an add-on is downloaded.
`addon.premium.downloaded`[](#addon-premium-downloaded "Direct link to addon.premium.downloaded")
Occurs whenever a premium version of an add-on is downloaded.
`install.extensions.opt_in`[](#install-extensions-opt_in "Direct link to install.extensions.opt_in")
Occurs whenever a user opts in for sharing a website’s plugins & themes list after previously opting out from it.
`install.extensions.opt_out`[](#install-extensions-opt_out "Direct link to install.extensions.opt_out")
Occurs whenever a user opt-out from sharing a website’s plugins & themes list after previously sharing it.
`install.ownership.candidate.confirmed`[](#install-ownership-candidate-confirmed "Direct link to install.ownership.candidate.confirmed")
Occurs whenever an account ownership transfer candidate confirms the transfer.
`install.ownership.completed`[](#install-ownership-completed "Direct link to install.ownership.completed")
Occurs whenever an account ownership transfer is complete.
`install.ownership.initiated`[](#install-ownership-initiated "Direct link to install.ownership.initiated")
Occurs whenever an account ownership transfer is initiated.
`install.ownership.owner.confirmed`[](#install-ownership-owner-confirmed "Direct link to install.ownership.owner.confirmed")
Occurs whenever an account ownership transfer is confirmed by the current installation account owner.
`install.site.opt_in`[](#install-site-opt_in "Direct link to install.site.opt_in")
Occurs whenever a user opts in for sharing basic website after previously opting out from it.
`install.site.opt_out`[](#install-site-opt_out "Direct link to install.site.opt_out")
Occurs whenever a user opts out from sharing basic website after previously sharing it.
`install.user.opt_in`[](#install-user-opt_in "Direct link to install.user.opt_in")
Occurs whenever a user opts in for sharing their basic profile info after previously opting out from it.
`install.user.opt_out`[](#install-user-opt_out "Direct link to install.user.opt_out")
Occurs whenever a user opts out from sharing their basic profile info after previously sharing it.
`license.site.blacklisted`[](#license-site-blacklisted "Direct link to license.site.blacklisted")
Occurs when a site has been blacklisted in the Customer Portal.
`license.blacklisted_site.deleted`[](#license-blacklisted_site-deleted "Direct link to license.blacklisted_site.deleted")
Occurs when a blacklisted site has been removed in the Customer Portal.
`license.site.whitelisted`[](#license-site-whitelisted "Direct link to license.site.whitelisted")
Occurs when a site has been whitelisted in the Customer Portal.
`license.whitelisted_site.deleted`[](#license-whitelisted_site-deleted "Direct link to license.whitelisted_site.deleted")
Occurs when a whitelisted site has been removed in the Customer Portal.
`plugin.free.downloaded`[](#plugin-free-downloaded "Direct link to plugin.free.downloaded")
Occurs whenever a free product version is downloaded.
`plugin.premium.downloaded`[](#plugin-premium-downloaded "Direct link to plugin.premium.downloaded")
Occurs whenever a paid product version is downloaded.
`plugin.version.deleted`[](#plugin-version-deleted "Direct link to plugin.version.deleted")
Occurs whenever a deployed version is deleted.
`plugin.version.deployed`[](#plugin-version-deployed "Direct link to plugin.version.deployed")
Occurs whenever a new product version is deployed to Freemius.
`plugin.version.released`[](#plugin-version-released "Direct link to plugin.version.released")
Occurs whenever a version is set as released.
`plugin.version.beta.released`[](#plugin-version-beta-released "Direct link to plugin.version.beta.released")
Occurs whenever a version is released as beta.
`plugin.version.release.suspended`[](#plugin-version-release-suspended "Direct link to plugin.version.release.suspended")
Occurs whenever a deployment release is suspended.
`plugin.version.updated`[](#plugin-version-updated "Direct link to plugin.version.updated")
Occurs whenever an existing version is re-deployed to Freemius.
`pricing.visit`[](#pricing-visit "Direct link to pricing.visit")
Occurs when a user has visited the pricing table via the WordPress admin or popup checkout modal.
---
# Integrating License Key Activation
This guide covers software such as desktop apps (macOS or Windows), Chrome extensions, and SaaS products that rely on license key activation.
WordPress Users
If you are integrating Freemius with a WordPress plugin, theme, or add-on, please follow the [WordPress SDK integration guide](https://freemius.com/help/help/documentation/wordpress/integration-with-sdk/.md).
## Initial Steps[](#initial-steps "Direct link to Initial Steps")
1. [Sign up for Freemius](https://dashboard.freemius.com/register/).
2. Create a new SaaS or App product tailored to your needs.
3. Go to the Plans and [configure the plans and prices](https://freemius.com/help/help/documentation/wordpress/setup-product-pricing-plans-refunds/.md).
For pre-purchase registration, see the [Checkout integration section](https://freemius.com/help/help/documentation/saas/saas-integration/.md#checkout-integration) in the [SaaS Integration doc](https://freemius.com/help/help/documentation/saas/saas-integration/.md).
## Revealing License Keys to Customers[](#revealing-license-keys-to-customers "Direct link to Revealing License Keys to Customers")
This configuration is only relevant for SaaS products. For [**Apps**](https://freemius.com/help/help/documentation/saas/app-integration/.md), license keys are always shown to buyers.
1. Log in to the [developer dashboard](https://dashboard.freemius.com/).
2. Navigate to ***Settings*** page.
3. Under the **License Keys** section, toggle on the switch to "show license keys to customers". The setting is automatically saved.

## Checkout Integration[](#checkout-integration "Direct link to Checkout Integration")
[Integrate the checkout](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md) with your buy buttons as a modal dialog triggered by JavaScript, or by using direct checkout links.
If you require registration prior to purchasing, check the ***Checkout integration*** section in the [SaaS Integration doc](https://freemius.com/help/help/documentation/saas/saas-integration/.md).
## License Key Integration[](#license-key-integration "Direct link to License Key Integration")
Create a license form in your app to validate the key.
### License Activation[](#license-activation "Direct link to License Activation")
To activate a license key, send a `POST` request to the [license activation API endpoint](https://docs.freemius.com/api/licenses/activate):
```
POST /v1/products/{product_id}/licenses/activate.json
Host: api.freemius.com
Content-Type: application/json
{
"uid": "{uuid}",
"license_key": "{license_key}",
"title": "{title}"
}
```
The following parameters must be included in the request body:
* `{title}` – The name of the installation device. This helps you and [your customer](https://freemius.com/help/help/documentation/users-account-management/activations/.md#activation-details-and-plan-upgrade) identify the device during deactivation. For example, "John's MacBook Pro".
* `{license_key}` – The entered license key
* `{uuid}` – An unique 32-character identifier. You need to generate it from your application. See [below](#generating-a-unique-identifier-uuid) for examples.
You can also include additional parameters which you can find in our [API documentation](https://docs.freemius.com/api/licenses/activate#licenses/activate/request/body).
#### Generating a Unique Identifier (UUID)[](#generating-a-unique-identifier-uuid "Direct link to Generating a Unique Identifier (UUID)")
Here are some code snippets to generate a unique identifier (UUID) for different platforms. The UUID should be consistent for the same device, so it can be used to track license activations and deactivations.
* Node.js
* Browser
* Windows
* MacOS
```
import nodeMachineId from 'node-machine-id';
import crypto from 'node:crypto';
// Works for any JS runtime, like Nodejs, Bun, Deno, etc.
function getDeviceGuid() {
const rawMachineId = nodeMachineId.machineIdSync();
return crypto
.createHash('sha256')
.update(`freemius-my-app:${rawMachineId}`)
.digest('hex')
.slice(0, 32);
}
console.log(getDeviceGuid());
```
```
// Store uuid in the local storage where the license is activated.
crypto.randomUUID().replace(/-/g, '');
```
```
// With C# using Microsoft.Win32;
using System.Security.Cryptography;
using System.Text;
string GetDeviceId()
{
string? machineGuid = null;
try
{
using var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography");
machineGuid = key?.GetValue("MachineGuid")?.ToString();
}
catch
{
// ignore and fallback
}
// If MachineGuid exists → hash it
if (!string.IsNullOrWhiteSpace(machineGuid))
{
using var md5 = MD5.Create();
var bytes = Encoding.UTF8.GetBytes(machineGuid);
var hash = md5.ComputeHash(bytes);
return Convert.ToHexString(hash); // 32 chars
}
// Fallback → random 32-char ID
return Guid.NewGuid().ToString("N"); // also 32 chars
}
```
```
import Foundation
import IOKit
import CryptoKit
func getDeviceId() -> String {
if let uuid = getIOPlatformUUID(), !uuid.isEmpty {
return md5(uuid)
}
// fallback → random 32-char ID
return UUID().uuidString.replacingOccurrences(of: "-", with: "")
}
private func getIOPlatformUUID() -> String? {
let matching = IOServiceMatching("IOPlatformExpertDevice")
let service = IOServiceGetMatchingService(kIOMainPortDefault, matching)
defer {
IOObjectRelease(service)
}
guard service != 0 else { return nil }
if let cfUUID = IORegistryEntryCreateCFProperty(
service,
"IOPlatformUUID" as CFString,
kCFAllocatorDefault,
0
) {
return cfUUID.takeRetainedValue() as? String
}
return nil
}
private func md5(_ string: String) -> String {
let data = Data(string.utf8)
let digest = Insecure.MD5.hash(data: data)
return digest.map { String(format: "%02hhx", $0) }.joined().uppercased()
}
```
If the license key is valid and activated, the endpoint will return an object with various useful properties which you can find in the [API documentation](https://docs.freemius.com/api/licenses/activate#licenses/activate/response\&c=200). The most important properties are:
* `install_id`: The ID of the created `Install` entity. This is essentially the record of the activation for the specific device.
* `install_api_token`: A bearer token that can be used to update the properties of the `Install` entity. More information can be found [here](https://freemius.com/help/help/documentation/saas/app-integration/.md#synchronizing-application-state-with-freemius).
* `license_plan_name`: The name of the plan associated with the activated license key. This is useful for showing the user which plan they are currently subscribed to.
Save the activation details
Ensure to store the `uuid`, `install_id`, `install_api_token` and `license_key` in the local storage (for Chrome extension use [chrome.storage](https://developer.chrome.com/docs/extensions/reference/api/storage)) where the license is activated. You'll need these details during license validation and deactivation.
### License Deactivation[](#license-deactivation "Direct link to License Deactivation")
To deactivate a license, send a `POST` request to the [license deactivation API endpoint](https://docs.freemius.com/api/licenses/deactivate):
```
POST /v1/products/{product_id}/licenses/deactivate.json?fields=id%2Cname%2Cslug
Host: api.freemius.com
Content-Type: application/json
{
"uid": "{uuid}",
"install_id": "{install_id}",
"license_key": "{license_key}"
}
```
* `{uuid}` – Must be replaced with the stored install’s uuid
* `{install_id}` – Must be replaced with the stored install’s ID
* `{license_key}` – Must be replaced with the stored license key
### License Validation[](#license-validation "Direct link to License Validation")
To validate a license, first fetch the activated license with a `GET` request to the [Retrieve an active license by ID API endpoint](https://docs.freemius.com/api/installations/retrieve-active-license-by-id):
```
GET /v1/products/{product_id}/installs/{install_id}/license.json?uid={uuid}&license_key={license_key}
Host: api.freemius.com
```
* `{uuid}` – Must be replaced with the stored install’s uuid
* `{install_id}` – Must be replaced with the stored install’s ID
* `{license_key}` – Must be replaced with the stored license key
If the license is activated on the specified install, the endpoint returns a `License` object that includes the `expiration` and `is_cancelled` properties (among others).
If `is_cancelled` is `true`, the license was canceled from the Developer Dashboard and is therefore invalid. If the license wasn’t canceled, `expiration` contains the license expiration date formatted as `Y-m-d H:i:s` (it can also be equal to `null` for lifetime licenses).
URL-encode query parameters for GET requests
When sending any query parameter with a `GET` request, for example, the license key or the UUID, please make sure to encode the values. For example:
* PHP
* JavaScript
```
$url = "https://api.freemius.com...?license_key=" . url_encode($license_key);
```
```
const url = `https://api.freemius.com...?license_key=${encodeURIComponent(licenseKey)}`;
```
## Distributing the Software[](#distributing-the-software "Direct link to Distributing the Software")
After integrating license key activation, the next step is to set up [custom download links](https://freemius.com/help/help/documentation/saas/custom-download-links/.md), where your customers can download the app files.
---
# iOS In-App Purchase Integration
In this guide, you will learn how to use the Freemius Hosted Checkout to provide an in-app purchase experience from your iOS apps without using Apple Checkout.
## Getting Started[](#getting-started "Direct link to Getting Started")
To get started with Freemius Checkout integration, you need to do the following:
* [Register to Freemius](https://dashboard.freemius.com/register/) as a maker.
* Created an [App product](https://freemius.com/help/help/documentation/getting-started/integrating-your-first-product/.md).
* Configure your [plans and pricing](https://freemius.com/help/help/documentation/wordpress/setup-product-pricing-plans-refunds/.md) from the Dashboard.

## Integration with No-Code Checkout Links[](#integration-with-no-code-checkout-links "Direct link to Integration with No-Code Checkout Links")
The easiest way to integrate Freemius Checkout with your app is by using the links generated from the Freemius Developer Dashboard.
1. Navigate to the **Plans** tab of your product.
2. Select the **Plan** you want to use.
3. Next to each **Pricing**, click the **Checkout Link** button and copy the **Production/Live** link.

You can then use your preferred method to open the link in your app, such as using a `WKWebView` or opening it in Safari.
For more advanced integrations, continue reading the sections below.
## Prerequisites for iOS Checkout Integration[](#prerequisites-for-ios-checkout-integration "Direct link to Prerequisites for iOS Checkout Integration")
* You have all the IDs of your plans. You can find them in the Freemius dashboard under the **Plans** tab of your product.
* You have the [Bearer Token Auth](https://docs.freemius.com/api/section/bearer-token-auth) for communicating with the Freemius API.
* You have created a [Webhook Listener](https://freemius.com/help/help/documentation/saas/events-webhooks/.md#how-to-create-a-webhook).
## Checkout Integration Using WebView[](#checkout-integration-using-webview "Direct link to Checkout Integration Using WebView")
We will use the [Freemius Hosted Checkout](https://freemius.com/help/help/documentation/checkout/integration/hosted-checkout/.md) links to provide an in-app purchase experience to your buyers without using Apple Checkout.
At any point in your app, you can use the following high-level flow to open the Freemius Hosted Checkout link:
1. **Generate the Checkout URL**: Use your `productID`, `planID`, and `currency` to [construct the URL](https://freemius.com/help/help/documentation/checkout/integration/hosted-checkout/.md#hosted-checkout-url-schema).
2. **Create a Button**: Add a button to your app's UI that triggers the checkout process.
3. **Open the URL in a WebView**: When the button is tapped:
* Create a `WKWebView`.
* Load the `checkoutURL` in the WebView.
Additionally, you can also use the [Freemius Checkout SDK](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md) to create a pricing page on your website and open your own pricing page from your app.
## Scoping the Checkout to Your Authenticated User[](#scoping-the-checkout-to-your-authenticated-user "Direct link to Scoping the Checkout to Your Authenticated User")
If your app collects the user's email address or has a user account system, you can scope the checkout to the authenticated user by passing additional parameters to the checkout URL.
We recommend including the following parameters:
* `user_email`: The email address of the authenticated user.
* `user_firstname`: The first name of the authenticated user.
* `user_lastname`: The last name of the authenticated user.
* `readonly_user`: Set to `true` to prevent the user from changing their email address during checkout.
Here is an example of a checkout URL with these parameters:
```
https://checkout.freemius.com/product//plan//?user_email=&user_firstname=&user_lastname=&readonly_user=true
```
## Setting Up Redirects[](#setting-up-redirects "Direct link to Setting Up Redirects")

To ensure a smooth user experience, you need to set up redirects for the Freemius Hosted Checkout links.
1. Go to the Freemius Developer Dashboard and under the **Plans** page, go to the **Customization** tab.
2. Enable the **Checkout Success Redirection** and enter a URL that will handle the success response from Freemius.
3. From the URL you provided, you can do post-processing and finally redirect the user to your application (for example, using a [deep link](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/)).
Please read more about [Redirects](https://freemius.com/help/help/documentation/checkout/integration/hosted-checkout/.md#redirection-after-a-successful-purchase) in the Hosted Checkout documentation.
## Syncing Purchases and Subscriptions[](#syncing-purchases-and-subscriptions "Direct link to Syncing Purchases and Subscriptions")
Once the user has completed the purchase, you might want to:
1. Activate the license in your app.
2. Sync the purchase with your backend.
To do this, please [create a webhook listener](https://freemius.com/help/help/documentation/saas/events-webhooks/.md#how-to-create-a-webhook) from the Freemius Developer Dashboard.
We recommend using the `license.*` events to sync the purchase with your backend. Then your backend can send the license information to your app using a [push notification](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns) or any other method you prefer.

We are giving an example below with a simple backend using Node.js and Express:
```
import crypto from 'crypto';
import express from 'express';
import bodyParser from 'body-parser';
const app = express();
const PRODUCT_SECRET_KEY = '';
app.post('/webhook', bodyParser.raw({ type: '*/*' }), (req, res) => {
const input = req.body.toString('utf8');
const hash = crypto
.createHmac('sha256', PRODUCT_SECRET_KEY)
.update(input)
.digest('hex');
const signature = (req.headers['x-signature'] as string) || '';
// Verify the signature and authenticity of the webhook event.
if (hash !== signature) {
res.status(200).send();
return;
}
const fsEvent = JSON.parse(input);
processEvent(fsEvent); });
app.listen(3000, () => {
console.log('Server is listening on port 3000');
});
function processEvent(fsEvent) {
let appInstances;
switch (fsEvent.type) {
case 'license.created':
appInstances = myApp().findByExternalUser(fsEvent.objects.user);
// Send push to the app to auto-activate the license
myApp().pushService(appInstances).sendActivationNotice( fsEvent.objects.license ); break;
// Synchronize license
case 'license.plan.changed':
case 'license.extended':
case 'license.shortened':
case 'license.expired':
case 'license.deleted':
case 'license.cancelled':
appInstances = myApp().findByExternalLicenseId(fsEvent.objects.license.id);
myApp().pushService(appInstances).sendSyncSignal();
break;
}
}
```
Here, the `myApp()` is a placeholder for your app's backend service. More information about the webhook listener can be found [here](https://freemius.com/help/help/documentation/saas/app-integration/.md#webhook-integration-optional).
## License Activation Through UI[](#license-activation-through-ui "Direct link to License Activation Through UI")
After the purchase, Freemius will send an email to the buyer with the license key. From your app, you can also provide a UI to enter the license key and activate it.

Our API provides several endpoints to activate licenses and check their status. Please refer to the [License Key Activation](https://freemius.com/help/help/documentation/saas/integrating-license-key-activation/.md) guide for more details.
## Advanced Concepts[](#advanced-concepts "Direct link to Advanced Concepts")
Here are some advanced concepts you might want to consider:
### Dynamically Listing Plans[](#dynamically-listing-plans "Direct link to Dynamically Listing Plans")
If you want to dynamically list the plans available for your product, you can use the [Freemius API](https://docs.freemius.com/api) to fetch the plans and their details. The API endpoints are protected by authentication, so we recommend using a server-side proxy to fetch the plans and then send them to your app.
```
const PRODUCT_ID = '';
const BEARER_TOKEN = '';
app.get('/api/plans', async (req, res) => {
try {
// Fetch plans from Freemius API
const response = await fetch(
`https://api.freemius.com/v1/products/${PRODUCT_ID}/plans`,
{
headers: {
Authorization: `Bearer ${BEARER_TOKEN}`,
},
}
);
if (!response.ok) {
return res
.status(response.status)
.json({ error: 'Failed to fetch plans' });
}
const data = await response.json();
// Extract plan ID and title
const plans = data.plans.map((plan) => ({
id: plan.id,
title: plan.title,
}));
res.json(plans);
} catch (error) {
console.error('Error fetching plans:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
```
Now, from your iOS app, you can call this endpoint to get the list of plans and their details. Please find the [API reference](https://docs.freemius.com/api/plans/list) for more details.
### License Upgrade Flow[](#license-upgrade-flow "Direct link to License Upgrade Flow")
If your buyer has purchased a license already, you can use the Freemius API to generate upgrade links.
1. Your app should call your backend to generate the upgrade link.
2. Your app will send the authenticated user ID and the license ID to your backend.
3. Your backend will call the Freemius API to generate the upgrade link.
Here is an example of how to generate the upgrade link:
```
app.get('/api/upgrade-link/:licenseId/:planId', async (req, res) => {
const { licenseId, planId } = req.params;
// Get authenticated user from the session or token
const { userId } = getAuthenticatedUser(req);
// IMPORTANT: Authorize the request to ensure that the user has access to this license.
userLicenseGatekeeper(userId, licenseId);
try {
// Fetch the upgrade link from Freemius API
const response = await fetch(
`https://api.freemius.com/v1/products/${PRODUCT_ID}/licenses/${licenseId}/checkout/link.json`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${BEARER_TOKEN}`,
},
}
);
if (!response.ok) {
return res
.status(response.status)
.json({ error: 'Failed to fetch upgrade link' });
}
const data = await response.json();
res.json(data.url);
} catch (error) {
console.error('Error fetching upgrade link:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
```
This will return the URL of the hosted checkout page, which you can open in your app. More information can be found in the [API reference](https://docs.freemius.com/api/licenses/generate-upgrade-link).
## Next Steps[](#next-steps "Direct link to Next Steps")
You have now successfully integrated Freemius Checkout links into the iOS app. From here, you can explore more about:
1. [Freemius Checkout SDK](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md) to create a pricing page on your website.
2. [App Integration](https://freemius.com/help/help/documentation/saas/app-integration/.md) to learn deeply about various integration methods.
3. [Checkout Customization](https://freemius.com/help/help/documentation/checkout/customization/applying-css-customization/.md) to customize the checkout with your own branding.
4. [Freemius API](https://docs.freemius.com/api) - Reference to our REST API.
If you have any questions, please feel free to reach out to us using the Help button in the bottom right corner of this page.
---
# Integrating Freemius into Your SaaS Application
This guide will walk you through the essential steps to integrate Freemius into your SaaS application. It covers everything from setting up the checkout process to managing user licenses and subscriptions, setting up webhooks for real-time updates, and providing a customer portal for your users.
**Are you building a SaaS application using Next.js?** You can **skip this** and check out our [Next.js SaaS Starter Guide](https://freemius.com/help/help/documentation/saas-sdk/framework/nextjs/.md) for a complete walkthrough and starter repository on integrating with Freemius.
You can also check our [JavaScript SDK](https://freemius.com/help/help/documentation/saas-sdk/js-sdk/.md) or [Framework Integration Guide](https://freemius.com/help/help/documentation/saas-sdk/framework/.md) for **other popular frameworks**. Read on to learn the core concepts that apply to any technology stack!
## Initial Steps[](#initial-steps "Direct link to Initial Steps")
1. [Sign-up to Freemius](https://dashboard.freemius.com/register/)
2. Create a new SaaS product
3. Go to the Plans and [configure the plans and prices](https://freemius.com/help/help/documentation/saas/saas-plans-pricing/.md)
4. Optionally customize the [unit label](https://freemius.com/help/help/documentation/saas/customize-license-unit-label/.md)
## Checkout Integration[](#checkout-integration "Direct link to Checkout Integration")
You can integrate the Checkout as a [modal dialog triggered using JavaScript](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md), or by using [direct checkout links](https://freemius.com/help/help/documentation/checkout/integration/hosted-checkout/.md).
Regardless of the method you choose, ensure that you set [`user_email`](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#user_email) to the logged-in user’s email. To enforce the purchase is made with the same customer email as in your system, you can direct the checkout to set the email input as read-only by setting [`readonly_user`](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#readonly_user) to `true`.
Here’s an example of a direct checkout link:
```
https://checkout.freemius.com/product/{product_id}/plan/{plan_id}/?user_email={email}&readonly_user=true
```
When `readonly_user` is set in a direct link, the checkout will auto redirect to
```
https://checkout.freemius.com/product/{product_id}/plan/{plan_id}/
```
to ensure the user can’t easily tinker with the email address.
If you’re using the hosted Checkout, we recommend setting up a [redirection URL](https://freemius.com/help/help/documentation/checkout/integration/hosted-checkout/.md#redirection-after-a-successful-purchase) so your app can immediately process the purchase information.
If you're using the modal integration, use the [`success`](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#success) or [`purchaseCompleted`](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#purchaseCompleted) callback handler to receive the purchase data.
note
Please be sure to [URL encode](https://developer.mozilla.org/en-US/docs/Glossary/Percent-encoding) the email address and other parameters when constructing the URL. Here is an example:
* JavaScript
* PHP
```
function generateCheckoutLink(productId, planId, email, otherParams = {}) {
const baseUrl = `https://checkout.freemius.com/product/${productId}/plan/${planId}/`;
const params = new URLSearchParams({
user_email: email,
readonly_user: 'true',
...otherParams,
});
return `${baseUrl}?${params.toString()}`;
}
```
```
function generateCheckoutLink($productId, $planId, $email, $otherParams = []) {
$baseUrl = "https://checkout.freemius.com/product/{$productId}/plan/{$planId}/";
$params = array_merge([
'user_email' => $email,
'readonly_user' => 'true',
], $otherParams);
$queryString = http_build_query($params);
return "{$baseUrl}?{$queryString}";
}
```
## Restricting or Relaxing Single Subscription Per User[](#restricting-or-relaxing-single-subscription-per-user "Direct link to Restricting or Relaxing Single Subscription Per User")
Please note that by default, for SaaS products, users can have only one active subscription at a time. You can relax this limitation if needed by going to **Settings** and disabling the **Restrict Single Subscription Per User** toggle.

To learn how to facilitate upgrades, please [continue reading](#handling-license-or-subscription-upgrades).
tip
If you don't envision supporting multiple subscriptions per user, we recommend keeping the restriction enabled. This approach simplifies your integration, provides better experience to your customers, and reduces potential support requests and disputes. Find our [case study](https://freemius.com/blog/freemius-release-notes-april-2025/#block_duplicate_subscriptions_with_a_simple_toggle).
## Database Updates[](#database-updates "Direct link to Database Updates")
Your database likely have a `user` table with the following columns:
* `id`
* `email`
* …
To store the entitlement mapping between your users and Freemius, we recommend creating a new `user_fs_entitlement` table in your database with the following columns:
* `id` - The primary key.
* `user_id` - A foreign key referencing your `user` table.
* `fs_license_id` - A unique identifier for the Freemius license (text/string).
* `fs_plan_id` - The Freemius plan ID (text/string).
* `fs_pricing_id` - The Freemius pricing ID (text/string).
* `fs_user_id` - The Freemius user ID (text/string).
* `type` - The entitlement type (e.g., `subscription`, `lifetime`, etc.).
* `expiration` - The license expiration timestamp (nullable).
* `is_canceled` - A boolean flag indicating if the license is canceled.
* `created_at` - Timestamp when the record was created.
This table will hold the mapping between your user entities and their corresponding Freemius licenses, plans, and entitlements.
With Freemius, a user’s `License` is what governs their account level, providing you with significant flexibility. This setup allows you to maintain feature access even if a subscription is canceled mid-term, or restrict access even if a subscription remains active. To effectively manage user account levels, ensure that your webhook is configured to handle license-related events (a webhook implementation can be found [below](#creating-a-webhook-listener)).
If you prefer not to store canceled or deleted licenses, you can remove the `is_canceled` column and instead add a `license_id` column to the `user` table, establishing a 1:1 relationship. For the purposes of this example, however, we'll assume that you want to preserve the entire license history.
## Saving Purchase Information[](#saving-purchase-information "Direct link to Saving Purchase Information")
When a user completes a purchase, you will receive the purchase data either through the [redirection URL](https://freemius.com/help/help/documentation/checkout/integration/hosted-checkout/.md#redirection-after-a-successful-purchase) (hosted Checkout) or the [callback function](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#success) (modal Checkout). You should then save the relevant information in your `user_fs_entitlement` table.
Our recommendation is to capture the `license_id` from the purchase data, [query the Freemius API](https://docs.freemius.com/api/licenses/retrieve) to retrieve the complete license details, and then store the necessary fields in your database. Here is an example of how to achieve this:
* JavaScript
* PHP
```
const licenseId = request.query.license_id as string; // Or retrieve it from the modal callback data
// @see https://docs.freemius.com/api/licenses/retrieve
const license = await getLicenseFromFreemiusApi(licenseId);
// @see https://docs.freemius.com/api/users/retrieve
const user = await getUserFromFreemiusApi(license.user_id);
const localUserId = getLocalUserIdByEmail(user.email);
await storeOrUpdateUserFsEntitlement({
user_id: localUserId,
fs_license_id: license.id,
fs_plan_id: license.plan_id,
fs_pricing_id: license.pricing_id,
fs_user_id: license.user_id,
type: license.type,
expiration: license.expiration,
is_canceled: license.is_canceled,
});
```
```
$license_id = $_GET['license_id']; // Or retrieve it from the modal callback data
// @see https://docs.freemius.com/api/licenses/retrieve
$license = getLicenseFromFreemiusApi($license_id);
// @see https://docs.freemius.com/api/users/retrieve
$user = getUserFromFreemiusApi($license->user_id);
$local_user_id = getLocalUserIdByEmail($user->email);
storeOrUpdateUserFsEntitlement([
'user_id' => $local_user_id,
'fs_license_id' => $license->id,
'fs_plan_id' => $license->plan_id,
'fs_pricing_id' => $license->pricing_id,
'fs_user_id' => $license->user_id,
'type' => $license->type,
'expiration' => $license->expiration,
'is_canceled' => $license->is_canceled,
]);
```
Another and more reliable way to capture this information is by setting up a webhook listener (see [below](#creating-a-webhook-listener)). This method ensures that you receive all purchase events, even if the user does not complete the redirection process after checkout.
## Checking User’s Plan[](#checking-users-plan "Direct link to Checking User’s Plan")
To determine the account level of a logged-in user, search for an active/non-canceled license associated with the user. Ensure the license has not expired (via `expiration`) to confirm the user is on the right plan and pricing indicated by `user_fs_entitlement.fs_plan_id` or `user_fs_entitlement.fs_pricing_id`. We recommend using the `pricing_id` for more granular control, especially if you have multiple quotas for the same plan.
* JavaScript
* PHP
```
function getUserPlanId(userId) {
const license = loadLicense({
userId: userId,
isCanceled: false,
});
if (!license) {
return null;
}
// In case you want to support a concept of non-expiring licenses, you can set their expiration to `null`.
if (license.expiration === null) {
return true;
}
const expiration = new Date(license.expiration);
const now = new Date();
return now < expiration ? license.fs_plan_id : null;
}
```
```
function getUserPlanId($userId)
{
$license = loadLicense([
"user_id" => $userId,
"is_canceled" => false,
]);
if (!is_object($license)) {
return null;
}
// In case you want to support a concept of non-expiring licenses, you can set their expiration to `null`.
if (is_null($license->expiration)) {
return true;
}
$expiration = new DateTime($license->expiration);
$now = new DateTime("now");
return $now < $expiration ? $license->fs_plan_id : null;
}
```
**How to Get the Plan ID or Pricing ID?**
To get the plan ID, simply navigate to the **Plan** page under your SaaS product in the [Freemius Developer Dashboard](https://dashboard.freemius.com/).

The ID of the plan is displayed next to the plan name.
To get the pricing ID, click on the specific plan to view its details. The pricing ID is listed next to each pricing option within the plan.

note
Freemius includes a built-in [dunning mechanism](https://freemius.com/help/help/documentation/marketing-automation/dunning-failed-payments/.md). If a subscription renewal fails, the system will automatically attempt to process the payment through a series of emails sent over several days following the original renewal date. The subscription will remain active during this period, even though the license may expire, until the recovery process is complete or the subscription is eventually canceled.
## Handling License or Subscription Upgrades[](#handling-license-or-subscription-upgrades "Direct link to Handling License or Subscription Upgrades")
Freemius allows your users to upgrade the plan or license quota of their subscription. The upgrade process goes through our Checkout again, which collects up-to-date information from the user. After a successful purchase, the license object will be updated with the new information.
The flow involves:
1. Sending an API request from your app with the `license_id` to generate the upgrade Checkout URL.
2. Passing the URL to your user or triggering the modal Checkout (depending on your integration).
3. Synchronizing your SaaS with the up-to-date information.
Below is the API endpoint and an example of how to call it:
```
POST /v1/products/{product_id}/licenses/{license_id}/checkout/link.json HTTP/1.1
Content-Type: application/json
Accept: application/json
Authorization: Bearer 123
Host: api.freemius.com
Content-Length: 127
{
"plan_id": "planID",
"billing_cycle": "annual",
"quota": 1,
"currency": "usd"
}
```
The API will return an object with three properties:
* `url` - The upgrade link of the hosted checkout that you can share.
* `settings` - Configuration parameter that you can use with the modal Checkout.
* `expires` - Expiration date of the link.
You will need to use a [Bearer Token](https://docs.freemius.com/api/section/bearer-token-auth) to authenticate the request from your backend. More information about our REST API is available [here](https://docs.freemius.com/api/).
Once the upgrade is complete, depending on your integration, the Checkout will either redirect to the success URL on your SaaS website or pass the data through the modal integration. From there, you can update your own system to reflect the changes in real time.
## Creating a Webhook Listener[](#creating-a-webhook-listener "Direct link to Creating a Webhook Listener")
We strongly recommend setting up [webhooks](https://freemius.com/help/help/documentation/saas/events-webhooks/.md) to ensure your backend reliably receives upgrade and license change notifications, even if the user's browser fails to complete the redirect or if network interruptions occur during the Checkout process.
* JavaScript
* PHP
```
// Example Express-style handler (ensure raw body is available!)
// For Express, configure a raw body parser middleware to set req.rawBody.
const crypto = require('crypto');
function handleFreemiusWebhook(req, res) {
const body = (req.rawBody || '').toString('utf8');
const hash = crypto
.createHmac('sha256', '')
.update(body)
.digest('hex');
const signature = req.headers['x-signature'] || '';
if (hash !== signature) {
// Invalid signature, don't expose any data to attackers.
return res.status(200).end();
}
const fs_event = JSON.parse(body);
function loadUser(fsUser) {
// Query your local database to find the user ID by email.
const localUser = findUserByEmail(fsUser.email);
if (localUser) return localUser;
// You can choose to create the user here if they don't exist.
return registerUser(fsUser.email, fsUser.first, fsUser.last);
}
function loadLicense(fsLicense) {
// Query your local database to find the license by fs_license_id.
return loadUserFsEntitlement(fsLicense.id);
}
function createUserLicense(fsUser, fsLicense) {
const localUser = loadUser(fsUser);
// Insert or update the user's entitlement in your local database.
storeOrUpdateUserFsEntitlement({
user_id: localUser.id,
fs_license_id: fsLicense.id,
fs_plan_id: fsLicense.plan_id,
fs_pricing_id: fsLicense.pricing_id,
fs_user_id: fsLicense.user_id,
type: fsLicense.type,
expiration: fsLicense.expiration,
is_canceled: fsLicense.is_canceled,
});
}
function updateUserLicense(fsUser, fsLicense) {
let license = loadLicense(fsLicense);
if (!license) {
// Likely the 'license.created' event failed processing.
createUserLicense(fsUser, fsLicense);
} else {
license.plan_id = fsLicense.plan_id;
license.pricing_id = fsLicense.pricing_id;
license.expiration = fsLicense.expiration;
license.is_canceled = fsLicense.is_canceled;
license.update();
}
// You can also notify the user of the plan change or grant additional entitlements.
return license;
}
switch (fs_event.type) {
case 'license.created':
createUserLicense(fs_event.objects.user, fs_event.objects.license);
break;
case 'license.extended':
case 'license.shortened':
case 'license.updated':
case 'license.cancelled':
case 'license.expired':
case 'license.plan.changed':
updateUserLicense(fs_event.objects.user, fs_event.objects.license);
break;
}
return res.status(200).end();
}
```
```
");
$signature = $_SERVER["HTTP_X_SIGNATURE"] ?? "";
if (!hash_equals($hash, $signature)) {
// Invalid signature, don't expose any data to attackers.
http_response_code(200);
exit();
}
$fs_event = json_decode($input);
function loadUser($fsUser)
{
// Query your local database to find the user ID by email.
$local_user = findUserByEmail($fsUser->email);
if ($local_user) {
return $local_user;
}
// You can choose to create the user here if they don't exist.
return registerUser($fsUser->email, $fsUser->first, $fsUser->last);
}
function loadLicense($fsLicense)
{
// Query your local database to find the license by fs_license_id.
return loadUserFsEntitlement($fsLicense->id);
}
function createUserLicense($fsUser, $fsLicense)
{
$local_user = loadUser($fsUser);
// Insert or update the user's entitlement in your local database.
storeOrUpdateUserFsEntitlement([
"user_id" => $local_user->id,
"fs_license_id" => $fsLicense->id,
"fs_plan_id" => $fsLicense->plan_id,
"fs_pricing_id" => $fsLicense->pricing_id,
"fs_user_id" => $fsLicense->user_id,
"type" => $fsLicense->type,
"expiration" => $fsLicense->expiration,
"is_canceled" => $fsLicense->is_canceled,
]);
}
function updateUserLicense($fsUser, $fsLicense)
{
$license = loadLicense($fsLicense);
if (!is_object($license)) {
// Likely the 'license.created' event failed processing.
createUserLicense($fsUser, $fsLicense);
} else {
$license->plan_id = $fsLicense->plan_id;
$license->pricing_id = $fsLicense->pricing_id;
$license->expiration = $fsLicense->expiration;
$license->is_canceled = $fsLicense->is_canceled;
$license->update();
}
// You can also do other stuff like notifying the user of the plan change or give additional entitlements etc.
return $license;
}
switch ($fs_event->type) {
case "license.created":
createUserLicense($fs_event->objects->user, $fs_event->objects->license);
break;
case "license.extended":
case "license.shortened":
case "license.updated":
case "license.cancelled":
case "license.expired":
case "license.plan.changed":
updateUserLicense($fs_event->objects->user, $fs_event->objects->license);
break;
}
http_response_code(200);
```
To trigger custom emails for specific subscription events, you can handle events such as `subscription.created` and `subscription.canceled`.
Using a JavaScript Environment?
Do check our [JS SDK Integration Doc](https://freemius.com/help/help/documentation/saas-sdk/js-sdk/integration/.md) for an easier integration.
## Customer Portal[](#customer-portal "Direct link to Customer Portal")
Freemius comes with a self-service [customer dashboard](https://freemius.com/help/help/documentation/users-account-management/.md) out-of-the-box. Allowing your customers to easily access their order history, subscriptions, billing information, license keys, and more. They can change plans, update payment methods, and cancel subscriptions, putting control directly in their hands.
Alternately if your front-end is using React, you can integrate the [Customer Portal Component](https://freemius.com/help/help/documentation/saas-sdk/react-starter/components/.md#customer-portal-component) directly into your SaaS application.
However you’d like to implement your own, within your SaaS, here are the API endpoints that will help you out:
**[Payments history](https://docs.freemius.com/api/users/list-payments)**
```
GET https://api.freemius.com/v1/products/{product_id}/users/{user_id}/payments.json
```
When selling add-ons:
```
GET https://api.freemius.com/v1/stores/{store_id}/users/{user_id}/payments.json
```
**Invoice download**
```
GET https://api.freemius.com/v1/products/{product_id}/users/{user_id}/payments/{payment_id}/invoice.pdf
```
**[Payment method update](https://docs.freemius.com/api/licenses/generate-upgrade-link)**
```
POST https://api.freemius.com/v1/products/{product_id}/licenses/{license_id}/checkout/link.js
Content-Type: application/json
Accept: application/json
Authorization: Bearer 123
Host: api.freemius.com
Content-Length: 127
{
"is_payment_method_update": true
}
```
**[Plan change](https://docs.freemius.com/api/licenses/generate-upgrade-link)**
```
POST https://api.freemius.com/v1/products/{product_id}/licenses/{license_id}/checkout/link.js
Content-Type: application/json
Accept: application/json
Authorization: Bearer 123
Host: api.freemius.com
Content-Length: 127
{
"plan_id": "newPlanID”
}
```
**[Get subscription by license](https://docs.freemius.com/api/licenses/retrieve-latest-subscription)**
```
GET https://api.freemius.com/v1/products/{product_id}/licenses/{license_id}/subscription.json
```
**[Get subscriptions](https://docs.freemius.com/api/users/list-subscriptions)**
```
GET https://api.freemius.com/v1/products/{product_id}/users/{user_id}/subscriptions.json
```
**[Cancel subscription by license](https://docs.freemius.com/api/licenses/cancel-current-subscription)**
```
DELETE https://api.freemius.com/v1/products/{product_id}/licenses/{license_id}/subscription.json
```
**[Cancel subscription](https://docs.freemius.com/api/subscriptions/cancel)**
```
DELETE https://api.freemius.com/v1/products/{product_id}/subscriptions/{subscription_id}.json
```
**[Get licenses](https://docs.freemius.com/api/users/list-licenses)**
```
GET https://api.freemius.com/v1/products/{product_id}/users/{user_id}/licenses.json
```
When selling add-ons:
```
GET https://api.freemius.com/v1/stores/{store_id}/users/{user_id}/licenses.json
```
---
# SaaS and Apps Plans & Pricing
In this guide we will learn how to configure pricing plans for your users, each with distinct billing cycles, prices, number of units (e.g., seats, devices, credits), currencies and feature sets.

## Setting up plans[](#setting-up-plans "Direct link to Setting up plans")
To create your first plan:
1. Log in to the [Developer Dashboard](https://dashboard.freemius.com).
2. Navigate to the ***Plans*** page.
3. Click the **Add Plan** button to create a new plan.

4. Then insert a specify a title and unique name e.g., 'Pro' and 'pro'.
5. Enter information about the plan such as pricing for the **Single Unit** option.

tip
* You can create pricing options for plans that offer more than 1 unit. To do this click **Add Bulk** and select the number of units, then enter the relevant pricing options.
* Similarly, you can add pricing in other currencies. This is done by clicking **Add Currency** and select the number of units, then enter the relevant pricing options.

The pricing plans will be displayed in the [Freemius Checkout](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md) when customers purchase your product, as seen here:

## Plans Customization[](#plans-customization "Direct link to Plans Customization")
There are other settings you can fill out to customize your plans, such as:
* [Additional currencies](https://freemius.com/help/help/documentation/selling-with-freemius/multi-currency/.md)
* [Free and Paid Trials](#free-and-paid-trials)
* [Features](#configure-a-plans-features)
* [Refund Policy](https://freemius.com/help/help/documentation/saas/setup-refund-policy/.md)
* [Checkout Confirmation Dialog](#checkout-confirmation-dialog)
* [Checkout Success Redirection](#checkout-success-redirection)
* [Customize License Unit Label](https://freemius.com/help/help/documentation/saas/customize-license-unit-label/.md)
* [Checkout CSS](https://freemius.com/help/help/documentation/checkout/customization/applying-css-customization/.md)
### Free and Paid Trials[](#free-and-paid-trials "Direct link to Free and Paid Trials")
Freemius enables you to easily offer free trials for your users, with or without requiring a payment method. Offering a free trial may help you increase your product’s distribution and sales. See the [supported trial types](https://freemius.com/help/help/documentation/wordpress/free-trials/.md).
### Configure a Plan's Features[](#configure-a-plans-features "Direct link to Configure a Plan's Features")
You can set up the features for each plan.

These can be set up by:
1. Navigating to ***Plans*** page.
2. Click the relevant plan to edit its details.
3. Scroll down to the **Features** section.
4. Add the respective feature details. Type the name of the feature and click the “+” button or press enter to add it to the plan. Repeat this step until you've added all the required features.
tip
These features can be accessed later using the [Features API endpoint](https://docs.freemius.com/api/products/list-features).
### Checkout Confirmation Dialog[](#checkout-confirmation-dialog "Direct link to Checkout Confirmation Dialog")
After purchasing, customers will see a confirmation dialog that includes a thank-you message and a close button. The custom dialog allows the title, message and button to be configured. It is accessible via the **Customization** tab, under the **Plans** page. [Learn more](https://freemius.com/help/help/documentation/checkout/customization/customizing-confirmation-dialog/.md).

### Checkout Success Redirection[](#checkout-success-redirection "Direct link to Checkout Success Redirection")
Under the **Plans** page, via the **Customization** tab, you can set a redirection URL where the Checkout will take the customer after a successful purchase. It will work only for Checkouts opened in page mode and will not show the confirmation dialog (even if customized). The URL will be automatically populated with purchase data. [Learn more](https://freemius.com/help/help/documentation/checkout/integration/hosted-checkout/.md#redirection-after-a-successful-purchase).

note
Click [here](https://freemius.com/help/help/documentation/wordpress/setup-product-pricing-plans-refunds/.md) to set up WordPress and other products pricing.
---
# Setup Recommended Refund Policy for your SaaS or App
You can tailor the refund policy for your SaaS or App products sold through Freemius to help build trust with your customers and reduce purchase anxiety.
The refund policy settings are accessible via the **Plans** page. Click the **Refund Policy** tab to reveal the settings.

To learn more about the available refund policy options visit our [Refund Policy Guide](https://freemius.com/help/help/documentation/selling-with-freemius/refund-policy/.md).
Here are our recommendations for your product:
## For SaaS Makers[](#for-saas-makers "Direct link to For SaaS Makers")
Refund policies for SaaS should reflect **how and when value is delivered**, as well as whether the usage has a real, irreversible cost.
### Traditional SaaS (Low or No Usage-based Costs)[](#traditional-saas-low-or-no-usage-based-costs "Direct link to Traditional SaaS (Low or No Usage-based Costs)")
Examples include analytics tools, form builders, CRMs, subscription apps, and other services where user activity does not meaningfully increase your operating costs.
For these products, we generally recommend offering a **[money-back guarantee](https://freemius.com/help/help/documentation/selling-with-freemius/refund-policy/.md#strict---money-back-guarantee) of 14 to 30 days**. This aligns with common SaaS buyer expectations, reduces purchase hesitation, and typically results in higher conversion rates with limited abuse.
The exact refund period should be determined by how quickly new users can realistically experience value. Simpler, self-serve products can often use a shorter window, while more complex tools may benefit from a longer one. Refunds are typically limited to the first billing cycle.
### SaaS with Ongoing, Usage-based Features[](#saas-with-ongoing-usage-based-features "Direct link to SaaS with Ongoing, Usage-based Features")
These include AI-assisted tools, chatbots, monitoring services, or platforms where value accumulates over time and depends on continued use.
In these cases, a **shorter [money-back guarantee](https://freemius.com/help/help/documentation/selling-with-freemius/refund-policy/.md#strict---money-back-guarantee) refund window of 7 to 14 days** can still be effective, especially when paired with clear usage boundaries. Many makers choose to limit refunds to **[unused credits](https://freemius.com/help/help/documentation/selling-with-freemius/refund-policy/.md#consumptive-usage)** only, or make refunds conditional on reasonable usage during the refund period.
This approach helps maintain buyer confidence while protecting the business from excessive or abusive usage.
### SaaS with One-off or Consumptive Usage[](#saas-with-one-off-or-consumptive-usage "Direct link to SaaS with One-off or Consumptive Usage")
Examples include AI content generation, video or image processing, data exports, or any service where value is delivered instantly and cannot be undone.
For these products, we generally **do not recommend offering refunds for consumed usage**. Once the service has been used and the output has been delivered, it is difficult to prevent abuse.
Instead of refunds, consider offering:
* A free preview or limited free tier
* Watermarked or partial outputs
* A small amount of trial credits
If a refund policy is offered at all, it should be limited to **[unused credits only](https://freemius.com/help/help/documentation/selling-with-freemius/refund-policy/.md#consumptive-usage)**, and clearly state that no refunds are available once usage has occurred.
## For App Makers (Downloadable Software)[](#for-app-makers-downloadable-software "Direct link to For App Makers (Downloadable Software)")
This category includes desktop and mobile applications that are **downloaded and installed locally**, such as macOS, Windows, or Linux apps, as well as packaged mobile apps sold outside of app stores.
For most downloadable apps, we generally recommend offering a **limited [money-back guarantee](https://freemius.com/help/help/documentation/selling-with-freemius/refund-policy/.md#strict---money-back-guarantee) of 7 to 14 days**. This gives users enough time to install the app, test compatibility with their system, and evaluate core functionality, while keeping the refund window short enough to reduce abuse.
That said, refunds may **not be appropriate** for apps that:
* Deliver their full value immediately upon download or first use
* Are designed for one-time or short-term use
* Allow easy copying, exporting, or offline reuse after installation
Examples include license unlockers, media conversion tools, utilities with instant output, or software where continued access does not depend on an ongoing service.
In such cases, many app makers choose a **no-refund policy** and instead rely on:
* Free trials or feature-limited demo versions
* Watermarked exports or restricted functionality
* Clear system requirements and preview materials
This approach helps set expectations upfront while minimizing the risk of refund abuse.
Disputes & Chargebacks
In the event, the buyer disputes a charge, see how we handle [disputes and chargebacks](https://freemius.com/help/help/documentation/selling-with-freemius/refund-policy/.md#disputes--chargebacks).
---
# Security
Learn all about securing your Developer Dashboard account.
---
# Team Member & Role Management
You can easily manage who can access a product via the **Team** section in the Developer Dashboard.
1. Log in to the [Freemius Developer Dashboard](https://dashboard.freemius.com/).
2. Click the **Products** tab under the Freemius logo on the top left.
3. Choose the product you wish to manage.
4. Navigate to **Team** in the menu on the left.

### Adding a Team Member[](#adding-a-team-member "Direct link to Adding a Team Member")
1. Click the **Add Member...** button. If your team member doesn't have a Freemius account, they first need to [create an account](https://dashboard.freemius.com/register/) as a preliminary step before you can add them.
2. Fill in the new member’s email address.
3. Select to confirm the suggested member.
4. Click **Yes - Add** button to add the member.
[](/help/videos/freemius-developer-dashboard-add-team-member.mp4)
### Removing a Team Member[](#removing-a-team-member "Direct link to Removing a Team Member")
1. Click the role next to the team member you want to remove and choose the **Remove member** option from the list.
2. Confirm the removal action.
[](/help/videos/freemius-developer-dashboard-remove-team-member.mp4)
## Managing the team member roles[](#managing-the-team-member-roles "Direct link to Managing the team member roles")
### Roles Overview[](#roles-overview "Direct link to Roles Overview")
These are the different roles that Freemius currently supports.
| Roles | Permissions |
| ---------- | --------------------------------------------------------------------------------------------- |
| Owner | Has full access to all platform features and settings and can change product ownership. |
| Admin | Has full access to all platform features and settings but cannot change product ownership. |
| Developer | Focuses on product development and has access to development tools. |
| Support | Assists users with queries and issues, plus having access to support tools and user accounts. |
| Accountant | Manages financial aspects, including invoicing and payments. |
### Changing a Team Member’s Role[](#changing-a-team-members-role "Direct link to Changing a Team Member’s Role")
Click the role of the member and choose the new role you’d like to assign them.
[](/help/videos/freemius-developer-dashboard-changing-team-roles.mp4)
### Assigning a New Owner[](#assigning-a-new-owner "Direct link to Assigning a New Owner")
Click the role of the member and choose the **Owner** role.
[](/help/videos/freemius-developer-dashboard-change-ownership.mp4)
---
# Two-Factor Authentication (2FA)
Freemius Developer Dashboard supports Two-Factor Authentication (2FA) to protect your account against unauthorized access.
## How to activate 2FA?[](#how-to-activate-2fa "Direct link to How to activate 2FA?")
We recommend the Google Authenticator or Microsoft Authenticator app from the Google Play Store or App Store.
* Go to your [Profile](https://dashboard.freemius.com/#!/profile/) (top-right menu).
* Under the *Security* section, turn on the *Activate the two-step authentication*.
* Follow the on-screen instructions.

Please also save the generated backup code somewhere safe. The backup code is used to recover your account in case the authenticator app is not accessible anymore:

## How to deactivate 2FA?[](#how-to-deactivate-2fa "Direct link to How to deactivate 2FA?")
You will need the authenticator app to deactivate 2FA.
* Go to the profiles page again.
* Simply click on the *Deactivate two-step authentication* toggle and follow the on-screen instruction.

## What if I lose my Authenticator app?[](#what-if-i-lose-my-authenticator-app "Direct link to What if I lose my Authenticator app?")
When you register for 2FA, you would be given a backup code. Please use the backup code while logging in to reset the 2FA authentication. After a successful login, please enable 2FA again.

## What if I lose my backup code?[](#what-if-i-lose-my-backup-code "Direct link to What if I lose my backup code?")
So as long as you have access to the authenticator app, you can re-generate backup codes.
* Go to the profiles page.
* Click on *generate a backup code* and follow the on-screen instruction.

If you have lost both your authenticator app and the backup code, please contact us directly through our support. We will use additional validation method to have your account recovered.
---
# Selling Templates
Whether you're selling Static files, HTML templates, Starter Kits, Elementor Template kits, themes for Joomla, Magento, Laravel or any other platform templates and kits, Freemius makes it easy to monetize templates and kits.
This section walks you through the the tools and resources for the product setup:
* [Creating products, plans and refund policy](https://freemius.com/help/help/documentation/selling-templates/templates-plans-pricing/.md).
* [Deploying your product](https://freemius.com/help/help/documentation/selling-templates/deployments/.md).
Whether you’re launching your first product or migrating from another provider or marketplace, this section gives you the steps to get started with confidence.
---
# Deploying your Files
Creating a release is the only step required to make your template files available for download after a successful purchase. Follow the steps below to deploy your template files:
1. First, ensure to create a ZIP file containing your template files. This ZIP file will be downloaded by your customers after a successful purchase.
Release Notes
We recommend including a `readme.txt` or `readme.md` file in your ZIP file that contains release notes for the version you are releasing. This will help your customers understand what changes or updates are included in the new version or how to use the template files effectively.
2. Navigate to the ***Deployment*** page in the [Freemius Developer Dashboard](https://dashboard.freemius.com/).
3. Click the **Add New Version** button.

4. Add the version number and the required platform version. The version number should follow the [Semantic Versioning](https://freemius.com/help/help/documentation/release-management/semantic-versioning/.md) so as to make it easier to manage updates and compatibility.

5. The dialog box will prompt you to upload a ZIP file containing your template files. Select the right file and upload it.

6. Once the upload is complete, by default, the zip has an `Unreleased` tag.
Unreleased Tag
The zip is not available for the customers to download, however, this allows you to download the files and test.
7. Once everything is set, change the status from `Unreleased` to `Released` to make the files available for download after a successful purchase for your customers.

Once the release is made, your customers will be able to download the template files after a successful purchase. Learn more about [distributing your templates and providing updates](https://freemius.com/help/help/documentation/selling-templates/distribution/.md). If you wish to test the checkout flow here how you can make a [sandbox purchase](https://freemius.com/help/help/documentation/checkout/integration/testing/.md).
---
# Distributing your Templates and Providing Updates
Now that you have released your template files, they are ready for distribution. Your customers will be able to download the template files via the following channels:
## Checkout Confirmation Dialog[](#checkout-confirmation-dialog "Direct link to Checkout Confirmation Dialog")
Once your customers complete the checkout, the purchase confirmation dialog box will have a link to download the template files. The customer can click on the link to download the ZIP file.

Download Link Availability
The download link is temporarily available for a limited time after the purchase. It will expire. This is to ensure that customers have enough time to download their files while also maintaining security and preventing unauthorized access to the files.
## After Purchase Email[](#after-purchase-email "Direct link to After Purchase Email")
Freemius sends a purchase confirmation email to the customer. Once you've [configured the installation instructions](https://freemius.com/help/help/documentation/selling-templates/installation-instruction/.md), the email will contain the instructions along with the download link for the template files.

## Customer Portal[](#customer-portal "Direct link to Customer Portal")
Additionally, after a successful purchase, the customer will receive a purchase confirmation email that contains the link to the Customer Portal.
The customer can click on the link to access the [Customer Portal](https://customers.freemius.com). Then navigate to the [**Downloads**](https://freemius.com/help/help/documentation/users-account-management/downloads/.md) page to find the download link for their purchased templates.

This is also the place from where the custmers can download latest updates.
---
# Set Download and Installation Instructions
We recommend setting up clear installation instructions for your customers to ensure they have a smooth experience after they purchase your template files.
1. Go to the **Emails** page and click on the **Install Instructions** tab.

2. Click the **Add step** button to add explanations. The content accepts a subset of markdown as explained in the UI.
3. Use the special `{download_url}` variable to insert a secure download link.
4. Add any other instructions or information in additional steps.
Once set, your customers will receive the instructions in the [purchase confirmation email](https://freemius.com/help/help/documentation/selling-templates/distribution/.md#after-purchase-email).
---
# Setup Product, Plans & Pricing
Here is how to set up your product, plans, billing cycles, prices and currencies for your templates and kits.
## Add the product[](#add-the-product "Direct link to Add the product")
To create your product;
1. Log in to the [Developer Dashboard](https://dashboard.freemius.com).
2. Click the "**+**" in the top right corner next to the profile picture and click "New Product".

3. Add the required details for your product, which include:
1. A store name,
2. Title e.g., 'My Template'
3. And the downloadable file name. This is a unique name for your product e.g. `my-template`.
4. Upload the product icon to match your brand.

4. Click the "Get Started" button to create your product.
## Add plans[](#add-plans "Direct link to Add plans")
Creating a new product comes with a default "Pro" plan. You can edit this plan or create more plans for your product. Each plan can have different pricing, billing cycles and settings.

To create your a plan:
1. Under the ***Plans*** page, click the **Add Plan** button to create a new plan.

2. Then insert a title and unique name e.g., 'Pro' and 'pro' respectively.
3. Click the "**Create New**" button to complete the plan setup.
note
* You can create pricing options for plans that offer more than 1 site usage by clicking **Add Bulk** and select the number of units, then enter the relevant pricing options.
* Similarly, you can add pricing in [other currencies](https://freemius.com/help/help/documentation/selling-with-freemius/multi-currency/.md) by clicking **Add Currency** and select the number of units, then enter the relevant pricing options.
Set up One-Time Fee
To sell templates and kits that don't require recurring payments, you can create a plan with a one-time/lifetime purchase product by adding a price to the Lifetime input field only. This can also be setup for bulk and multiple currencies as in the note above.

The pricing plans will be displayed in the [Freemius Checkout](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md) when customers purchase your product, as seen here:

## Plans Customization[](#plans-customization "Direct link to Plans Customization")
To customize the different settings for a specific plan under the ***Plans*** page.
1. Click the respective settings tab.
2. Change the settings as needed.

Here are the settings you can customize for each plan:
### Refund Policy[](#refund-policy "Direct link to Refund Policy")
By default, the product has a "No Refunds" policy, which means that all sales are final.
This is the recommended option because refunds are only expected if the buyer has not downloaded or accessed the software. Once downloaded, refund requests can be declined unless you choose otherwise. This helps reduce abuse for easily copyable or one-time-use software. However, this can be changed.
To learn more about the available refund policy options visit our [Refund Policy Guide](https://freemius.com/help/help/documentation/selling-with-freemius/refund-policy/.md).
### Prorated Discount Period[](#prorated-discount-period "Direct link to Prorated Discount Period")
Freemius supports [proration](https://freemius.com/help/help/documentation/selling-with-freemius/.md) out of the box, giving incentive to your buyers to upgrade to a higher plan or pricing. While subscriptions are automatically prorated based on the remaining time in the billing cycle, lifetime licenses proration period is set to 30 days by default. You can customize this period.
### Payment Recovery Flow[](#payment-recovery-flow "Direct link to Payment Recovery Flow")
Freemius [sends recovery emails](https://freemius.com/help/help/documentation/marketing-automation/dunning-failed-payments/.md). to customers when a payment fails. By default, these link to our Checkout app at `checkout.freemius.com`.
Some buyers may find it confusing to receive an email from your brand/website but be redirected to a third-party domain, which could be mistaken for phishing.
Here you can set a custom recovery URL on your own website for customers.
### Renewals Discount[](#renewals-discount "Direct link to Renewals Discount")
For products with recurring billing, you can set up a discount for customers who renew their subscription. This is a great way to incentivize customers to continue using your product and reward their loyalty.
---
# Selling with Freemius
Welcome to Freemius!
If we have to guess - you’re probably here because you’re looking for a comprehensive, secure and easy solution that will enable you to sell your digital product.
If that’s the case - then you’re in the right place, because that’s exactly what Freemius does best. If that’s not the case - you’re welcome to keep on reading this documentation but it might not be very useful for you.
In case you’re still not sure if Freemius is right for your needs, let’s quickly walk through some of the major features & solutions that Freemius offers for plugin & theme authors.
Find more information about:
* [Trial Activations](https://freemius.com/help/help/documentation/selling-with-freemius/set-up-trials/.md),
* [Coupon Discounts](https://freemius.com/help/help/documentation/selling-with-freemius/coupon-discount/.md),
* [Refund Policy](https://freemius.com/help/help/documentation/selling-with-freemius/refund-policy/.md) and [Refund Payments](https://freemius.com/help/help/documentation/selling-with-freemius/refund-payment/.md),
* [Multi-currency Support](https://freemius.com/help/help/documentation/selling-with-freemius/multi-currency/.md),
* [License Renewal](https://freemius.com/help/help/documentation/selling-with-freemius/license-renewals-mechanism/.md),
* [Supported Countries](https://freemius.com/help/help/documentation/selling-with-freemius/supported-countries/.md),
* [Supported Product Types](https://freemius.com/help/help/documentation/selling-with-freemius/allowed-prohibited-products/.md),
* [Getting Paid](https://freemius.com/help/help/documentation/selling-with-freemius/your-earnings/.md),
* [Product Retirement](https://freemius.com/help/help/documentation/selling-with-freemius/product-retirement/.md).
These features and more are designed to help you effectively build your software business with ease and confidence using Freemius.
---
# Allowed & Prohibited Products
Freemius is exclusively focused on serving makers selling software like SaaS and downloadable software. We do not support the selling of physical goods or human services like consultations. We also don’t allow anything illegal, harmful, abusive, deceptive, or sketchy—our goal is to support trustworthy makers building real software businesses.
## Acceptable Products[](#acceptable-products "Direct link to Acceptable Products")
We support three main software categories:
* **SaaS (Software-as-a-Service)** Cloud-based software accessed via the web. *Examples: Analytics tools, REST APIs, design platforms, productivity apps, AI-powered services, web-based games.*
* **Downloadable Software** Software that users install on their devices or servers after purchase. *Examples: WordPress plugins & themes, desktop and mobile apps and games, browser extensions, developer tools.*
* **Static Software** Code-based products that don’t require back-end servers or dynamic processing. *Examples: Boilerplates, starter kits, themes, HTML templates, code libraries, dashboard templates, template kits.*
Modern software often blends these categories—for example, SaaS products may include downloadable components for easy integrations, and apps can offer cloud-based features through connected services. We fully support these combinations, as long as the core product remains software.
## Prohibited Products[](#prohibited-products "Direct link to Prohibited Products")
warning
**Important:** If you violate our [Vendor Terms of Service](https://freemius.com/terms/vendor/) or attempt to sell prohibited products, your account may be restricted, placed under review, or suspended without notice—especially if we determine your business to be deceptive, fraudulent, high-risk, or low quality with excessive risk of refunds or chargebacks.
### Non-Software Products[](#non-software-products "Direct link to Non-Software Products")
* Physical goods or services requiring physical delivery.
* Human services like SEO, marketing, design, development, consulting, or support.
* SaaS products requiring fulfillment through human services or physical delivery.
* Hosting services.
* Marketplaces selling third-party products via Freemius.
### Illegal or Restricted by Law[](#illegal-or-restricted-by-law "Direct link to Illegal or Restricted by Law")
* Any product violating any law or government regulation.
* Adult and other age-restricted products.
* Gambling-related products.
### Harmful or Infringing AI Content Generation[](#harmful-or-infringing-ai-content-generation "Direct link to Harmful or Infringing AI Content Generation")
Software or AI tools that generate or facilitate:
* Copyright or trademark infringement.
* Impersonation, misleading identity representation, or deceptive content.
* Deepfakes, unauthorized face swaps, or voice cloning.
* Adult, sexually explicit, or NSFW content.
* Content designed to bypass platform, legal, or safety restrictions.
This includes services marketed as "unrestricted", "no moderation", or similar positioning that enables abusive or illegal use cases.
### Intellectual Property Violations[](#intellectual-property-violations "Direct link to Intellectual Property Violations")
Products that infringe on, or facilitate the infringement of, trademarks, copyrights, terms of service, or trade secrets. This includes, but is not limited to:
* IPTV services.
* Illicit streaming services.
* Streaming downloaders.
* Media burning software used to copy or distribute protected content.
### Unlicensed or Resale-Restricted Products[](#unlicensed-or-resale-restricted-products "Direct link to Unlicensed or Resale-Restricted Products")
Products you didn’t create or don’t fully own the rights to, including, but not limited to:
* Products for which you lack proper licenses or original intellectual property rights.
* Private Label Rights (PLR) or Master Resell Rights (MRR) products.
* Any resold product, even with a reseller certificate.
### Medical & Health Advice[](#medical--health-advice "Direct link to Medical & Health Advice")
Software or AI tools that provide medical, diagnostic, treatment, prescription, mental health, or other health-related advice without appropriate regulatory compliance, licensing, oversight, or disclaimers.
This includes products that may create risk of harm by presenting themselves as professional medical or healthcare guidance.
### Payment Processing Restrictions[](#payment-processing-restrictions "Direct link to Payment Processing Restrictions")
* Products listed on [Stripe’s restricted businesses](https://stripe.com/legal/restricted-businesses).
* Products listed on [PayPal’s prohibited list](https://www.paypal.com/us/legalhub/paypal/acceptableuse-full).
* Products that violate rules set by Card Brand Networks (e.g., Visa or MasterCard).
### Unauthorized Access & Hacking Tools[](#unauthorized-access--hacking-tools "Direct link to Unauthorized Access & Hacking Tools")
Products or services that enable unauthorized access to systems, data, or devices belonging to others, including but not limited to:
* Spyware
* Keyloggers
* Phone or device unlocking services
* Hacking tools or exploits
* Password recovery or cracking tools
### Deceptive, Risky, or Harmful[](#deceptive-risky-or-harmful "Direct link to Deceptive, Risky, or Harmful")
* Sexually-oriented or pornographic content, including NSFW chatbots.
* Products threatening Freemius' or our partners’ reputation.
* Products that cause, or pose a significant risk of, chargebacks, refunds, fines, damages, or other liabilities.
warning
We reserve the right to update this list at any time.
## FAQ[](#faq "Direct link to FAQ")
### Can I sell a subscription that includes access to support?[](#can-i-sell-a-subscription-that-includes-access-to-support "Direct link to Can I sell a subscription that includes access to support?")
Yes, you can—**as long as the subscription unlocks real product features**. Support can be part of the value you offer, but the core of what you're selling must be your software.
In open-source ecosystems—like WordPress—it’s common to include premium support as part of a paid license. That’s totally fine, provided there are tangible features beyond just access to support.
If you stop providing support and we receive valid claims from buyers—with evidence—that they’re not getting the help they paid for, we’ll reach out to you first. If the issue isn’t resolved, we may be forced to issue refunds to protect the buyer experience and maintain trust.
### Can I use Freemius to sell my product on behalf of someone else?[](#can-i-use-freemius-to-sell-my-product-on-behalf-of-someone-else "Direct link to Can I use Freemius to sell my product on behalf of someone else?")
No. You must be the creator or legal rights holder of the product. Freemius is designed for makers selling their own software, not as a platform for reselling others' products.
### Can I sell software related to crypto or blockchain?[](#can-i-sell-software-related-to-crypto-or-blockchain "Direct link to Can I sell software related to crypto or blockchain?")
Crypto-related products fall under restricted categories due to high risk. Some may be allowed on a case-by-case basis, but they require additional review. Please contact us to discuss before selling anything blockchain-related.
### Why can’t I sell services like support or product customization?[](#why-cant-i-sell-services-like-support-or-product-customization "Direct link to Why can’t I sell services like support or product customization?")
We get it—paid customizations and dedicated support plans often go hand-in-hand with software. While we plan to support service-based add-ons in the future, right now, offering services through Freemius brings compliance, legal, and risk challenges we’re not equipped to handle:
* **Tax & Legal Complexity:** Services are taxed differently than software, and handling that properly requires separate systems and processes. Plus, the legal documents we generate—like Buyer Terms and the EULA—are designed for software sales and would need major adjustments to cover services.
* **Fulfillment Risks:** As your Merchant of Record, we’re responsible for the transaction. If a customer claims "Service not as described", we have no way to verify delivery, which could force us to issue refunds—sometimes for thousands of dollars.
Moreover, Freemius is built from the ground up for selling software. From license key management to automated software updates and software-focused workflows, our platform is tailored to digital products—not services.
### Can I sell pre-launch orders or charge for early access before my product is ready?[](#can-i-sell-pre-launch-orders-or-charge-for-early-access-before-my-product-is-ready "Direct link to Can I sell pre-launch orders or charge for early access before my product is ready?")
Usually, no. Pre-orders and paid waitlists are considered high-risk for us. If the product or service isn’t delivered as promised, buyers may demand refunds or file disputes, exposing both you and Freemius to financial and reputational risk.
That said, we do make exceptions in high-trust cases—but trust is key. Here’s when we might allow it:
* If you have a proven track record with Freemius, supported by solid historical payment data.
* If you have other actively selling products on Freemius, where existing income can serve as collateral.
In these cases, pre-orders may be allowed, but we may need to adjust your payout terms—holding some or all funds until fulfillment is verified.
---
# How to Transfer a Product Between Different Stores
In Freemius, you have the flexibility to move products between different stores. This is particularly useful when reorganizing for better operational clarity or adjusting ownership.
## Transferring Products Across Stores[](#transferring-products-across-stores "Direct link to Transferring Products Across Stores")
To move a product from one store to another, follow these steps:
1. Log in to the [Developer Dashboard](https://dashboard.freemius.com/).
2. Navigate to the ***Settings*** page of the relevant product.
3. Open the store selection dropdown in the **Information** tab near the bottom.
4. Choose the target store from the dropdown.
5. Click the "Update" button at the bottom of the page to complete the process.

## Effects of the Product Transfer[](#effects-of-the-product-transfer "Direct link to Effects of the Product Transfer")
When you move a product from one store to another, the process is seamless and customers are not affected. All historical and customer data, including purchases, licenses, product settings, and configurations, remains intact and associated with the product.
---
# Setting up Coupon Discounts with Configurable Segments, Types & Plans
Coupons are a powerful marketing tool that allow you to offer discounts to customers, incentivizing them to make a purchase or renew their subscription.
[](/help/videos/freemius-checkout-manually-apply-coupon.mp4)
## How to create a discount coupon[](#how-to-create-a-discount-coupon "Direct link to How to create a discount coupon")
A coupon can be created at both the **store** and **product** levels. If a coupon is created at the store level, it can be applied to any product within that store. If a coupon is created at the product level, it will only be applicable to that specific product.
Follow these steps to create a new coupon:
1. Log in to your [Freemius Developer Dashboard](https://dashboard.freemius.com/).
2. Select the store or product where you want to create the coupon.
3. Navigate to the **Coupons** page in the Developer Dashboard.
4. Select the **Coupons** tab.
5. Click the **Add Coupon** button.
6. A settings panel will open with options such as the coupon name, [lifespan](#lifespan), [discount type](#discount-type), [number of redemptions](#redemptions), [who can redeem the coupon](#who-can-redeem-the-coupon), and [where to apply it](#where-to-apply-the-coupon).
7. After configuring the coupon, scroll to the bottom of the panel and click **Create**. The new coupon will then appear in the coupons table.

note
You can also duplicate or delete existing coupons from the coupons table by clicking the **Duplicate** or **Delete** buttons next to each coupon. To edit a coupon, click the item in the table and the settings panel will open for editing.
## Customizing coupons[](#customizing-coupons "Direct link to Customizing coupons")
You can create multiple coupons with different settings to cater to various marketing strategies and customer segments. The different settings include:
### Lifespan[](#lifespan "Direct link to Lifespan")
When creating a coupon, set the period during which it will be active by selecting the start and (optionally) end dates in the **Effective date range** section.

For ongoing (forever, until deleted) coupons, leave the end date empty. You can also activate or deactivate the coupon using the toggle button.
### Discount Type[](#discount-type "Direct link to Discount Type")
You can configure the type of discount the coupon will provide. There are two main categories:
#### Renewal cycle:[](#renewal-cycle "Direct link to Renewal cycle:")
* **First payment only**: The coupon will apply only to the initial payment.
* **First payment and renewals**: The coupon will apply to the initial payment and all future renewals.
#### Value:[](#value "Direct link to Value:")
* **Percentage**: The coupon will apply a percentage discount to the payment amount.
* **Fixed amount**: The coupon will apply a fixed monetary discount to the payment amount.

### Redemptions[](#redemptions "Direct link to Redemptions")
You can limit the number of times a coupon can be redeemed. This is useful for creating exclusive offers or managing overall usage. Limits can be set per user or as a total number of redemptions across all users.
### Who Can Redeem the Coupon[](#who-can-redeem-the-coupon "Direct link to Who Can Redeem the Coupon")
You can specify which group of users is eligible to redeem the coupon. This allows you to target specific customer segments with tailored discounts.
Coupons can be applied to:
* All users: Everyone can use the coupon.
* New customers only: Only users who have never made a purchase can use the coupon.
* Previous customers only: Only users who have made a purchase in the past can use the coupon.
* Current customers only: Only users with an active subscription can use the coupon.
* Current and previous customers only: Only users who have made a purchase in the past or have an active subscription can use the coupon.
* Migrated customers only: Only users who have been migrated from another platform and have not yet made a purchase through Freemius can use the coupon.
### Where to Apply the Coupon[](#where-to-apply-the-coupon "Direct link to Where to Apply the Coupon")
You can choose to apply the coupon to specific plans/tiers and billing cycles. This allows you to create targeted discounts for particular offerings (e.g., monthly or yearly subscriptions or one-off purchases).

## Configuring Store-level Coupons[](#configuring-store-level-coupons "Direct link to Configuring Store-level Coupons")
Store-level coupons require you to select the specific products within the store where the coupon will be applicable, or you can select all products.

You can configure the same options as for product-level coupons, including lifespan, discount type, redemptions, who can redeem the coupon, and where to apply it.
note
Go to the **Store** → **Coupons** page in the Freemius Developer Dashboard to create and manage store-level coupons.
## Creating Same Coupon with Discounts for Different Plans[](#creating-same-coupon-with-discounts-for-different-plans "Direct link to Creating Same Coupon with Discounts for Different Plans")
Freemius allows you to create coupons with the same code but different settings. This is useful for running multiple promotions simultaneously or targeting different plan and pricing segments with tailored discounts.
For example, you might want to create two coupons with the same code that give:
* A 20% discount for the Professional plan.
* A 10% discount for other plans.
To do this, create two coupons with the same code but select different plans options for each under "Where to apply the coupon."

warning
This type of advanced coupon setup is only available for plans, licenses, and billing cycles. You cannot create different coupons for different types of users.
## Applying Coupons in the Checkout[](#applying-coupons-in-the-checkout "Direct link to Applying Coupons in the Checkout")
Freemius Checkout provides multiple ways to apply coupons, enabling you to offer discounts for general promotional campaigns, [Special Coupons & Discounts](https://freemius.com/help/help/documentation/marketing-automation/special-coupons-discounts/.md) as well as [Assigning Affiliate Coupons](https://freemius.com/help/help/documentation/affiliate-platform/affiliate-coupon/.md) for your affiliate program.
For additional guidance on selecting the approach that best aligns with your marketing strategy and customer engagement objectives, refer to the [coupon usage guide](https://freemius.com/help/help/documentation/checkout/features/coupons/.md).
---
# The License Renewals Mechanism
All Freemius subscriptions are automatically renewed. When a renewal payment is successfully processed, the associated license will automatically be extended, based on the billing cycle. In case there's a delay with the renewal payment processing the license will be set to `Expired` mode until the renewal is processed.
When there's an issue processing the renewal payment, an [Automated Emails Dunning Campaign](https://freemius.com/help/help/documentation/marketing-automation/dunning-failed-payments/.md) will be triggered in order to recover the failed payment.
If the subscription is canceled prior to the automatic renewal, an automated emails campaign will be triggered, before the license expiration, using the following schedule:
* 30 days before license expiration
* 7 days before license expiration
* 2 days before license expiration
* 1 day after license expiration
Each email contains a secure direct link to renew the license.
If the customer renews the license prior to the expiration date, the license will be extended for an additional 30 or 365 days (based on their selected billing period). For example, if the license was renewed 5 days prior to the expiration, and the customer has chosen an annual billing cycle, the license will expire 370 days after the renewal date (5 days + 365 days).
## Manual License Renewal[](#manual-license-renewal "Direct link to Manual License Renewal")
There's also an option to send a license renewal email manually from within the Freemius Developer dashboard. This can be done in two ways:
### Sending renewal email from Freemius[](#sending-renewal-email-from-freemius "Direct link to Sending renewal email from Freemius")
* Go to the ***Licenses*** in the Developer Dashboard.
* Change the filter from *Active* to *All*.
[](/help/videos/freemius-developer-dashboard-license-change-active-to-all.mp4)
* Search for the relevant license key (using the license key or ID).
* Scroll horizontally to the preferred license and click the option icon (3 vertical dots) to get the options dropdown.
* Click the **Resend renewal email**. The process will be initiated to send the email to the customer from Freemius.
[](/help/videos/freemius-developer-dashboard-license-send-manual-renewal.mp4)
note
We only allow sending email for licenses:
* which are about to expire in 30 days and are associated with a subscription
* for expired licenses (These licenses must not be canceled i.e. the subscription is still active)
* for expired licenses with an active subscription.
This can happen if the payment method for the subscription has failed.
### Manually share renewal link[](#manually-share-renewal-link "Direct link to Manually share renewal link")
Instead of the option above,
* Choose the **Copy renewal link** text to get the renewal URL into your clipboard.

* Paste the renewal link on your preferred communication platform, then send the license owner for renewal.
---
# Multi-Currency Pricing & Support
Freemius infrastructure fully supports multi-currency transactions. Currently, we support 3 currencies:
* `USD`: `$` U.S. Dollars (Default)
* `EUR`: `€` Euros
* `GBP`: `£` British Pounds
note
More currencies will be added based on demand. If you are processing high volumes from a specific geolocation that uses a particular currency currently not supported, please contact our [support](mailto:support@freemius.com).
## Who can benefit from multi-currency the most?[](#who-can-benefit-from-multi-currency-the-most "Direct link to Who can benefit from multi-currency the most?")
Accepting multiple currencies is particularly valuable for European sellers, who generally have a major chunk of their customers based in Europe. It may not be the best decision for developers that get their earnings paid in `USD`, due to the additional FX conversion rate costs from `EUR`/`GBP` to `USD`, however, accepting your customers’ local currency can have a positive impact on conversion rates, so the trade-off there is your decision.
## How to start accepting multiple currencies?[](#how-to-start-accepting-multiple-currencies "Direct link to How to start accepting multiple currencies?")
1. Log in to the [Developer Dashboard](https://dashboard.freemius.com).
2. Open any of your paid plans that you’d like to offer in an additional currency.
3. In the plan’s config you’ll find a new dropdown labeled *Add Currency*.
4. Click it and select the desired currency:
[](/help/videos/freemius-dashboard-plan-multi-currency.mp4)
This will replicate the `USD` prices, which then you can change as you desire.
## Multi-Currency FAQ[](#multi-currency-faq "Direct link to Multi-Currency FAQ")
### How to automatically open the checkout with a non-USD currency?[](#how-to-automatically-open-the-checkout-with-a-non-usd-currency "Direct link to How to automatically open the checkout with a non-USD currency?")
We introduced a new `currency` parameter to the [Freemius Checkout JavaScript API](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md) (the API behind our Buy Button). When set, the checkout will be opened with the selected currency.
### Can users change the currency during the checkout process?[](#can-users-change-the-currency-during-the-checkout-process "Direct link to Can users change the currency during the checkout process?")
After setting up multiple currencies in the Developer Dashboard, corresponding prices will automatically appear as a dropdown in the WP Admin Dashboard pricing:
[](/help/videos/freemius-sdk-pricing-currency-switcher.mp4)
Additionally, a dropdown overlay appears at the top right corner of the checkout page:
[](/help/videos/freemius-checkout-currency-switcher.mp4)
### Are there any additional fees/costs for selling with non-USD currencies?[](#are-there-any-additional-feescosts-for-selling-with-non-usd-currencies "Direct link to Are there any additional fees/costs for selling with non-USD currencies?")
We don’t take any additional fees for processing foreign currency transactions. On the contrary, we’ve structured the Freemius platform to minimize the cost of gateway fees by opening bank accounts in Europe, making it even cheaper than it would be for most plugin sellers in some cases. For example, the gateway fee for `EUR` transactions processed with a European credit card is now only 1.4% + €0.25. That said, if you like to get paid in `USD`, there are naturally going to be Foreign Exchange conversion costs involved since we’ll need to convert the `EUR` earnings into `USD`. We can provide those details at the time of payout, upon request, but the bottom line is that we don’t profit from foreign exchange in any way.
### Can I get my earnings in multiple currencies?[](#can-i-get-my-earnings-in-multiple-currencies "Direct link to Can I get my earnings in multiple currencies?")
Yes you can! If, for example, you are selling your product(s) both with `USD` and `EUR` you can get your `USD` earnings into one account and `EUR` earnings into another (both accounts must be owned by the same legal entity). If you are interested in that option and already processing more than $3k per month through Freemius, please contact our support and we’ll help you set it up.
### Can the currency be automatically selected based on geolocation?[](#can-the-currency-be-automatically-selected-based-on-geolocation "Direct link to Can the currency be automatically selected based on geolocation?")
By default, our checkout is set with `currency` parameter's value to `'auto'` to enable the checkout automatically choose the currency based on the geolocation of the buyer. More information on this, is available in [this guide](https://freemius.com/help/help/documentation/checkout/features/local-languages-currencies/.md#setting-the-currency).
### Is it possible to set a non-USD currency as the default for my plugin or theme?[](#is-it-possible-to-set-a-non-usd-currency-as-the-default-for-my-plugin-or-theme "Direct link to Is it possible to set a non-USD currency as the default for my plugin or theme?")
To set the default currency of the pricing page and checkout within the WP Admin dashboard, use the `'default_currency'` filter. More information on this, including an example block of code, is available in our [Freemius Checkout JavaScript API](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#default_currency) documentation.
---
# Product Retirement
End-of-life (EOL) is a natural part of a product’s lifecycle. Whatever the reason, a product should be retired gracefully and with respect to your customers. Below are some considerations and practices for retiring a product on Freemius.
## Alternatives to Retiring[](#alternatives-to-retiring "Direct link to Alternatives to Retiring")
If you don’t have the time and resources or simply don’t wish to keep/manage your product, you can consider selling it, giving your product up for adoption to another developer, or open-sourcing the assets. These options would enable continued use and value for your users and customers.
Feel free to contact and we’ll try to help you find a new home for your product.
## How to Retire a Product on Freemius?[](#how-to-retire-a-product-on-freemius "Direct link to How to Retire a Product on Freemius?")
### Stop the Sales[](#stop-the-sales "Direct link to Stop the Sales")
Obviously, you would need to stop sales and charging for the product you’re about to retire. You can move your features to a free plan or find another option as best fits your product.
### Notify Customers[](#notify-customers "Direct link to Notify Customers")
Some users, or their clients, may rely and depend on your product for their business. Make sure you provide at least 90 days for your customers to prepare for the change and find an alternative (preferably by recommending one to them).
The announcement should include even those users not eligible to receive support.
### Release a New Version of the Product[](#release-a-new-version-of-the-product "Direct link to Release a New Version of the Product")
Push an update of your product with a non-dismissible WP Admin notice about the EOL, making sure the admins of websites using your product can’t miss it, even if they miss the email announcement.
The notice should specify the scheduled retirement date, preferably with a link to a page on your website or a public Google Doc that covers all of the details about the product retirement. Commit to improving the page as you get more questions during the grace period to address concerns.
### Continued Service During the Announcement Period[](#continued-service-during-the-announcement-period "Direct link to Continued Service During the Announcement Period")
**Please note:** in the case that you have paying customers, you are still under obligation to provide services (support, critical updates, or others based on your Terms/SLA) until the official EOL date.
You will likely encounter additional support requests and questions during this period.
### Remove Feature Blocking[](#remove-feature-blocking "Direct link to Remove Feature Blocking")
1. Mark the plans as non-blocking:

2. Contact support to flag existing licenses as non-blocking (in case they are blocking).
### Make Sure Users Can Access and Export Their Data[](#make-sure-users-can-access-and-export-their-data "Direct link to Make Sure Users Can Access and Export Their Data")
In case your product is storing any user/customer data, make sure to provide them with an option to retrieve and save their data.
### Cancel All Active Subscriptions[](#cancel-all-active-subscriptions "Direct link to Cancel All Active Subscriptions")
Of course, it wouldn’t be fair to keep charging for an EOL product with no updates and support. Cancel the subscriptions and keep the existing licenses active.
### Review Your Terms and the EULA[](#review-your-terms-and-the-eula "Direct link to Review Your Terms and the EULA")
To avoid any legal liability, ensure that all cases, offers, and promises are covered in your product retirement plan and transition.
### Don’t Delete the Product[](#dont-delete-the-product "Direct link to Don’t Delete the Product")
Avoid deleting the product on Freemius for at least 12 months after the retirement date. It’s hard to predict all of the potential edge cases, so you never know when you’ll need to have access to the product’s data.
## FAQs[](#faqs "Direct link to FAQs")
### Do I need to refund customers?[](#do-i-need-to-refund-customers "Direct link to Do I need to refund customers?")
Not necessarily, except for those that are in the refund period, as provisioned by the EULA. However, as a courtesy, it is recommended to refund those that purchased longer-term licenses in the period of up to 30 days before the announcement to retire the product.
### Do I need to offer support forever for lifetime customers?[](#do-i-need-to-offer-support-forever-for-lifetime-customers "Direct link to Do I need to offer support forever for lifetime customers?")
No. This is covered by the EULA.
---
# Refund Payment
This document outlines the various refund options and procedures available to software makers using Freemius.
We empower software makers to issue refunds for product purchases and subscriptions, offering the following options:
* Full refunds
* Partial refunds
* [VAT refunds](#how-to-refund-vat)
* [US sales tax refunds](#how-to-refund-us-sales-tax)
Additionally, full refunds can be processed, excluding fees, helping makers avoid losses on refunded payments.
## How to process a refund[](#how-to-process-a-refund "Direct link to How to process a refund")
1. As a software maker, log in to the Developer Dashboard.
2. Choose the relevant product.
3. Navigate to the ***Payments*** section.
4. Locate the relevant payment and click the **Refund** button under the `Action` column.

5. Select the preferred refund option. This could be partial or full.

6. You can optionally modify the behavior of the license post-processing the refund.

7. Proceed with making a refund note.

note
All team members, except the product owner, **must** provide a reason for the refund. This note adds context to the refund, ensuring relevant team members are informed. However, this step is optional for the product owner.
8. Next, click the “Yes - Refund & Cancel” button to initiate the refund.
## Tax Refunds[](#tax-refunds "Direct link to Tax Refunds")
Our system also enables makers to provide VAT and sales tax refunds while retaining the rest of the payment. I.e, a maker can make a refund on only the tax and keep the rest of the payment.
note
The tax refund option ONLY appears for payments with tax.
note
Upon tax refunds of subscription payments, the subscription will be automatically updated to exempt VAT/Sales tax for future renewals.
### How to refund VAT[](#how-to-refund-vat "Direct link to How to refund VAT")
This pertains to instances where customers did not provide their VAT number in the designated tax field during checkout.
1. Start with steps 1-4 in [how to process a refund](#how-to-process-a-refund) above.
2. Select the **Tax refund** option.
3. Insert the business’ valid VAT number in the appropriate field.

4. Click the **“Yes - Refund”** button to initiate the refund.
note
Our system will process a full refund of the original payment and create a new payment, excluding VAT charges. The new reverse charge invoice can be sent to the customer for accounting purposes.
### How to refund US Sales Tax[](#how-to-refund-us-sales-tax "Direct link to How to refund US Sales Tax")
Currently, our checkout does not support submitting an exemption certification number for tax-exempt entities. To receive an exemption, the purchase must first be made with tax applied, after which the product’s team can issue a refund via the Developer Dashboard.
1. Start with steps 1-4 in [how to process a refund](#how-to-process-a-refund) above.
2. Select the **"Tax refund"** option.
3. Insert the tax exemption certificate number.

4. Read and acknowledge the certificate disclaimer.
5. Click **“Yes - Refund”** to initiate the refund process.
## Refund Processing Timeline[](#refund-processing-timeline "Direct link to Refund Processing Timeline")
* PayPal refunds are processed immediately.
* A credit card refund may take 5-10 business days to reflect in the buyer’s account.
## Difference in Amount of Partial Refunds[](#difference-in-amount-of-partial-refunds "Direct link to Difference in Amount of Partial Refunds")
For **full refunds**, the buyer is reimbursed the exact amount paid and in the same currency, regardless of any subsequent currency fluctuations.
**Partial refunds** are handled differently. When a buyer makes a purchase using a credit card or a PayPal account that involves currency conversion, exchange rate fluctuations may affect the refunded amount.
For example, if a German buyer with a settlement currency of EUR purchases a $20 product at a conversion rate of 0.9 EUR per 1 USD, the amount charged in EUR is 18 EUR. Later, if a partial refund of $10 is issued when the conversion rate has shifted to 0.85 EUR per 1 USD, the refund will amount to 8.5 EUR. This results in a discrepancy of 0.5 EUR due to the change in the exchange rate.
## General Refund Policies[](#general-refund-policies "Direct link to General Refund Policies")
* Once a partial refund is processed, there’s no way to process another one for the same payment.
* PayPal payments can be refunded for up to 180 days.
* Credit card payments can be refunded for up to 365 days.
---
# How to Set up a Refund Policy for Your Product
Upon creating a new product on Freemius, please choose one of the 4 available refund policies that suits your product and customers best from:
* [No Refunds Policy](#no-refunds-policy)
* [Strict - "Money Back Guarantee"](#strict---money-back-guarantee)
* [Moderate - "Satisfaction Guarantee"](#moderate---satisfaction-guarantee)
* [Flexible - "Double Guarantee"](#flexible---double-guarantee)
tip
A clear refund policy can help build trust with your customers and reduce purchase anxiety.
## Configure the Refund Policy[](#configure-the-refund-policy "Direct link to Configure the Refund Policy")
To set up the refund policy for your product, follow these steps:
1. Go to your product in the [Freemius Dashboard](https://dashboard.freemius.com/).
2. Click on the **Plans** Page.
3. Then select the **Refund Policy** tab.
4. Set a **Refund Time Period**.
5. Choose one of the available **Refund Policy** option in the dropdown field.
6. Click away to autosave the changes.

## Consumptive Usage[](#consumptive-usage "Direct link to Consumptive Usage")
If your product includes **one-off or consumptive usage**, such as AI content generation, video or image processing, data exports, API calls, or any service where value is delivered instantly and cannot be undone, we strongly recommend enabling the following option in your refund policy settings:

note
This option is currently available for SaaS products only. If you have a different product type that includes consumptive usage, please contact our support team for assistance.
When this option is enabled:
* Any instance of the text *“full refund of the Purchase price”* will be adjusted to *“refund of the Purchase price only for any unused credits”* in the buyer-facing terms.
* An additional clarification will be automatically added to the purchase terms, making the refund boundaries explicit for buyers.
The following disclaimer will be added to the refund policy text shown to buyers:
> For clarity, once credits are consumed, they are non-refundable, as the underlying service has already been delivered. This includes, for example, executing an AI-powered action, generating content, processing data, or any other one-time or consumptive operation that uses credits.
This setting helps protect your business from abuse while keeping the refund policy fair and transparent for legitimate customers.
## Our Recommendation[](#our-recommendation "Direct link to Our Recommendation")
Here are our recommendations depending on the type of product you offer:
* [SaaS and Apps](https://freemius.com/help/help/documentation/saas/setup-refund-policy/.md).
* [WordPress Plugins and Themes](https://freemius.com/help/help/documentation/wordpress/setup-refund-policy/.md).
## Available Refund Policies[](#available-refund-policies "Direct link to Available Refund Policies")
Freemius supports several refund policy types, designed to fit different product models and risk profiles. Each policy determines what buyers see at checkout, how refund requests are handled, and how much flexibility you retain as a seller.
### No Refunds Policy[](#no-refunds-policy "Direct link to No Refunds Policy")
A conservative refund policy that limits refunds to very specific cases and leaves most refund decisions at your discretion.
#### What it means for you (the seller)?[](#what-it-means-for-you-the-seller "Direct link to What it means for you (the seller)?")
##### SaaS[](#saas "Direct link to SaaS")
Refunds are only expected if the buyer requests them within 14 days before accessing or using the service. Once the service has been accessed, any refund request becomes discretionary. You can review the case, check usage, and decide whether to approve or decline the refund. This policy works best for SaaS products where onboarding is immediate and value is delivered quickly.
##### Consumable Credits[](#consumable-credits "Direct link to Consumable Credits")
This policy is well suited for credit-based or usage-based products. Once credits are used, you are under no obligation to refund. Requests made before any usage can be approved, but anything after consumption is fully at your discretion.
##### Downloadable Software (e.g. plugins)[](#downloadable-software-eg-plugins "Direct link to Downloadable Software (e.g. plugins)")
Refunds are only expected if the buyer has not downloaded or accessed the software. Once downloaded, refund requests can be declined unless you choose otherwise. This helps reduce abuse for easily copyable or one-time-use software.
tip
If you're using our [WordPress solution](https://freemius.com/help/help/documentation/wordpress/.md), you can verify whether a plugin or theme was downloaded by checking the customer's events and looking for the `plugin.premium.downloaded` event.
#### Fine Print[](#fine-print "Direct link to Fine Print")
```
If You change Your mind about your Purchase and have not yet downloaded, used or otherwise accessed the paid Software, then, upon Your request within 14 days from the Purchase date, we will issue a full refund of the Purchase price.
Refund requests made after downloading, using or otherwise accessing the service (but before expiry of 14 days from the Purchase date) are handled on a case by case basis and are issued at our sole discretion.
```
### Strict - "Money Back Guarantee"[](#strict---money-back-guarantee "Direct link to Strict - \"Money Back Guarantee\"")
A defect-based refund policy focused on product stability, not subjective satisfaction.
#### TLDR[](#tldr "Direct link to TLDR")
Refund when the product had a bug/problem you couldn't resolve.
#### What will the buyer see?[](#what-will-the-buyer-see "Direct link to What will the buyer see?")
> If during the next `{{ period }}` you experience an issue that makes the product unusable and we are unable to resolve it, we'll happily consider offering a full refund of your money.
#### What it means for you (the seller)?[](#what-it-means-for-you-the-seller-1 "Direct link to What it means for you (the seller)?")
##### SaaS[](#saas-1 "Direct link to SaaS")
Refunds are only required when a verified defect makes the service’s core functionality unusable and your support team cannot resolve it within the refund period. Feature gaps, unmet expectations, or third-party conflicts are not grounds for refunds. This policy works well for mature SaaS products with stable infrastructure and clear documentation.
##### Consumable Credits[](#consumable-credits-1 "Direct link to Consumable Credits")
Refunds should generally apply only if a defect prevents the credits from being used at all. Once credits are successfully consumed, refunds are typically not applicable, even if the buyer is unhappy with the outcome.
##### Downloadable Software (e.g. plugins)[](#downloadable-software-eg-plugins-1 "Direct link to Downloadable Software (e.g. plugins)")
Refunds apply only when a defect renders the software unusable and cannot be fixed with reasonable support. Incompatibilities with third-party themes, plugins, or environments are excluded.
#### Fine print[](#fine-print-1 "Direct link to Fine print")
```
If, during the first `{{ refundPeriod }}` days following the purchase date, You experience a defect that makes the Software's material functions unusable, and following Your cooperation with our support team we are unable to resolve it, then following Your request we will issue a full refund of the Purchase price or provide you with replacement, non-defective product.
For clarity, any defect or lack of use arising from a conflict or incompatibility with a third party product, software or services, is not covered by this policy, and nor are missing Software features.
```
### Moderate - "Satisfaction Guarantee"[](#moderate---satisfaction-guarantee "Direct link to Moderate - \"Satisfaction Guarantee\"")
A balanced refund policy that covers both defects and reasonable dissatisfaction, while still requiring an attempt to resolve issues.
#### TLDR[](#tldr-1 "Direct link to TLDR")
Refund when missing a feature, didn't work as expected, or had a problem you couldn't resolve (including 3rd party issues).
#### What will the buyer see?[](#what-will-the-buyer-see-1 "Direct link to What will the buyer see?")
> If over the next `{{ refundPeriod }}` days you are unhappy with our product or have an issue that we are unable to resolve, we'll happily consider offering a 100% refund of your money.
#### What it means for you (the seller)?[](#what-it-means-for-you-the-seller-2 "Direct link to What it means for you (the seller)?")
##### SaaS[](#saas-2 "Direct link to SaaS")
Refunds may be requested for missing features, usability issues, or defects, as long as the buyer has worked with your support team to try to resolve them. This policy gives you a chance to save the customer before a refund is issued. It is a good fit for growing SaaS products where onboarding and expectations still vary.
##### Consumable Credits[](#consumable-credits-2 "Direct link to Consumable Credits")
Refunds may apply only if credits could not reasonably be used due to product issues. Once credits are consumed successfully, refunds are generally not expected, even if the buyer is dissatisfied with the results.
##### Downloadable Software (e.g. plugins)[](#downloadable-software-eg-plugins-2 "Direct link to Downloadable Software (e.g. plugins)")
Refunds may be granted for defects or incompatibilities, including third-party conflicts, provided the issue cannot be resolved with support. This is more generous than the Strict policy and may slightly increase refund volume.
#### Fine print[](#fine-print-2 "Direct link to Fine print")
```
If, during the first `{{ refundPeriod }}` days following the Purchase date, You are not satisfied with the Software due to missing features or due to a defect that makes the Software’s functions unusable (including defects arising from a conflict or incompatibility with a third party product, software or services), and following Your cooperation with our support team we are unable to resolve the issue, then following Your request we will issue a full refund of the Purchase price.
```
### Flexible - "Double Guarantee"[](#flexible---double-guarantee "Direct link to Flexible - \"Double Guarantee\"")
The most buyer-friendly refund policy, designed to maximize conversions and reduce purchase anxiety.
#### One liner[](#one-liner "Direct link to One liner")
Risk free, no questions asked refund.
#### What will the buyer see?[](#what-will-the-buyer-see-2 "Direct link to What will the buyer see?")
> If you don't like our product over the next `{{ refundPeriod }}` days, we'll happily refund 100% of your money. **No questions asked.**
#### What it means for you (the seller)?[](#what-it-means-for-you-the-seller-3 "Direct link to What it means for you (the seller)?")
Any refund request made within the refund period should be honored, regardless of usage or reason.
##### SaaS[](#saas-3 "Direct link to SaaS")
This policy is best suited for SaaS products with low marginal costs and strong onboarding, where refunds are rare in practice.
##### Consumable Credits[](#consumable-credits-3 "Direct link to Consumable Credits")
This policy should be used with caution for credit-based products. Many sellers pair it with limitations such as refunds for unused credits only, or disable it entirely for consumptive use cases to prevent abuse.
##### Downloadable Software (e.g. plugins)[](#downloadable-software-eg-plugins-3 "Direct link to Downloadable Software (e.g. plugins)")
Refunds are expected on request within the refund period, even after download. This policy can significantly improve conversion rates, but may also increase refund volume, especially for one-time-use plugins.
#### Fine print[](#fine-print-3 "Direct link to Fine print")
```
If, during the first `{{ refundPeriod }}` days following the Purchase date, You are not satisfied with the Software for any reason whatsoever (no questions asked) and notify us that You would like to cancel Your Purchase, we will issue a full refund of the Purchase price.
```
## Refunds don't have to end the relationship[](#refunds-dont-have-to-end-the-relationship "Direct link to Refunds don't have to end the relationship")
After processing a refund, consider reaching out and asking for feedback. While refunds are rarely anyone's favorite outcome, handling them quickly and respectfully can leave a positive impression and even build long-term loyalty.
For more ideas on turning refunds into learning opportunities, see [How to Win Customer Loyalty with Your Refund Process](https://returngo.ai/improve-customer-loyalty-with-returns/#:~:text=Process%20Returns%20Quickly\&text=If%20the%20returns%20process%20is,returns%20will%20improve%20customer%20loyalty).
## Disputes & Chargebacks[](#disputes--chargebacks "Direct link to Disputes & Chargebacks")
A clear refund policy helps reduce disputes, but it cannot fully eliminate them.
Setting the right refund policy is an important step in protecting your business. However, some customers may still choose to dispute a payment through PayPal, their bank, or their credit card provider, regardless of the refund policy you've defined.
In those cases, [dispute or chargeback fees](https://freemius.com/help/help/documentation/getting-started/our-pricing/.md#dispute-and-chargeback-fees) may apply, and Freemius may need to step in to review and handle the case. Each dispute is evaluated individually, based on the circumstances, the selected refund policy, and the evidence available.
---
# How to Offer Trials With or Without Requiring a Payment Method
The Freemius Checkout allows you to set up trial periods for your products, enabling potential customers to experience your product before committing to a purchase.
Selling WordPress Plugins and Themes?
Offering a free trial can help increase your product’s distribution and sales. Check out [The Ultimate Guide To Free Trials For Premium WordPress Plugins And Themes](https://freemius.com/blog/trials-premium-wordpress-plugins-themes/).
## How to Activate and Use Trials[](#how-to-activate-and-use-trials "Direct link to How to Activate and Use Trials")
Trials must be set up in each plan's configuration settings. To configure a trial period for your product, follow these steps:
1. Log in to your [Freemius Dashboard](https://dashboard.freemius.com/).
2. Navigate to the **Products** section and select the product you want to configure.
3. Go to the **Plans** page.
4. Select the desired plan and scroll to the **Trial** section.
5. Specify the duration of the trial period (e.g., 7 days, 14 days). This setting is saved automatically when you select the duration.
6. Choose whether to require a payment method.

note
Trials are available for all product types using the Freemius Checkout.
## Supported Trial Types[](#supported-trial-types "Direct link to Supported Trial Types")
Freemius supports two types of trials based on whether a payment method is required:
### Free Trial — Without a Payment Method[](#free-trial--without-a-payment-method "Direct link to Free Trial — Without a Payment Method")
When customers sign up for this option, they are not asked for a payment method.
To set up this trial option in your plan settings, toggle **OFF** the “Require Credit Card or PayPal” option.
### Paid Trial — Requiring a Payment Method[](#paid-trial--requiring-a-payment-method "Direct link to Paid Trial — Requiring a Payment Method")
When customers sign up for this option, a payment method is required.
To set up this option in your plan settings, toggle **ON** the “Require Credit Card or PayPal” option.

note
Requiring payment details means that the user will be charged automatically when the trial period ends unless they cancel beforehand. Customers will be notified via email before the trial ends to remind them of the upcoming charge.
## Share Trial Links With Customers[](#share-trial-links-with-customers "Direct link to Share Trial Links With Customers")
After activating trials in your Freemius Dashboard, you can make trial option links available to your customers. These links can direct users to start a trial for your product. For example:
### Simple HTML Buy Buttons[](#simple-html-buy-buttons "Direct link to Simple HTML Buy Buttons")
You can create simple HTML buy buttons that include the trial option. You can obtain these from the Freemius Dashboard as explained in [Generating Freemius Checkout Buy Buttons](https://freemius.com/help/help/documentation/getting-started/making-your-first-sale/.md#get-the-buy-button-checkout-code).

### Checkout Links With the Trial[](#checkout-links-with-the-trial "Direct link to Checkout Links With the Trial")
You can also create [direct checkout links](https://freemius.com/help/help/documentation/checkout/integration/hosted-checkout/.md#generating-checkout-links) that lead users to start a trial for your product. Do this by adding the [trial parameter](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#trial) to your pricing page URL.
```
https://checkout.freemius.com/product/{your-product-id}/?trial={true|false|free|paid}
```
### Integration With the Freemius WordPress SDK[](#integration-with-the-freemius-wordpress-sdk "Direct link to Integration With the Freemius WordPress SDK")
If you prefer customers to purchase from the WordPress dashboard, integrate the Freemius WordPress SDK into your WordPress products. See how to [leverage trials in your free product version with the Freemius WordPress SDK](https://freemius.com/help/help/documentation/wordpress/free-trials/.md).
---
# So, what exactly does it do?
Freemius is a merchant of record (MoR) specializing in selling software products. The Freemius platform offers in-depth analytics and marketing automation capabilities designed to maximize Average Sale Price (ASP) and conversion rates while minimizing churn.
With Freemius, makers can sell SaaS (Software as a Service) and downloadable software, including WordPress plugins and themes, as well as browser and desktop applications.
Freemius empowers software makers to start selling online within minutes by offering several flexible options:
1. [**Freemius Checkout / Buy Button**](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md): Sell from any webpage by embedding a simple JavaScript code snippet.
2. [**No-code Checkout Links**](https://freemius.com/help/help/documentation/getting-started/making-your-first-sale/.md#access-checkout-links): Sell from virtually anywhere, including social networks (Facebook, X/Twitter), emails, and more.
3. [**In-Dashboard Checkout**](https://freemius.com/help/help/documentation/getting-started/making-your-first-sale/.md#in-dashboard-upgrading-in-wp-admin): Sell freemium products directly within the WP Admin dashboard.
These methods can be used individually or in combination, with no additional setup required on your part.
These are the different services offered by Freemius:
## Global Sales Tax Compliance[](#global-sales-tax-compliance "Direct link to Global Sales Tax Compliance")
Freemius empowers product owners to focus on enhancing their products by taking care of global sales tax management, including:
* [US Sales Tax](https://freemius.com/us-sales-tax-and-economic-nexus/)
* [UK & EU VAT](https://freemius.com/eu-vat-uk-vat-europe/)
Our robust checkout system captures tax details and processes payments to facilitate deductions and exemptions for your customers while ensuring taxes are remitted to the appropriate authorities.
Find more information about:
* [Trial Activations](https://freemius.com/help/help/documentation/selling-with-freemius/set-up-trials/.md),
* [Coupon Discounts](https://freemius.com/help/help/documentation/selling-with-freemius/coupon-discount/.md),
* [Refund Policy](https://freemius.com/help/help/documentation/selling-with-freemius/refund-policy/.md) and [Refund Payments](https://freemius.com/help/help/documentation/selling-with-freemius/refund-payment/.md),
* [Multi-currency Support](https://freemius.com/help/help/documentation/selling-with-freemius/multi-currency/.md),
* [License Renewal](https://freemius.com/help/help/documentation/selling-with-freemius/license-renewals-mechanism/.md),
* [Supported Countries](https://freemius.com/help/help/documentation/selling-with-freemius/supported-countries/.md),
* [Supported Product Types](https://freemius.com/help/help/documentation/selling-with-freemius/allowed-prohibited-products/.md),
* [Getting Paid](https://freemius.com/help/help/documentation/selling-with-freemius/your-earnings/.md),
* [Product Retirement](https://freemius.com/help/help/documentation/selling-with-freemius/product-retirement/.md).
These features and more are designed to help you effectively build your software business with ease and confidence using Freemius.
---
# Supported Countries
At Freemius, we power software sales around the globe. Whether you're a maker building a software business or a buyer looking to make a purchase, here's how we support you across countries.
## Where Freemius Checkout Works (Payment Processing)[](#where-freemius-checkout-works-payment-processing "Direct link to Where Freemius Checkout Works (Payment Processing)")
Freemius Checkout supports payments from nearly every country worldwide, as long as the region is not under U.S. sanctions.
Freemius supports payment processing globally, except for countries with US sanctions, which are listed as [unsupported countries](#unsupported-countries).
## Where We Can Pay You (Payouts for Makers)[](#where-we-can-pay-you-payouts-for-makers "Direct link to Where We Can Pay You (Payouts for Makers)")
We work with software makers in 190+ countries and territories, who can receive payouts via:
* PayPal (default payment method, using PayPal MassPay)
* Payoneer
* Wire transfer (international and domestic / IBAN / SWIFT)
* Wise (formerly TransferWise)
Check out the guide on [how Freemius handles your earnings and payouts](https://freemius.com/help/help/documentation/selling-with-freemius/your-earnings/.md).
### Supported Countries for Payouts[](#supported-countries-for-payouts "Direct link to Supported Countries for Payouts")
This means you can sell with Freemius if you live or operate your business from any of these supported countries:
note
This list includes all countries where at least one payout method is available.
* Aland Islands
* Albania
* Algeria
* American Samoa
* Andorra
* Angola
* Anguilla
* Antarctica
* Antigua & Barbuda
* Argentina
* Armenia
* Aruba
* Australia
* Austria
* Azerbaijan
* Bahamas
* Bahrain
* Bangladesh
* Barbados
* Belgium
* Belize
* Benin
* Bermuda
* Bhutan
* Bolivia
* Bosnia & Herzegovina
* Botswana
* Bouvet Island
* Brazil
* British Indian Ocean Territory
* British Virgin Islands
* Brunei
* Brunei Darussalam
* Bulgaria
* Burkina Faso
* Burundi
* Cambodia
* Cameroon
* Canada
* Cape Verde
* Caribbean Netherlands
* Cayman Islands
* Chad
* Channel Islands
* Chile
* China
* Christmas Island
* Cocos Islands
* Colombia
* Comoros
* Cook Islands
* Costa Rica
* Crimea
* Croatia
* Côte d'Ivoire
* Curacao
* Cyprus
* Czech Republic
* Côte d'Ivoire
* Democratic Republic of the Congo
* Denmark
* Djibouti
* Dominica
* Dominican Republic
* Ecuador
* Egypt
* El Salvador
* Equatorial Guinea
* Estonia
* Ethiopia
* Falkland Islands
* Faroe Islands
* Fiji
* Finland
* France
* French Guiana
* French Polynesia
* French Southern Territories
* Gabon
* Gambia
* Georgia
* Germany
* Ghana
* Gibraltar
* Greece
* Greenland
* Grenada
* Guadeloupe
* Guatemala
* Guernsey
* Guinea
* Guyana
* Honduras
* Hong Kong
* Hungary
* Iceland
* India
* Indonesia
* Ireland
* Isle of Man
* Israel
* Italy
* Jamaica
* Japan
* Jersey
* Jordan
* Kazakhstan
* Kenya
* Kiribati
* Kosovo
* Kuwait
* Kyrgyz Republic
* Kyrgyzstan
* Laos
* Latvia
- Lesotho
- Liechtenstein
- Lithuania
- Luxembourg
- Macao
- Macedonia
- Madagascar
- Malawi
- Malaysia
- Maldives
- Malta
- Marshall Islands
- Martinique
- Mauritania
- Mauritius
- Mayotte
- Mexico
- Micronesia
- Moldova
- Monaco
- Mongolia
- Montenegro
- Montserrat
- Morocco
- Namibia
- Nauru
- Nepal
- Netherlands
- Netherlands Antilles
- New Caledonia
- New Zealand
- Niger
- Nigeria
- Niue
- Norfolk Island
- Northern Mariana Islands
- Norway
- Oman
- Pacific Islands
- Pakistan
- Palau
- Panama
- Papua New Guinea
- Paraguay
- Peru
- Philippines
- Pitcairn Islands
- Poland
- Portugal
- Qatar
- Reunion
- Romania
- Rwanda
- Saint Barthelemy
- Saint Helena
- Saint Kitts and Nevis
- Saint Lucia
- Saint Pierre & Miquelon
- Saint Pierre and Miquelon
- Saint Vincent's & Grenadines
- Saint Vincents & Grenadines
- Samoa
- San Marino
- Sao Tome and Principe
- Saudi Arabia
- Senegal
- Serbia
- Sevastapol
- Seychelles
- Sierra Leone
- Singapore
- Sint Maarten
- Slovakia
- Slovenia
- Solomon Islands
- South Georgia and the South Sandwich Islands
- South Korea
- Spain
- Sri Lanka
- Suriname
- Svalbard and Jan Mayen
- Swaziland
- Sweden
- Switzerland
- Syria
- Taiwan
- Tajikistan
- Tanzania
- Thailand
- Timor Leste
- Togo
- Tokelau
- Tonga
- Trinidad & Tobago
- Tunisia
- Turkey
- Turkmenistan
- Turks and Caicos
- Tuvalu
- Uganda
- Ukraine (non-sanctioned regions)
- United Arab Emirates
- United Kingdom
- United States
- Uruguay
- Uzbekistan
- Vanuatu
- Vatican
- Vietnam
- Wallis & Futuna
- Western Sahara
- Zambia
- Zimbabwe
Since availability may change, here are the official links to check the most current list of supported countries per payout method:
* [PayPal-supported countries](https://www.paypal.com/uk/webapps/mpp/country-worldwide)
* [Wise supported countries](https://wise.com/help/articles/2571942/what-countriesregions-can-i-send-to)
* [Bank wires/SWIFT/IBAN supported countries](https://support.mercury.com/hc/en-us/articles/28773219548180-Sending-international-payments)
* Payoneer does not publish a public list of supported countries. You’ll be able to see if your country is supported during the sign-up process at [payoneer.com](https://www.payoneer.com).
## Unsupported Countries[](#unsupported-countries "Direct link to Unsupported Countries")
Due to international regulations and compliance with OFAC (Office of Foreign Assets Control), we **do *not*** support makers from, or process payments from customers located in, the following countries and regions:
* Afghanistan
* Belarus
* Central African Republic
* Congo
* Cuba
* Eritrea
* Guinea-Bissau
* Haiti
* Iran
* Iraq
* Lebanon
* Liberia
* Libya
- Mali
- Mozambique
- Myanmar (Burma)
- Nicaragua
- North Korea
- Russia
- Somalia
- South Sudan
- Sudan
- Ukraine (sanctioned regions)
- Venezuela
- Yemen
note
This list may change as global sanctions are updated: [Official OFAC list](https://ofac.treasury.gov/sanctions-programs-and-country-information)
---
# Check Earnings, Setup Payout Methods and Get Paid with Freemius
Once you cross the minimum earnings threshold, Freemius automatically makes payments to you for your hard work. All you need to do is add your [payout method](#set-up-payout-methods) on the "My Profile" page.

note
If you're looking for what countries you can sell from, please take a look at our [Supported Countries](https://freemius.com/help/help/documentation/selling-with-freemius/supported-countries/.md#supported-countries-for-payouts) guide.
## How to Check Your Earnings[](#how-to-check-your-earnings "Direct link to How to Check Your Earnings")
To see your current ***earnings*** status:
1. Log in to your Freemius account via the [Developer Dashboard](https://dashboard.freemius.com).
2. Click to toggle the menu at the top-right corner of the screen.
3. Navigate to the **My Earnings** screen.
[](/help/videos/freemius-developer-dashboard-my-earnings.mp4)
On the **My Earnings** screen, you can see your payable earnings amount based on your accumulated monthly sales.

## Set Up Payout Methods[](#set-up-payout-methods "Direct link to Set Up Payout Methods")
In order to get paid, you need to set up your payout methods and payout conversion currencies (if applicable). Here's how to do that:
1. Click the **My Profile** menu item in the dropdown at the top-right corner of the screen.
2. Scroll to the **Payout Method(s)** section.
3. Configure your payout method for each currency in which you sell.

The following payout methods are supported:
1. PayPal (default payment method, using PayPal MassPay)
2. Payoneer
3. Wire transfer (international and domestic / IBAN / SWIFT)
4. Wise (formerly TransferWise)
You can also set a different payout conversion currency for each currency in which you sell. International wires can be converted to your local currency. Due to our high volumes, we have negotiated favorable terms with our bank for currency conversion rates and a complete fee waiver on foreign-currency international wires.
## Set Up Billing Information to Receive Invoices[](#set-up-billing-information-to-receive-invoices "Direct link to Set Up Billing Information to Receive Invoices")
In order to get the [pro-forma](#payout-schedule-email) and [reverse invoices](#payout-email), you need to set up your billing information.
1. Click to toggle the menu at the top-right corner of the screen.
2. Navigate to the "My Profile" page.
3. Scroll to the **Billing Information** section and fill in the relevant fields.

## How and When Freemius Transfers Your Earnings[](#how-and-when-freemius-transfers-your-earnings "Direct link to How and When Freemius Transfers Your Earnings")
* Your earnings are automatically calculated on the 1st of each month.
* Payout is made on the 10th of each month, with a $100 minimum threshold. If your monthly earnings are below the minimum threshold, those earnings are automatically added to your balance for the next month.
* To account for the 30-day refund period (that is, refunds to clients who purchase your plugin/theme), earnings for month `X` are calculated on the 1st of month `X + 2`. For example, if your gross income between 01/01 - 01/31 was $1,000 and, at the same time, you had to process $500 of gross refunds between 01/01 - 29/02 (for payments during January), your earnings on March 1st will be $1,000 - $500 = $500 (minus commission). If that number surpasses the minimum threshold ($100), which in this case it does, the payout will be processed on the 10th of March. This system allows us to avoid connecting directly to your bank, your PayPal account, or your credit card while still ensuring that you can cover refunds if your clients request them.
note
For any clarifications about your earnings, please feel free to [contact us](mailto:support@freemius.com).
## How Freemius Notifies You About Payouts[](#how-freemius-notifies-you-about-payouts "Direct link to How Freemius Notifies You About Payouts")
As part of the payout process, Freemius sends two emails:
### Payout Schedule Email[](#payout-schedule-email "Direct link to Payout Schedule Email")
On the 1st of every month, Freemius sends you an email about an upcoming payout. This email has the expected payout details and a link to the pro-forma invoice. You can use the invoice to notify your bank or other system about the upcoming payment.

### Payout Email[](#payout-email "Direct link to Payout Email")
Once the payout has been completed, Freemius sends you a confirmation email. It includes a link to a reverse invoice, which you can use for accounting and other legal purposes.
In practice, this is the invoice you are expected to send to Freemius for the payout; however, we generate it on your behalf. It also displays [any volume discount](https://freemius.com/help/help/documentation/getting-started/our-pricing/.md#high-volume-rates), if applicable.

## FAQ[](#faq "Direct link to FAQ")
### Is it possible to expedite this month’s payout?[](#is-it-possible-to-expedite-this-months-payout "Direct link to Is it possible to expedite this month’s payout?")
We understand that timely payouts are important, and we strive to process them as quickly as possible. To provide some transparency, here’s an overview of how our payout process works.
On the 1st of each month, we run a script to calculate the exact payout amounts and how they should be distributed. To optimize for the best foreign exchange (FX) conversion rates, we use multiple payout channels. This FX optimization can save you as much as 4% on conversions, which is a significant amount. Additionally, we work to minimize payout transfer costs by using the most efficient payment methods, such as ACH for USD payouts within the US and a UK bank for GBP payouts.
Once the calculations are complete, we initiate transfers to fund the necessary accounts. Payouts are then processed as soon as the funds arrive. Depending on the payout channel and source of funding, this process can take 3-6 business days.
Please note that weekends and holidays may occasionally cause delays, as banks do not process transfers during these times. Our team also takes a well-deserved break on weekends, which can impact the processing speed.
While we currently cannot expedite the payout process, we have designed it to be as efficient as possible while ensuring you benefit from the best FX conversion rates and minimized transfer costs.
### Why is the refund amount in the *Dashboard* different from the refund amount shown in the *My Earnings* section?[](#why-is-the-refund-amount-in-the-dashboard-different-from-the-refund-amount-shown-in-the-my-earnings-section "Direct link to why-is-the-refund-amount-in-the-dashboard-different-from-the-refund-amount-shown-in-the-my-earnings-section")
The refund amount shown in the ***Dashboard*** represents the $-value of refunds processed during the selected date range. On the other hand, the refund amount shown in earnings for `month(X)` is the $-value of:
* Refunds processed during `month(X)` and `month(X+1)` for payments received during `month(X)`; plus
* *Late refunds* processed during `month(X)` of payments that were received before `month(X-2)`
This process protects against *refunds abuse* where an attacker can purchase many licenses (e.g., $100,000 worth of licenses) on `month(X)` and then refund all of the payments on `month(X+1)`. If we ignore the refunds of `month(X+1)` when calculating the earnings for `month(X)`, the attacker can steal money that way.
---
# Enable your customers to manage their account and find support
Freemius offers two ways for users and customers to manage their account:
1. [WP Admin Product Account page](https://freemius.com/help/help/documentation/wordpress-sdk/wp-admin-account/.md) (*for WordPress products only*).
2. External **Customer Portal** (previously know as the "User Dashboard").
The external Customer Portal is a web-based interface that allows your customers to manage their account details, view their purchases, download products, manage subscriptions, and access support resources for your products and stores.

## Usage of the Customer Portal[](#usage-of-the-customer-portal "Direct link to Usage of the Customer Portal")
This tool is specifically useful for:
### WordPress Products or Stores[](#wordpress-products-or-stores "Direct link to WordPress Products or Stores")
* To provide their customers with a dedicated space to manage their account outside the WordPress admin area, including account details modification, purchase viewing, product downloads, and subscription management.
* To provide support via a ticketing system, with Docs and FAQ integration directly in the portal.
### Active Affiliate Program[](#active-affiliate-program "Direct link to Active Affiliate Program")
* To display the product affiliate program to customers and position them as potential product ambassadors while [earning a commission](https://freemius.com/help/help/documentation/users-account-management/earn-becoming-an-affiliate/.md).
* To provide their affiliates with a dedicated space to view their stats and earnings.
### SaaS and Apps[](#saas-and-apps "Direct link to SaaS and Apps")
* To display customers' purchase history, including details of each transaction.
* To enable customers to view and manage their subscriptions, including the ability to cancel or update their subscription plans.
* To enable your buyers [manage activations](https://freemius.com/help/help/documentation/users-account-management/activations/.md) on the devices or instances where the license have been added.
### Templates and Kits[](#templates-and-kits "Direct link to Templates and Kits")
* To provide customers with a dedicated space to download latest versions of purchased templates and kits.
* To provide support via a ticketing system, with Docs and FAQ integration directly in the portal.
## Login and Security[](#login-and-security "Direct link to Login and Security")
* Customers will receive their login credentials to the portal after their first purchase.
* For security reasons, customers will have to update their auto-generated password upon their first login.
* You can also use the [magic login](https://freemius.com/help/help/documentation/users-account-management/magic-login-link/.md) feature to generate secure passwordless login links from your own application or website.
---
# Enable your customers to manage their license activations
The Freemius Customer Portal includes a dedicated "**Activations**" page where your customers can view and manage their active installations of your app. This feature allows you to sell [your app](https://freemius.com/help/help/documentation/saas/app-integration/.md) while fully offloading license management to Freemius. Freemius handles key operations, including [license activation and deactivation](https://freemius.com/help/help/documentation/saas/integrating-license-key-activation/.md), and gives your customers a way to manage those licenses outside of the app.

Once your application is integrated with the [license activation API](https://freemius.com/help/help/documentation/saas/integrating-license-key-activation/.md), your customers will be able to see a list of their active installations (activations) on this page. Each activation represents an instance or a device where the license key has been activated.
## Features[](#features "Direct link to Features")
The activations page includes the following handy features for your customers:
### Activation Details and Plan Upgrade[](#activation-details-and-plan-upgrade "Direct link to Activation Details and Plan Upgrade")
Clicking an activation opens a details page with more information, such as the plan, license key, and number of activations.

It also displays easy upgrade and downgrade options.
### License Deactivation[](#license-deactivation "Direct link to License Deactivation")
To deactivate a license, your customers can simply click the "Deactivate" button. A confirmation step is shown before the license is actually deactivated.

Completing this process allows your customers to free up the license for use on another device or installation.
---
# Applying CSS Customization to the Customer Portal
You can customize the appearance of the Freemius Customer Portal with CSS to match your brand and site design. The portal supports custom style sheets and CSS variable overrides for flexible theming.
## How CSS Customization Works[](#how-css-customization-works "Direct link to How CSS Customization Works")
* The Customer Portal is embedded via an iframe or direct link, and you can apply custom CSS by specifying a style sheet URL in the Freemius Developer Dashboard.
* The portal uses CSS variables for colors, typography, and layout. Overriding these variables makes theme adjustments easy.
## Overriding CSS Variables[](#overriding-css-variables "Direct link to Overriding CSS Variables")
Inspect the portal's root element using your browser's developer tools to discover available CSS variables.

Common variable groups include:
* `--fs-ds-theme-*`: Theme colors for major components
* `--fs-ds-typography-*`: Font families, sizes, and weights
* `--fs-ds-appearance-*`: Border radius, sizing, etc.
For example, to change the primary color and font family, add the following CSS:
```
:root {
--fs-ds-theme-primary: #6753ff;
--fs-ds-typography-font-family: 'Open Sans', sans-serif;
}
```
## Adding Your Custom CSS[](#adding-your-custom-css "Direct link to Adding Your Custom CSS")
1. Host your CSS file on a secure (HTTPS) server.
2. Log in to your [Freemius Developer dashboard](https://dashboard.freemius.com/).
3. Navigate to your store by clicking **Stores** in the top-left panel under the Freemius logo.
4. Select your store by name.
5. Scroll down the menu and select the **Settings** page.
6. Under the Customer Portal sub-page, scroll to the **Customization** section.
7. Paste your stylesheet URL into the **Customer Portal CSS stylesheet** field.
Your styles will then load and apply to the portal.

## Tips[](#tips "Direct link to Tips")
* Always use the `:root` selector for global variable overrides.
* Monitor changes in the Customer Portal HTML to avoid breaking your custom styles.
* For advanced theming, combine variable overrides with custom CSS rules targeting portal elements.
warning
Major portal updates may affect your custom styles; review changes after each update.
If you have questions or need help with advanced customization, contact Freemius Support.
---
# Cancellation Survey: Collect User Feedback on Subscription Cancellations
Sometimes, users cancel their subscriptions for reasons that can be addressed to improve your product or service. Understanding these reasons helps enhance customer retention and reduce churn.
## Collecting Cancellation Feedback[](#collecting-cancellation-feedback "Direct link to Collecting Cancellation Feedback")
The Freemius Customer Portal includes a built-in cancellation survey displayed when a customer cancels a subscription. This survey runs as part of the cancellation flow and requires no additional setup, gathering valuable feedback from users.

## Accessing the Feedback[](#accessing-the-feedback "Direct link to Accessing the Feedback")
You can access cancellation reasons via [events and webhooks](https://freemius.com/help/help/documentation/saas/events-webhooks/.md#subscription-cancelled) and process the data programmatically using the [API endpoint](https://docs.freemius.com/api/events/retrieve).
Additionally this information is also available in the transactional email sent to you when a subscription is canceled by a customer.

Reducing Cancellations
Freemius has out-of-the-box automation features that help reduce churn. You can also use [special coupons and discounts](https://freemius.com/help/help/documentation/marketing-automation/special-coupons-discounts/.md#subscription_cancellation_coupon) as an additional strategy to combat cancellations
---
# Downloading Purchased Products
The Customer Portal has a dedicated **Downloads** section where customers can easily access and download the latest versions of your downloadable software products.

## Supported Product Types[](#supported-product-types "Direct link to Supported Product Types")
The Downloads section supports the following product types:
1. [**WordPress**](https://freemius.com/help/help/documentation/wordpress/.md) products, including plugins, themes, [add-ons](https://freemius.com/help/help/documentation/wordpress/selling-add-ons-extensions/.md), and [bundles and membership](https://freemius.com/help/help/documentation/wordpress/selling-bundles-and-memberships/.md) for WordPress.
2. [**Templates and Kits**](https://freemius.com/help/help/documentation/selling-templates/.md) for various platforms.
3. [**Apps**](https://freemius.com/help/help/documentation/saas/app-integration/.md) - While Freemius doesn't support deployment for Apps, you can still create various custom links and those will be shown in the Download section. Please follow [our guide](https://freemius.com/help/help/documentation/saas/custom-download-links/.md).
## Composer Integration[](#composer-integration "Direct link to Composer Integration")
If your customers wish to manage WordPress plugins and themes using [Composer](https://getcomposer.org/), click the **Composer** button and follow the provided instructions.

---
# Earn - Becoming an Affiliate
Freemius Makers have an affiliate program out of the box that enables their product users market the products they use and earn sales commission.

note
By default this is disabled for each product. The product maker needs to [activate the Affiliate program](https://freemius.com/help/help/documentation/affiliate-platform/affiliate-program-activation/.md) for their users to earn. If you do not see this section in your dashboard consider talking to the product maker to enable this for you.
## How do you join the program[](#how-do-you-join-the-program "Direct link to How do you join the program")
There are multiple ways to sign up/apply to join the program via:
### Customer Portal affiliate application form[](#customer-portal-affiliate-application-form "Direct link to Customer Portal affiliate application form")
* Inside your account dashboard, look for the ***Earn*** section on the side menu on the left.
* Click the **Become an Affiliate** button.

* Submit your application to the program by filling in the required details.

* Agree to the terms.

### The product maker's website affiliate application form[](#the-product-makers-website-affiliate-application-form "Direct link to The product maker's website affiliate application form")
On the product page/website, the maker might have an embedded form which you can use to apply.
### The WP Admin Dashboard Affiliates Application Form[](#the-wp-admin-dashboard-affiliates-application-form "Direct link to The WP Admin Dashboard Affiliates Application Form")
If a product maker has inserted the application form in their product, a form like this might be available.

Submit your application to the program by filling in the required details and agree to the terms.
Your application will be reviewed by the product maker and approved if appropriate. You can now start selling as an affiliate.
note
Your commission will be calculated and remitted after the 10th and before the end of the month to the payment email provided.
---
# Embed the Freemius Customer Portal into your website
You can provide your customers with easy access to their accounts by either adding a direct link to the Customer Portal on your website or embedding it directly into your site.
## Adding a Link on Your Store[](#adding-a-link-on-your-store "Direct link to Adding a Link on Your Store")
If you have a website, we recommend adding a direct link to the portal to make it easier for your customers to access their account. The Customer Portal URL takes the structure below:
```
https://customers.freemius.com/store/
```
Where `` is your unique store identifier. Here are the steps to find your **Store ID** in the [Store's settings](#find-store-id--public-key).
## Embedding the Customer Portal in Your Store[](#embedding-the-customer-portal-in-your-store "Direct link to Embedding the Customer Portal in Your Store")
Alternatively, you can embed the portal directly on your site. The customer portal has been designed to work for this exact use case, keeping users on your store and making the portal feel native to your site.
### Find Store ID & Public Key[](#find-store-id--public-key "Direct link to Find Store ID & Public Key")
You will need your store ID and public key to embed the portal.
1. Log into your [Freemius Developer dashboard](https://dashboard.freemius.com/).
2. Navigate to your store by clicking **Stores** in the top-left panel under the Freemius logo.
3. Select your store by name.
4. Scroll down the menu and select the **Settings** page.
5. Under the **API & Keys** sub-page, you will find your store ID and public key.
[](/help/videos/freemius-dashboard-my-store-menu.mp4)
### Embedding Into a WordPress Website[](#embedding-into-a-wordpress-website "Direct link to Embedding Into a WordPress Website")
1. Install and activate [Freemius Customer Portal WordPress Plugin](https://github.com/Freemius/freemius-users-dashboard/).
2. Create a new page on your website. We recommend using one of the following slugs: `users`, `account`, `members`. Save the page's link to be used in step 4.
3. In your [Freemius Developer account](https://dashboard.freemius.com/), navigate to your store by clicking **Stores** in the top-left panel under the Freemius logo.
4. Select your store by name.
5. Scroll down the menu and select the **Settings** page.
6. Under the Customer Portal sub-page, scroll to the **Embed on your Website** section.
[](/help/videos/freemius-dashboard-my-store-menu-user-dashboard-settings.mp4)
7. In the *Customer Portal URL* setting field, paste the URL address of the page created (in step 2). Make sure the protocol (HTTP or HTTPS) is accurate, otherwise, the portal will not load.

8. Add this shortcode to the page you created:
```
[fs_members store_id="" public_key="" position="fixed" left="0" right="0" top="px" bottom="0"]
```
9. Replace the following placeholders in the shortcode with your information that we obtained in the [previous steps](#find-store-id--public-key):
* `` - with your store ID.
* `` - with your store's public key.
* `` - with your site's header height.
Responsive Header Height
If your site’s header height is responsive, you can customize the dashboard’s position with media queries by styling the iframe’s `
` container as follows:
```
#fs_dashboard_container {
top: {headerDesktopHeight}px;
}
@media screen and(max-width: 600px) {
#fs_dashboard_container {
top: {headerMobileHeight}px;
}
}
```
### Disabling Redirect to WordPress Login Page[](#disabling-redirect-to-wordpress-login-page "Direct link to Disabling Redirect to WordPress Login Page")
Users might be redirected to the default WordPress login page when they log out of the embedded Customer Portal. To disable this behavior, add the following code snippet to the bottom of your active theme's `functions.php` file:
```
function my_fs_members_dashboard_config( $config ) {
$config = str_replace( 'window.location.href', '// window.location.href', $config );
return $config;
}
add_filter( 'fs_members_dashboard', 'my_fs_members_dashboard_config' );
```
### Support For Multiple Domains[](#support-for-multiple-domains "Direct link to Support For Multiple Domains")
For security purposes, the portal will not load unless it is embedded on the domain specified in the Freemius Developer Dashboard.
However, there are cases where you may want to allow the portal to be embedded on multiple domains, for example, if you have a staging environment or a local development environment.
To set up, follow these steps:
1. Log into your [Freemius Developer dashboard](https://dashboard.freemius.com/).
2. Navigate to your store by clicking **Stores** in the top-left panel under the Freemius logo.
3. Select your store by name.
4. Scroll down the menu and select the **Settings** page.
5. Under the Customer Portal sub-page, scroll to the **Embed on your Website** section.
6. Add your additional domains in the **Additional Whitelisted Domains** configuration.
7. Click the **Add** button to save the additional domain.

## Customizing the Customer Portal Appearance[](#customizing-the-customer-portal-appearance "Direct link to Customizing the Customer Portal Appearance")
For the Customer Portal to match your website and brand look, find up-to-date instructions on [customizing the Customer Portal appearance with CSS](https://freemius.com/help/help/documentation/users-account-management/applying-css-customization/.md).
---
# License Security
The Customer Portal offers builders, like agencies and freelancers, who work on client projects greater protection of their data and license through a special **LICENSE SECURITY** section available for every license.

## White Label Mode[](#white-label-mode "Direct link to White Label Mode")
By flagging a license as "White Labeled", license owners can easily hide confidential information about their account and license:

This means that account details normally shown in the *Account* tab in the WP Admin will not appear after checking the “This license is activated on my client(s) site(s)” box.
Here’s what will be hidden when a license is set as white-labeled:
* User information
* Billing details and invoices
* License key
* Pricing page
* Add-on prices (if you sell add-ons)
* Contact Us page
## URL Whitelisting (aka "License Firewall")[](#url-whitelisting-aka-license-firewall "Direct link to URL Whitelisting (aka \"License Firewall\")")
Freemius comes with a License Firewall, empowering license owners to control the websites that can activate their license or continue receiving updates.

[](/help/videos/freemius-customer-portal-license-security-url-whitelisting.mp4)
---
# Generate Magic Links to Automatically Log in Customers to the Portal
Using the Freemius API, you can generate magic links (or session links) that allow your customers to automatically log in to the Customer Portal without having to enter their credentials again. This provides a seamless experience for your customers when they want to manage their subscriptions, billing, and more.
Here is the flow:
1. The user logs in to your website or application.
2. Your website or application displays a "**Manage Account**" button that points to the [Customer Portal](https://customers.freemius.com).
3. When the user clicks the "**Manage Account**" button, a new tab opens with the Customer Portal, and the user is automatically logged in to their Customer Portal without the need to log in again.
To implement this flow securely, you need to generate a magic link from the Freemius API.
## Generate a Magic Link from the API[](#generate-a-magic-link-from-the-api "Direct link to Generate a Magic Link from the API")
You need to send an authenticated request to [this API endpoint](https://docs.freemius.com/api/products/generate-portal-login-link) using the product's [bearer token](https://docs.freemius.com/api/section/bearer-token-auth).
* You can provide the email address of your user using the `email` parameter.
* Alternatively, you can provide the Freemius user ID using the `id` parameter. This is useful if you have saved the user's ID from Freemius locally (check our [integration](https://freemius.com/help/help/documentation/saas/.md) guide).
Here are some examples:
* JS SDK
* PHP
```
// Using the email address
const { link } =
await freemius.api.user.retrieveHostedCustomerPortalByEmail(
'user@example.com'
);
// Using the Freemius user ID
const { link } = await freemius.api.user.retrieveHostedCustomerPortal(9999);
```
More information can be found in the [JS SDK documentation](https://freemius.com/help/help/documentation/saas-sdk/js-sdk/api/.md#generate-magic-link-for-sso-to-customer-portal).
```
// Configure the Freemius API client
const PRODUCT_ID = "1234";
const BEARER_TOKEN = "";
// Configure the payload with either the email address or the Freemius user ID
$payload = (object)["email" => "user@example.com"]; // or (object)["user_id" => 9999]
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . BEARER_TOKEN,
"Content-Type: application/json"
],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_URL => "https://api.freemius.com/v1/products/" . PRODUCT_ID . "/portal/login.json",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
// Get the magic link from the response
$link = json_decode($response)->link;
```
## Securely Logging in to the Customer Portal[](#securely-logging-in-to-the-customer-portal "Direct link to Securely Logging in to the Customer Portal")
We recommend making an asynchronous request to your backend to generate the magic link when your customer clicks the "Manage Account" button.
warning
The generated link is valid for only 5 minutes. Do not generate it in advance, as it may expire before the user attempts to use it.
Once you receive the magic link from your backend, you can open it in a new tab to log the user in to the Customer Portal.
Assuming you have an endpoint at `/api/generate-magic-link` that generates the magic link, here is an example of how to implement this flow:
```
document
// Assuming the ID of the button is "manage-account-button"
.getElementById('manage-account-button')
// We want to intercept the click event to generate the magic link before opening the Customer Portal
.addEventListener('click', async (event) => {
event.preventDefault();
try {
const response = await fetch('/api/generate-magic-link', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Failed to generate magic link');
}
const { link } = await response.json();
window.open(link, '_blank');
} catch (error) {
console.error('Error generating magic link:', error);
alert(
'Sorry, we were unable to log you in to the Customer Portal. Please try again later.'
);
}
});
```
## Logging In to Specific Sections of the Customer Portal[](#logging-in-to-specific-sections-of-the-customer-portal "Direct link to Logging In to Specific Sections of the Customer Portal")
The generated magic link will log the user in to the main "Websites" or "Licenses" page of the Customer Portal (depending on the types of products your store has).
However, if you want to log the user in to a specific section of the Customer Portal, you can modify the generated magic link by adding a `next` query parameter. This parameter should contain the URL of the section where you want to direct the user.
The `next` parameter should be a relative URL that starts with `/store/{store_id}/`. For example, to log the user in to the "Subscriptions" section of the Customer Portal, you would add `/store/{store_id}/subscriptions` to the generated magic link.
You must URL-encode the `next` parameter value. Here are some functions to help you with that:
* JS
* PHP
```
function generateMagicLinkWithNextParameter(magicLink, nextPath) {
const url = new URL(magicLink);
url.searchParams.set('next', nextPath);
return url.toString();
}
```
```
function generateMagicLinkWithNextParameter($magicLink, $nextPath)
{
$url = parse_url($magicLink);
$queryParams = [];
if (isset($url["query"])) {
parse_str($url["query"], $queryParams);
}
$queryParams["next"] = $nextPath;
$url["query"] = http_build_query($queryParams);
return (isset($url["scheme"]) ? $url["scheme"] . "://" : "") .
($url["host"] ?? "") .
(isset($url["port"]) ? ":" . $url["port"] : "") .
($url["path"] ?? "") .
"?" .
$url["query"] .
(isset($url["fragment"]) ? "#" . $url["fragment"] : "");
}
```
Here are some URL paths for the different sections of the Customer Portal:
* **Websites**: /store/{store\_id}/websites
* **Specific Website**: /store/{store\_id}/websites/{website\_url}/filter/installed
* **Specific Product**: /store/{store\_id}/websites/{website\_url}/filter/installed/(details:installs/{install\_id})
* **Downloads**: /store/{store\_id}/downloads
* **Licenses**: /store/{store\_id}/licenses
* **Specific License**: /store/{store\_id}/licenses/(details:licenses/{license\_id})
* **Payment/Invoices**: /store/{store\_id}/payments
* **Subscriptions**: /store/{store\_id}/subscriptions
* **Specific Subscription**: /store/{store\_id}/subscriptions/(details:subscriptions/{subscription\_id})
* **My Profile**: /store/{store\_id}/profile
* **FAQ**: /store/{store\_id}/faq
* **Support**: /store/{store\_id}/support
* **Earn** (Affiliate): /store/{store\_id}/affiliation
* **Performance**: /store/{store\_id}/affiliation/performance
* **Affiliate URLs**: /store/{store\_id}/affiliation/urls
* **Referrals**: /store/{store\_id}/affiliation/referrals
* **Visit Log**: /store/{store\_id}/affiliation/visit-log
* **Payouts**: /store/{store\_id}/affiliation/payouts
---
# Orders History
As a product user, this section enables you to see all your previous orders and their details.

## Generate a printable invoice[](#generate-a-printable-invoice "Direct link to Generate a printable invoice")
Freemius always sends a post-purchase invoice to the customer's email on product makers behalf. However, sometimes users cannot find the purchase email via their inbox.
If the product user desires to get a printable invoice for their accounting purposes at a later time, they can always generate one by clicking on the **Invoice** button to get a downloadable/printable invoice.

---
# Single Sign-On for WordPress
If you are using WordPress to power your website and like to allow users and customers to login to your WordPress with their Freemius credentials, you can use our official [Single Sign-On WordPress Plugin](https://github.com/Freemius/freemius-wordpress-sso).
This plugin bridges the gap between WordPress and the Freemius API. When a user logs in with their Freemius credentials and there is no matching user in WordPress, a new user with the same email address and password will be created in WordPress.
When [embedding the Customer Portal](https://freemius.com/help/help/documentation/users-account-management/.md) on your site using our [Customer Portal WordPress plugin](https://github.com/Freemius/freemius-users-dashboard), a logged-in user will be automatically logged into their Freemius Customer Portal without the need to log in again. This structure offers a much better user experience to your users.
## Enabling SSO for WordPress[](#enabling-sso-for-wordpress "Direct link to Enabling SSO for WordPress")
1. Download the plugin from [GitHub](https://github.com/Freemius/freemius-wordpress-sso/archive/refs/heads/master.zip).
2. Open your WordPress installation's `wp-config.php` file and add the following lines at the end, replacing the placeholders with your actual credentials:
```
define( 'FREEMIUS_STORE_ID', '' );
define( 'FREEMIUS_DEVELOPER_ID', '' );
define( 'FREEMIUS_SECRET_KEY', '' );
```
Access the required credentials
Via the [Freemius Developer Dashboard](https://dashboard.freemius.com/), under the:
* **My Profile** page, get your `developer ID` and `secret key`.
* **Store** → **Settings** page, get your `store ID`.
3. Upload and activate the plugin.
4. Done! Users will be able to login with their Freemius credentials and your local WP users will be automatically logged in to Freemius without needing to log in again.
## How to restrict content from non-paying customers?[](#how-to-restrict-content-from-non-paying-customers "Direct link to How to restrict content from non-paying customers?")
The SSO (Single Sign-On) plugin comes with several handy methods to easily allow you control the logic according to the user's state on Freemius.
For example, if you have a contact form page or a forum, which you wish to restrict from users but show it to your customers, you can create a [page template](https://developer.wordpress.org/themes/template-files-section/page-template-files/) that will render different content according to the user's licenses on Freemius as follows:
```
get_freemius_has_any_license();
$has_any_active_license = $sso->get_freemius_has_any_active_license();
}
} ?>
Whatever you want to show when the user is not logged in.
User logged in and has at least one license on Freemius.
User logged in and has at least one ACTIVE license on Freemius.
Whatever you want to show when the user is logged in but does not have any licenses for your product on Freemius.
```
---
# Support Integration in the Customer Portal
## How to enable the Contact Form[](#how-to-enable-the-contact-form "Direct link to How to enable the Contact Form")
To activate the contact form,
1. Log into your [Freemius Developer dashboard](https://dashboard.freemius.com/).
2. Navigate to your store by clicking **Stores** in the top-left panel under the Freemius logo.
3. Select your store by name.
4. Scroll down the menu and select the **Settings** page.
5. Under the Customer Portal sub-page, scroll to the **Support Form** section.
6. Toggle the *Show support contact form* switch on:
[](/help/videos/freemius-developer-dashboard-enable-contact-form.mp4)
7. Refresh the Customer Portal, and you should see the new “Support” menu item.

### Contextual Categories[](#contextual-categories "Direct link to Contextual Categories")
The contact form dynamically shows different categories according to the selected product and user information.
For example, a “Trial Extension” category will only appear for products offering free trials:

We plan to keep improving this as we go, making it more intelligent over time.
### FAQ Integration[](#faq-integration "Direct link to FAQ Integration")
Upon a category and subcategory selection, the contact form will automatically reveal the relevant questions and answers from the FAQ, when applicable:
### Refund Policy Integration[](#refund-policy-integration "Direct link to Refund Policy Integration")
When a customer indicates they are contacting support for a refund, a short human-readable summary of the refund policy will reactively appear to remind the customer what they agreed to when purchasing your product:

The commonest refund request is for subscription renewals. So, we hope that the policy reminder, which highlights that renewals are generally not refundable, will reduce the number of customers asking for refunds.
### Help Scout Docs Integration[](#help-scout-docs-integration "Direct link to Help Scout Docs Integration")
If you integrate the contact form with Help Scout Docs, you can auto-populate relevant articles directly from your knowledge base:
[](/help/videos/freemius-user-dashboard-contact-form-helpscout-docs.mp4)
To enable the integration of the contact form with your Help Scout Docs, head to ***Integrations*** -> ***Help Scout*** in the Developer Dashboard, and set your Docs API Key:

More information can be found [here](https://freemius.com/help/help/documentation/integrations/help-scout/.md).
note
If you are not using Help Scout for your documentation center, we plan to enrich the capability for custom API endpoints to search documents.
### Public Knowledge Base Integration[](#public-knowledge-base-integration "Direct link to Public Knowledge Base Integration")
Whether you use Help Scout Docs or any other publicly available knowledge base, we now encourage users to check your docs while waiting to hear back from support:

To enable this extra capability, you’ll need to enable the “Knowledge Base” switch in any of your product’s plans, and include the link to your knowledge base:

### Freemius Checkout Integration[](#freemius-checkout-integration "Direct link to Freemius Checkout Integration")
If you are not offering free support, you’ll notice that support-related categories highlighted as **PAID ONLY**, making it clear to users that they need to be a paying customer to receive support.
If the user chooses to ignore that, all the integrations mentioned above will continue functioning, trying to surface all the relevant data to help the user in a self-served approach. But, if the user pursues a ticket, they’ll be prompted with a dialog box explaining they need to purchase (or renew) a license first. If they choose to get a license, the checkout will be opened right on the spot, offering them an uninterrupted experience until the submission of their ticket without even leaving the page!
[](/help/videos/freemius-user-dashboard-contact-form-contextual-upsell.mp4)
## Customizations[](#customizations "Direct link to Customizations")
### Link to Your Own Support Page[](#link-to-your-own-support-page "Direct link to Link to Your Own Support Page")
If you'd like to link to your own support page instead of using the built-in form, you can configure it from your store settings.
1. Log into your [Freemius Developer dashboard](https://dashboard.freemius.com/).
2. Navigate to your store by clicking **Stores** in the top-left panel under the Freemius logo.
3. Select your store by name.
4. Scroll down the menu and select the **Settings** page.
5. Under the Customer Portal sub-page, scroll to the **Support Form** section.
6. Fill in the URL of your external support page.

7. When a customer accesses support from the Customer Portal, they will see a **Continue** button that opens your custom support page in a new tab.

### How to hide selected categories of the contact form[](#how-to-hide-selected-categories-of-the-contact-form "Direct link to How to hide selected categories of the contact form")
Each category of the contact form is assigned with a unique `id` attribute. These attributes allow you to easily hide any category using your [custom CSS stylesheet set in the “My Store” configuration page](https://freemius.com/help/help/documentation/users-account-management/applying-css-customization/.md#adding-your-custom-css).
For example, the “Technical Support” category’s unique `id` is `contact__technical_issue` enabling you to hide the category using:
```
#contact__technical_issue {
display: none;
}
```

tip
Learn how to [embed the Customer Portal](https://freemius.com/help/help/documentation/users-account-management/embedding-customer-portal/.md) right into your website.
---
# Integrate WordPress plugins & themes with Freemius WP SDK
The WordPress SDK enables plugin and theme developers to [integrate their products with Freemius](https://freemius.com/help/help/documentation/wordpress-sdk/integrating-freemius-sdk/.md) and work with WordPress sites seamlessly, handling all aspects of licensing.
It automatically manages license activation, deactivation, and validation, ensuring that only authorized users can access premium features while free users can upgrade.
This section explains the features of the WordPress SDK used for integration and license management, including:
* [In-dashboard purchases & upgrades](https://freemius.com/help/help/documentation/wordpress-sdk/wp-admin-in-dashboard-upgrading/.md).
* [Opt-in screen](https://freemius.com/help/help/documentation/wordpress-sdk/opt-in-message/.md).
* [License activation screen](https://freemius.com/help/help/documentation/wordpress-sdk/opt-in-message/.md#license-activation-screen).
* [Licensing handling](https://freemius.com/help/help/documentation/wordpress-sdk/software-licensing/.md).
* [Account Management](https://freemius.com/help/help/documentation/wordpress-sdk/wp-admin-account/.md).
* [Localization support](https://freemius.com/help/help/documentation/wordpress-sdk/text-strings-customization/.md).
* [Testing procedures](https://freemius.com/help/help/documentation/wordpress-sdk/testing/.md).
* [Debugging techniques](https://freemius.com/help/help/documentation/wordpress-sdk/debugging/.md).
Customizing the Integration
When integrating your products with Freemius, the WordPress SDK provides various [action hooks and filters](https://freemius.com/help/help/documentation/wordpress-sdk/filters-actions-hooks/.md) that empower developers to modify the default feature settings.
---
# Debugging
Freemius has inbuilt options for debugging and logging issues so as to make the integration process easier for developers. Here is how to enable and use these features:
## Access the Freemius Debug Page[](#access-the-freemius-debug-page "Direct link to Access the Freemius Debug Page")
You can access the **Freemius Debug** page directly via `{domain}/wp-admin/admin.php?page=freemius` to get information about the active SDK, Freemius-powered products on the site, users, and more.

Setting `WP_FS__DEV_MODE` to `true` will enable logging. It will automatically activate the logger that outputs all logs to your browser's debugging console.

## Output Logs to Screen[](#output-logs-to-screen "Direct link to Output Logs to Screen")
You can output all logs to the screen by adding `&fs_dbg_echo=true` to the query string.
Security Tip
Adding `fs_dbg_echo=true` to the query string will only work when Freemius is set to [development mode](https://freemius.com/help/help/documentation/wordpress-sdk/testing/.md#setting-freemius-into-development-mode). Therefore, websites using your product are not at risk of exposing the logs unless an administrator sets `WP_FS__DEV_MODE` to `true` or manually toggles on the debugging switch in the *Freemius Debug* page.
## Debug Bar Integration[](#debug-bar-integration "Direct link to Debug Bar Integration")
Additionally, if you are using [Debug Bar](https://wordpress.org/plugins/debug-bar/), Freemius SDK logging is fully integrated with it to make debugging even easier with its UI.

---
# Filters & Actions Hooks
The Freemius WordPress SDK provides a collection of filters and actions that allow developers to customize and extend the functionality of their WordPress plugins or themes.
The concept is similar to WordPress’ own filters and actions also known as [hooks](https://developer.wordpress.org/plugins/hooks/).
## Using Freemius Hooks[](#using-freemius-hooks "Direct link to Using Freemius Hooks")
Using the Freemius hooks requires access to the Freemius instance, which is typically available in the main plugin file or wherever the SDK is initialized.
### Direct Usage[](#direct-usage "Direct link to Direct Usage")
In your theme or plugin, you can use the standard `add_action()` or `add_filter()` functions via the Freemius instance, namely;
* `my_fs()->add_action()`
* `my_fs()->add_filter()`
An example using the direct method.
```
my_fs()->add_action( 'after_license_activation', 'my_fs_after_license_activation' );
function my_fs_after_license_activation() {
// Handle successful activation.
}
```
note
Ensure to change `my_fs()` to your actual Freemius instance prefix.
### Unique Hook Names[](#unique-hook-names "Direct link to Unique Hook Names")
Similar to WordPress hooks, you can use a unique hook name for your product to avoid conflicts with other plugins or themes that might be using the same hook name.
The naming convention for creating unique hook names is as follows:
* The hook starts with `fs_` to identify it as a Freemius SDK hook.
* Second, include the action name, e.g., `after_license_change`.
* Finally, add the product slug at the end.
* For plugins, add `my-plugin-slug`. Replace `my-plugin-slug` with the actual plugin slug.
* For themes, add `my-theme-slug-theme`. Replace `my-theme-slug` with the actual theme slug before the suffix `-theme`.
Example: A product with the slug `awesome-plugin` using `playground_anonymous_mode` filter hook.
```
add_filter( 'fs_playground_anonymous_mode-awesome-plugin', '__return_false' );
```
tip
The hook priorities and accepted arguments work the same way as in WordPress core e.g., see the [`is_submenu_visible`](#is_submenu_visible) filter hook.
## Filters Hooks[](#filters-hooks "Direct link to Filters Hooks")
Filters allow developers to modify specific data or behavior within the Freemius SDK. To add a filter, use the following method from the Freemius instance:
```
my_fs()->add_filter(
string $tag,
callable $callback,
$priority = 10,
$accepted_args = 1
);
```
The method signature is the same as WordPress' [add\_filter](https://developer.wordpress.org/reference/functions/add_filter/) function. The following filters are available:
### `default_currency`[](#default_currency "Direct link to default_currency")
Set the default currency of the pricing page and checkout within the WP Admin dashboard.
```
function my_fs_default_currency( $currency ) {
return 'eur';
}
my_fs()->add_filter( 'default_currency', 'my_fs_default_currency' );
```
### `connect_url`[](#connect_url "Direct link to connect_url")
Modifies the URL used for the activation screen.
### `trial_promotion_message`[](#trial_promotion_message "Direct link to trial_promotion_message")
Customizes the message in the admin notice promoting trial usage to users.
### `is_long_term_user`[](#is_long_term_user "Direct link to is_long_term_user")
Determines if a user is considered long-term based on custom criteria. The uninstall reasons shown in the deactivation feedback dialog are determined based on this user type.
### `uninstall_reasons`[](#uninstall_reasons "Direct link to uninstall_reasons")
Defines the reasons presented to users in the deactivation feedback dialog during product deactivation.
### `is_plugin_update`[](#is_plugin_update "Direct link to is_plugin_update")
Checks if Freemius was first added in a product update.
### `api_domains`[](#api_domains "Direct link to api_domains")
Specifies API domains to include in an activation error message about domains that need to be whitelisted.
### `support_forum_submenu`[](#support_forum_submenu "Direct link to support_forum_submenu")
Manages the visibility of the support forum submenu.
### `support_forum_url`[](#support_forum_url "Direct link to support_forum_url")
Sets the URL for the support forum link for the product.
```
my_fs()->add_filter( 'support_forum_url', function ( $url ) {
return 'https://awesome-plugin.com/support-forum/';
});
```
### `pricing_url`[](#pricing_url "Direct link to pricing_url")
Override the default Freemius WordPress SDK pricing page. The \`$url\` parameter can be set to any other link e.g. website.
```
my_fs()->add_filter( 'pricing_url', function ( $url ) {
return 'https://awesome-plugin.com/pricing/';
});
```
### `connect_message`[](#connect_message "Direct link to connect_message")
Customizes the message displayed during the activation process.
### `connect_message_on_update`[](#connect_message_on_update "Direct link to connect_message_on_update")
Adjusts the connection message shown during the activation process when Freemius was first added in a product update.
### `show_deactivation_subscription_cancellation`[](#show_deactivation_subscription_cancellation "Direct link to show_deactivation_subscription_cancellation")
Disable the subscription cancellation prompt when deactivating the plugin.
```
my_fs()->add_filter( 'show_deactivation_subscription_cancellation', '__return_false' );
```
### `uninstall_confirmation_message`[](#uninstall_confirmation_message "Direct link to uninstall_confirmation_message")
Customizes the confirmation message to show in an additional panel within the deactivation feedback dialog before the panel with the deactivation reasons is shown.
### `pending_activation_message`[](#pending_activation_message "Direct link to pending_activation_message")
Customizes the message shown when activation is pending (when it requires completion by clicking an activation link sent via email).
### `is_submenu_visible`[](#is_submenu_visible "Direct link to is_submenu_visible")
Controls the visibility of specific submenus for the product. Here is an example to conditionally show the contact submenu item for customers with a trial or are paying.
```
function my_fs_submenu_visibility_handler( $is_visible, $id ) {
if ( 'contact' === $id ) {
$is_visible = my_fs()->is_paying_or_trial();
}
return $is_visible;
}
my_fs()->add_filter( 'is_submenu_visible', 'my_fs_submenu_visibility_handler', 10, 2 );
```
### `plugin_icon`[](#plugin_icon "Direct link to plugin_icon")
Sets the icon displayed for the product. This affects the icon on the activation page, pricing page, and the updates section.
### `show_trial`[](#show_trial "Direct link to show_trial")
Helps determine whether the Free Trial tab or the trial promotion admin notice should be shown.
### `is_pricing_page_visible`[](#is_pricing_page_visible "Direct link to is_pricing_page_visible")
Controls the visibility of the pricing link for the product.
### `pricing/show_annual_in_monthly`[](#pricingshow_annual_in_monthly "Direct link to pricingshow_annual_in_monthly")
By default, the pricing page shows monthly prices.

However, this behavior can be changed in the pricing page by including this single line of code in your SDK integration code and the pricing app will show the actual annual amounts.
```
my_fs()->add_filter( 'pricing/show_annual_in_monthly', '__return_false' );
```

### `checkout/parameters`[](#checkoutparameters "Direct link to checkoutparameters")
Allows you to customize the Checkout with a subset of the options available in the [Checkout JS API](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md). For example, the snippet below enables the refund badge and reviews, and sets the billing cycle selector to a dropdown (bypassing the upsells UI).
**Example**
```
my_fs()->add_filter( 'checkout/parameters', function ( $existing_params ) {
// You can use the $existing_params to check how the checkout is currently configured.
// Everything you return from this callback will override the existing parameters.
return [
'show_refund_badge' => true,
'show_reviews' => true,
'billing_cycle_selector' => 'dropdown',
];
} );
```
To ensure the core checkout logic is not broken, only a specific set of keys is supported.
* [currency](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#currency)
* [default\_currency](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#default_currency)
* [always\_show\_renewals\_amount](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#always_show_renewals_amount)
* [annual\_discount](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#annual_discount)
* [billing\_cycle](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#billing_cycle)
* [billing\_cycle\_selector](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#billing_cycle_selector)
* [bundle\_discount](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#bundle_discount)
* [maximize\_discounts](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#maximize_discounts)
* [multisite\_discount](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#multisite_discount)
* [show\_inline\_currency\_selector](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#show_inline_currency_selector)
- [form\_position](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#form_position)
- [is\_bundle\_collapsed](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#is_bundle_collapsed)
- [layout](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#layout)
- [refund\_policy\_position](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#refund_policy_position)
- [show\_refund\_badge](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#show_refund_badge)
- [show\_reviews](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#show_reviews)
- [title](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#title)
- [show\_monthly](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#show_monthly)
- [show\_upsells](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#show_upsells)
### `checkout/purchasedCompleted`[](#checkoutpurchasedcompleted "Direct link to checkoutpurchasedcompleted")
Add custom JavaScript code to run after the user has successfully made a purchase. For example
```
my_fs()->add_filter('checkout/purchaseCompleted', function () {
return <<add_action( string $tag, callable $function_to_add, $priority = 10, $accepted_args = 1 );
```
This method signature is similar to WordPress' [add\_action](https://developer.wordpress.org/reference/functions/add_action/) function. The following actions are available:
### `after_license_change`[](#after_license_change "Direct link to after_license_change")
Executed after a license change occurs.
```
my_fs()->add_action( 'after_license_change', 'my_fs_log_after_license_change', 10, 2 );
function my_fs_log_after_license_change( $status, $plan ) {
error_log( print_r( 'License status: ' . $status, true ) );
error_log( print_r( 'Plan:', true ) );
error_log( print_r( $plan, true ) );
}
```
### `after_license_activation`[](#after_license_activation "Direct link to after_license_activation")
Executed after a license activation occurs. Example:
```
my_fs()->add_action( 'after_license_activation', 'my_fs_after_license_activation' );
function my_fs_after_license_activation() {
// Handle successful activation.
}
```
On execution, this hook returns two arguments:
* The status as a string, depending on the license change type (e.g., `changed`).
* The plan object with a schema similar to the [retrieve a plan](https://docs.freemius.com/api/plans/retrieve) API endpoint.
### `after_license_deactivation`[](#after_license_deactivation "Direct link to after_license_deactivation")
Executed after a license deactivation occurs. Example:
```
my_fs()->add_action( 'after_license_deactivation', 'my_fs_after_license_deactivation' );
function my_fs_after_license_deactivation( $license ) {
if ( ! empty( $license->id ) ) {
// Success, do something with the license.
} else {
// Failure, handle the error.
// echo $license->error->code;
// echo $license->error->message;
}
}
```
On execution, the hook provides a license object with a schema similar to the [retrieve a license](https://docs.freemius.com/api/licenses/retrieve) API endpoint.
### `after_plans_sync`[](#after_plans_sync "Direct link to after_plans_sync")
Runs after the product's plans are synchronized with Freemius.
### `after_account_details`[](#after_account_details "Direct link to after_account_details")
Triggered after account details on the “Account” page are outputted (before the billing and payments sections).
### `after_account_user_sync`[](#after_account_user_sync "Direct link to after_account_user_sync")
Executed after user account synchronization.
### `after_account_plan_sync`[](#after_account_plan_sync "Direct link to after_account_plan_sync")
Runs after account plan synchronization.
### `before_account_load`[](#before_account_load "Direct link to before_account_load")
Triggered before an account is loaded.
### `after_account_connection`[](#after_account_connection "Direct link to after_account_connection")
Executed after a successful activation connection (when an account is successfully created).
### `account_email_verified`[](#account_email_verified "Direct link to account_email_verified")
Triggered when an account email address is verified.
### `account_page_load_before_departure`[](#account_page_load_before_departure "Direct link to account_page_load_before_departure")
Executed before finishing the loading of the Account page.
### `before_account_delete`[](#before_account_delete "Direct link to before_account_delete")
Runs before an account is deleted.
### `after_account_delete`[](#after_account_delete "Direct link to after_account_delete")
Executed after an account is deleted.
### `sdk_version_update`[](#sdk_version_update "Direct link to sdk_version_update")
Triggered after the SDK version is updated.
### `plugin_version_update`[](#plugin_version_update "Direct link to plugin_version_update")
Executed after the product version is updated.
### `initiated`[](#initiated "Direct link to initiated")
Runs after the Freemius SDK is initialized.
### `after_init_plugin_registered`[](#after_init_plugin_registered "Direct link to after_init_plugin_registered")
Runs after the Freemius SDK is initialized and the user is registered.
### `after_init_plugin_anonymous`[](#after_init_plugin_anonymous "Direct link to after_init_plugin_anonymous")
Runs after the Freemius SDK is initialized and the user is anonymous.
### `after_init_plugin_pending_activations`[](#after_init_plugin_pending_activations "Direct link to after_init_plugin_pending_activations")
Runs after the Freemius SDK is initialized and the user’s activation is pending confirmation.
### `after_init_addon_registered`[](#after_init_addon_registered "Direct link to after_init_addon_registered")
Runs after the Freemius SDK is initialized for an add-on and the user is registered.
### `after_init_addon_anonymous`[](#after_init_addon_anonymous "Direct link to after_init_addon_anonymous")
Runs after the Freemius SDK is initialized for an add-on and the user is anonymous.
### `after_init_addon_pending_activations`[](#after_init_addon_pending_activations "Direct link to after_init_addon_pending_activations")
Runs after the Freemius SDK is initialized for an add-on and the user’s activation is pending confirmation.
### `after_premium_version_activation`[](#after_premium_version_activation "Direct link to after_premium_version_activation")
Triggered after a premium version of the product is activated.
### `after_free_version_reactivation`[](#after_free_version_reactivation "Direct link to after_free_version_reactivation")
Executed after reactivating the free version of the product.
### `after_uninstall`[](#after_uninstall "Direct link to after_uninstall")
Runs after the product is uninstalled.
### `before_admin_menu_init`[](#before_admin_menu_init "Direct link to before_admin_menu_init")
Runs before the product’s menu and submenus are added.
## More action hooks[](#more-action-hooks "Direct link to More action hooks")
We have more action hooks related to the:
* [Opt-In Screen](https://freemius.com/help/help/documentation/wordpress-sdk/opt-in-message/.md#advanced-opt-in-customization-actions)
## Checking if a hook is registered[](#checking-if-a-hook-is-registered "Direct link to Checking if a hook is registered")
Just like in WordPress core, the Freemius SDK instance provides a `has_filter()` method, which you can use to check whether a specific hook has been registered. This works for both actions and filters. The method signature is:
```
my_fs()->has_filter( string $tag, callable|bool $function_to_check );
```
You can omit the second parameter to check for any registered filter or action.
---
# Contributing to the Freemius SDK
This guide is intended to help new [Freemius SDK](https://github.com/Freemius/wordpress-sdk) developers get up and running quickly. It contains step-by-step instructions on how to effectively contribute to the SDK via the git command line and GitHub.
## Prerequisites[](#prerequisites "Direct link to Prerequisites")
For some aspects of development you may need [npm](https://www.npmjs.com/) and [Node JS](https://nodejs.org/en/) installed on your system in order to run build scripts. The easiest way to do this is via the [Node installer](https://nodejs.org/en/) which installs both npm and Node via a single installation process.
## Who is This Guide For?[](#who-is-this-guide-for "Direct link to Who is This Guide For?")
This guide is primarily intended for both new Freemius developer core team members, and Freemius partners, looking to set up their local development environment for the first time. It's important that this is done correctly to ensure it's inline with recommended best practices.
It's also also useful to refer back to this guide even if you've contributed to the Freemius SDK previously. You may need to review the workflow process from time to time if it's been a while since your last commit, or just to make sure nothing has changed.
## Ways to Contribute[](#ways-to-contribute "Direct link to Ways to Contribute")
To contribute to the Freemius SDK codebase you typically fork the main GitHub repository, and then clone this locally to make the required code changes. The following sections cover the whole process in detail.
There are various types of code edits that can be made, including:
* Adding new features.
* Updating existing features.
* Bug and typo fixes.
* Adding, or updating, comments.
## Workflow Overview[](#workflow-overview "Direct link to Workflow Overview")
Freemius uses the [Gitflow](https://nvie.com/posts/a-successful-git-branching-model/) branching model for managing contributions to the Freemius SDK repository. In this workflow the main development work is done solely via the `develop` branch. Each new development task is created in a 'feature' branch that is branched directly from `develop`.
When a feature branch is ready for review, submit a pull request and wait for feedback. If approved, the pull request will then be merged into the `develop` branch. Otherwise, it is rejected and the feature branch can be modified as required and submitted again, or deleted. Periodically the `develop` branch of the SDK will be merged into the `master` branch and a tag will be created to mark the [release](https://github.com/Freemius/wordpress-sdk/releases).
In this guide we'll only be using the command line to work with the Freemius SDK repository but you can use a Git GUI if you prefer. Popular choices are [Sourcetree](https://www.sourcetreeapp.com/) (free) and [Fork](https://git-fork.com/).
## Setting Up For the First Time[](#setting-up-for-the-first-time "Direct link to Setting Up For the First Time")
Before you can clone the Freemius SDK locally you'll need to decide on the location. You have a couple of choices depending on how you plan on working with the SDK.
The recommended way is to clone the Freemius SDK repository to a local folder on your hard drive, and then either link to it from each of your plugins/themes, or copy it over via an automated script.
To link to the SDK use [symlinks](https://en.wikipedia.org/wiki/Symbolic_link) as these can be used to map any folder to any location on your local computer and are supported via the command line on macOS, Windows, and Linux operating systems.
tip
If you're on Windows and are having issues with setting up symlinks then try using the [Link Shell Extension](https://schinagl.priv.at/nt/hardlinkshellext/linkshellextension.html) utility. Basic usage is described [here](https://www.howtogeek.com/16226/complete-guide-to-symbolic-links-symlinks-on-windows-or-linux/) (see the 'How to Create Symbolic Links with a Graphical Tool' section).
The advantage of using symlinks is that it's very easy to *always* make sure you include the latest version of the SDK with your WordPress products. Every time the local SDK repository is updated it will also be updated (automatically) in each of the plugins/themes that are associated with it (via symlinks).
However, if 'sharing' the SDK among several different plugins/themes via symlinks then don't rely on the plugin icon stored in `/freemius/assets/img`. Instead we recommend using the `'plugin_icon'` [Freemius filter](https://freemius.com/help/help/documentation/wordpress-sdk/opt-in-message/.md#opt-in-icon-customization) to override this with the correct icon path.
You could also just download the [latest release](https://github.com/Freemius/wordpress-sdk/releases) of the SDK, extract it, and add it manually to each plugin/theme. However, this is rather inefficient and the SDK can quickly become out of date as new versions are released.
### Local WordPress Development Setup[](#local-wordpress-development-setup "Direct link to Local WordPress Development Setup")
When developing with the Freemius SDK as part of an existing plugin or theme you'll need to be running a local WordPress site for testing purposes. There are many different ways you can set up a local live WordPress site but perhaps the easiest all round solution is [Local](https://localwp.com/) by Flywheel, which offers a feature-rich free version.
Whatever local WordPress server you use it's recommended that you set it up so that it's easy to debug as follows:
1. Create a **new** WordPress site only for working on the Freemius SDK.
2. Add these PHP constants to your **wp-config.php** file:
```
define( 'WP_DEBUG', true );
define( 'SCRIPT_DEBUG', true );
define( 'COMPRESS_SCRIPTS', false );
define( 'FS_METHOD', 'direct');
define( 'WP_FS__DEV_MODE', true );
```
1. Install and activate the [Query Monitor](https://wordpress.org/plugins/query-monitor/) and [Gutenberg](https://wordpress.org/plugins/gutenberg/) plugins. And if you're developing themes then also install the [Theme Check](https://en-gb.wordpress.org/plugins/theme-check/) plugin.
2. For maximum compatibility always make sure you have the latest version of WordPress, and [Gutenberg](https://en-gb.wordpress.org/plugins/gutenberg/) plugin, installed.
### Cloning the Freemius SDK[](#cloning-the-freemius-sdk "Direct link to Cloning the Freemius SDK")
To be able to submit your own custom contributions you'll need to clone it to a suitable location on your local hard drive:
```
git clone [path to repository]
```
If you don’t have commit permissions to the repository, you'll first need to fork the SDK into a separate repository under your own GitHub account as you won't have write permissions to access the SDK repository directly.
The forked repository is referred to as the main 'remote' repository, and is usually labelled 'origin' by default during the cloning process. You'll also need to add a reference to the original repository that you created the fork from to be able to keep your local repository up-to-date with the latest changes as they are merged into the codebase.
```
git remote add upstream https://github.com/Freemius/wordpress-sdk.git
```
This additional remote is typically given the label 'upstream' to clearly identify it from the forked repository. You should now have two git remotes set up to allow you to push changes to the forked repository (in order to make pull requests), and to pull changes from the upstream repository (to sync with the latest changes).
You can check what remotes are available at any time via the following command:
```
git remote -v
```
### Create a Freemius Product via the Developer Dashboard[](#create-a-freemius-product-via-the-developer-dashboard "Direct link to Create a Freemius Product via the Developer Dashboard")
To fully test all aspects of the SDK you'll need to integrate it into a working WordPress plugin or theme. This can be very basic though as you're only using it to test the SDK codebase. To complete the [integration process](https://freemius.com/help/help/documentation/getting-started/.md) you'll not only need to set up the plugin code locally, you'll also have to create the associated WordPress product in the Freemius [developer dashboard](https://freemius.com/help/help/documentation/getting-started/explore-the-developer-dashboard/.md).
Tip: If you don't have a specific plugin ready to integrate the Freemius SDK with then you can use the Freemius [Hello Dolly](https://github.com/Freemius/hello-dolly) sample plugin as a ready-to-go starter plugin.
## General Workflow Steps[](#general-workflow-steps "Direct link to General Workflow Steps")
This section details the main workflow steps from start-to-finish when contributing to the Freemius SDK.
Before submitting any code please make sure your work adheres to the following requirements:
* All code should conform to WordPress [coding standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards/) and best practices.
* Any development work should always be based off the `develop` branch (via a feature branch).
* After a pull request has been submitted it may be approved, rejected, or approved subject to modifications being made. Each pull request is reviewed and assessed individually.
* **The less code changes a pull request contains, the quicker it will be reviewed and merged (if accepted).**
### General Set Up[](#general-set-up "Direct link to General Set Up")
Let's take a look now at getting the Freemius GitHub repository set up.
1. [Clone Freemius WordPress SDK](#cloning-the-freemius-sdk) locally to a suitable location on your local hard drive.
2. By default you'll be in the `master` branch but you'll be creating feature branches directly from the `develop` branch so do `git branch develop` and `git checkout develop` to create a local branch and switch to it.
### Developing via a Feature Branch[](#developing-via-a-feature-branch "Direct link to Developing via a Feature Branch")
1. Locally create and switch to a new feature branch, created directly off the develop branch, to work on a new feature/issue.
1. `git checkout develop`
2. `git branch features/123-bug-fix` . Prefix the branch name with `feature/` and include the GitHub issue ref. number where relevant.
3. `git checkout features/123-bug-fix`
2. First thing to do is to update the SDK version number in the feature branches */freemius/start.php* file. Base this on the existing version number in *start.php* of the **current [develop branch](https://github.com/Freemius/wordpress-sdk/blob/develop/start.php) on GitHub**. For example, if it’s currently 2.4.1, then the version in the new feature branch should be 2.4.1.1, but the version in your documentation comments should be the next release version number (@since 2.4.2).
3. Make changes in the newly created feature branch. When finished **do not merge back into the develop branch**.
4. Instead, add your changes to the staging area and commit to the feature branch in the local repository. e.g. `git commit -am "new commit message"`
5. Then, push the updated feature branch changes to the remote SDK repository.
1. e.g. `git push -u origin feature/123-bug-fix`
2. Note: The `-u` flag (alias of `--set-upstream`) is important here as it allows the remote GitHub repository to track changes. It makes it very easy to update pull requests and submit changes.
6. **Important:** Before creating a pull request make sure you've included the latest changes that may have been merged into the upstream `develop` branch since the feature branch was first created. This will help to avoid merge conflicts for development branches that have become stale. *(see step 3. in the next section for more details)*
7. Go to the main page of the GitHub repository you just pushed to and **select the feature branch**. You should see a message similar to 'This branch is *x* commit(s) ahead, *x* commits behind Freemius
:master
'.
8. Click either the **Compare & pull request** button or the **Pull request** link and add details about the pull request code changes in the description box on the next page.
9. **Important!** Make sure that you select to merge into the `develop` branch and **not** `master`. Note: If you accidentally select the `master` branch and submit the pull request, you can edit the target branch at any time by opening the pull request in GitHub and clicking the **Edit** button (top right of the pull request).

1. Create the pull request and assign it to Leo on GitHub. Also, select Leo as the reviewer *(you may need elevated permissions to be able to do this)*. Leo or Vova will eventually review and approve/reject the pull request.
2. If the pull request needs more work then make the required [code](https://stackoverflow.com/questions/16748115/how-to-modify-github-pull-request) locally and push again to the remote repository to **automatically update the pull request**. a. To do this, check out the feature branch again locally and make the required changes. b. Commit changes locally and push to the remote repository as before. c. The pull request will automatically update to include the new changes.
3. Once the pull request has been resolved then repeat the steps above to create a new feature branch to work on a new issue/feature. Make sure to delete old feature branches that are no longer needed in both the local and remote repositories to keep your development workspace clutter free.
## Keeping in Sync With the SDK Repository[](#keeping-in-sync-with-the-sdk-repository "Direct link to Keeping in Sync With the SDK Repository")
Whenever a feature branch pull request is approved it's then merged into the `develop` branch of the main SDK repository. In reality this actually happens quite often and means that the `develop` branch (and all feature branches) on your local repository soon become out-of-sync, or stale.
You can always check the status of the `develop` branch on the remote (forked) GitHub repository as a quick way to see if it is behind the upstream `develop` branch.

Follow these steps to keep your local `develop` branch up to date with all recent changes to the `develop` branch of the main SDK repository.
1. In your local repository run these git commands to get the recent changes and update your local repository. Note: If there are no changes received by `git fetch` upstream then there's no need to apply step c.
1. `git checkout develop`
2. `git fetch upstream`
3. `git reset --hard upstream/develop`
2. Whenever the local `develop` branch has been updated with changes to the main GitHub repository then you need to add these changes to each of your **active** feature branches as follows. Old unmaintained (stale) feature branches don't need to be updated.
1. `git checkout feature/123-bug-fix`
2. `git merge develop`
## Making Code Changes[](#making-code-changes "Direct link to Making Code Changes")
When committing code changes make sure to add **meaningful commit messages**. We have a very specific structure for commit messages, specifically:
1. It needs to be English sentences, properly capitalized, with correct grammar and punctuation. Including a dot "." at the end of each sentence.
2. We also have a “tagging” system to give a clear context for the commit. For example, if there's a commit related to the license activation logic the commit should look like:
* "\[license-activation] Explanation goes here."
* If it's a bug fix that is related to license activation: `"[license-activation] [bug-fix] Explanation goes here."`
* If it's a new feature related to license activation: `"[license-activation] [new] Explanation goes here."`
* For enhancement:` "[license-activation] [enhancement] Explanation goes here."`
* For optimization:` "[license-activation] [optimization] Explanation goes here."`
* If it's a minor/major bug fix:` "[license-activation] [bug-fix] [major] Explanation goes here."`
* If the commit is only including documentation:` "[license-activation] [php-doc] Explanation goes here."`
Basically, by looking at the tags the reader should be able to get the context of the logic, the purpose of the commit, and its scope or severity.
There are many different tags used in commit messages but you can get a good idea of the ones we use most frequently by browsing through the [recent commits history](https://github.com/Freemius/wordpress-sdk/network).

Here are a few more tag examples used in commit messages:
* `[refactor]`
* `[cleanup]`
* `[localization]`
* `[code-convention]`
* `[enrich]`
* `[new]`
* `[revert]`
* `[bug-fix]`
* `[fix]`
* `[ui]`
* `[ux]`
* `[testing]`
* `[debug]`
Always update the associated GitHub issue you're working on to explain recent changes. This will help to keep everyone working on the pull request up-to-date with progress.
Add proper comment blocks for new classes/functions with your name. Always comment new code, and add missing comments to existing code whenever you come across it. If you're not sure what it does then add a placeholder comment such as "@todo Add comment here".
## Discovered a Bug/Issue?[](#discovered-a-bugissue "Direct link to Discovered a Bug/Issue?")
While working on a particular feature you may from time-to-time come across an unrelated bug or issue in the code. If so then create a new issue in GitHub so it can be worked on via a separate feature branch.
## Resources[](#resources "Direct link to Resources")
* [Gitflow in less than 5-minutes](https://www.youtube.com/watch?v=1SXpE08hvGs).
* [Gitflow cheatsheet](https://danielkummer.github.io/git-flow-cheatsheet/).
---
# Misc Gists
## Adding new permission to the opt-in[](#adding-new-permission-to-the-opt-in "Direct link to Adding new permission to the opt-in")
```
'helpscout', // Unique permission id.
'icon-class' => 'dashicons ',
'label' => __( 'Help Scout', 'my_plugin_slug' ),
'tooltip' => __( 'Get easy access to our support via Help Scout Beacon.', 'my_plugin_slug' ), // Optional tooltip.
'desc' => __( 'Rendering Help Scout\'s beacon for easy support access', 'my_plugin_slug' ),
'priority' => 16,
/**
* The permission layer is currently only persistent with built-in permissions.
* So if you make this permission optional, you won't be able to determine if the user switched it on or off.
*/
'is_optional' => false,
'default' => true,
);
return $permissions;
}
my_fs()->add_filter( 'permission_list', 'add_helpscount_permission' );
```
```
is_registered() &&
// Checks if user didn't opt out from tracking.
my_fs()->is_tracking_allowed()
) {
// Render Help Scout Beacon.
require_once( '../path/to/help-scout-beacon.php' );
}
```
## Customizing the support forum URL for the premium code version[](#customizing-the-support-forum-url-for-the-premium-code-version "Direct link to Customizing the support forum URL for the premium code version")
```
is_premium() ) {
my_fs()->add_filter( 'support_forum_url', 'my_premium_support_forum_url' );
}
```
## Show the contact submenu item only when the user have a valid non-expired license[](#show-the-contact-submenu-item-only-when-the-user-have-a-valid-non-expired-license "Direct link to Show the contact submenu item only when the user have a valid non-expired license")
```
can_use_premium_code();
}
my_fs()->add_filter( 'is_submenu_visible', 'my_custom_is_submenu_visible', 10, 2 );
```
## Add permission for adding Help Scout Beacon[](#add-permission-for-adding-help-scout-beacon "Direct link to Add permission for adding Help Scout Beacon")
```
'helpscout', // Unique permission id.
'icon-class' => 'dashicons ',
'label' => __( 'Help Scout', 'my_plugin_slug' ),
'tooltip' => __( 'Get easy access to our support via Help Scout Beacon.', 'my_plugin_slug' ), // Optional tooltip.
'desc' => __( 'Rendering Help Scout\'s beacon for easy support access', 'my_plugin_slug' ),
'priority' => 16,
/**
* The permission layer is currently only persistent with built-in permissions.
* So if you make this permission optional, you won't be able to determine if the user switched it on or off.
*/
'is_optional' => false,
'default' => true,
);
return $permissions;
}
my_fs()->add_filter( 'permission_list', 'add_helpscount_permission' );
```
```
is_registered() &&
// Checks if user didn't opt out from tracking.
my_fs()->is_tracking_allowed()
) {
// Render Help Scout Beacon.
require_once( '../path/to/help-scout-beacon.php' );
}
```
## Controlling the in-dashboard trial promotion in the Freemius WordPress SDK[](#controlling-the-in-dashboard-trial-promotion-in-the-freemius-wordpress-sdk "Direct link to Controlling the in-dashboard trial promotion in the Freemius WordPress SDK")
```
override_i18n( array(
'hey' => 'Hey',
'trial-x-promotion-message' => 'Thank you so much for using %s!',
'already-opted-in-to-product-usage-tracking' => 'How do you like %s so far? Test all our %s premium features with a %d-day free trial.',
'start-free-trial' => 'Start free trial',
// Trial with a payment method required.
'no-commitment-for-x-days' => 'No commitment for %s days - cancel anytime!',
// Trial without a payment method.
'no-cc-required' => 'No credit card required',
) );
#----------------------------------------------------------------------------------
#region Show the 1st trial promotion after 7 days instead of 24 hours.
#----------------------------------------------------------------------------------
function my_show_first_trial_after_7_days( $day_in_sec ) {
// 7 days in sec.
return 7 * 24 * 60 * 60;
}
my_fs()->add_filter( 'show_first_trial_after_n_sec', 'my_show_first_trial_after_7_days' );
#endregion
#----------------------------------------------------------------------------------
#region Re-show the trial promotional offer after every 60 days instead of 30 days.
#----------------------------------------------------------------------------------
function my_reshow_trial_after_every_60_days( $thirty_days_in_sec ) {
// 60 days in sec.
return 60 * 24 * 60 * 60;
}
my_fs()->add_filter( 'reshow_trial_after_every_n_sec', 'my_reshow_trial_after_every_60_days' );
#endregion
#----------------------------------------------------------------------------------
#region Add custom action after trial start/expiration
#----------------------------------------------------------------------------------
function my_after_license_change_handler( $plan_change_desc, FS_Plugin_Plan $plan) {
$plan_name = $plan->name;
switch( $plan_change_desc ) {
case 'trial_started':
// Do something immediately after a trial is started.
break;
case 'trial_expired':
// Do something immediately after a trial expiration.
break;
}
}
my_fs()->add_action( 'after_license_change', 'my_after_license_change_handler', 10, 2);
#endregion
```
## EDD to Freemius Pricing Page Override based on URLs / Href[](#edd-to-freemius-pricing-page-override-based-on-urls--href "Direct link to EDD to Freemius Pricing Page Override based on URLs / Href")
```
```
## Freemius submenu items visibility filter[](#freemius-submenu-items-visibility-filter "Direct link to Freemius submenu items visibility filter")
```
add_filter( 'is_submenu_visible', 'my_is_submenu_visible', 10, 2 );
```
## Freemius Purchase Completion JavaScript Callback Filter[](#freemius-purchase-completion-javascript-callback-filter "Direct link to Freemius Purchase Completion JavaScript Callback Filter")
```
(function() {
if ( null == ga ) {
// Add code to include GA.
}
})();
' . $html;
}
my_fs()->add_filter('checkout/purchaseCompleted', 'my_after_purchase_js');
my_fs()->add_filter('templates/checkout.php', 'my_checkout_enrich');
```
## Freemius Buy Button Code for a Multi-Plans Table[](#freemius-buy-button-code-for-a-multi-plans-table "Direct link to Freemius Buy Button Code for a Multi-Plans Table")
```
```
## Controlling the visibility of admin notices added by the Freemius SDK[](#controlling-the-visibility-of-admin-notices-added-by-the-freemius-sdk "Direct link to Controlling the visibility of admin notices added by the Freemius SDK")
```
-theme`.
* @var string $plugin The product's title.
* @var string $wp_user_id An optional WP user ID that this admin notice is for.
* }
* @return bool
*/
function my_custom_show_admin_notice( $show, $msg ) {
if ('trial_promotion' == $msg['id']) {
// Don't show the trial promotional admin notice.
return false;
}
return $show;
}
my_fs()->add_filter( 'show_admin_notice', 'my_custom_show_admin_notice', 10, 2 );
```
## Merging different free and premium product versions into one with Freemius[](#merging-different-free-and-premium-product-versions-into-one-with-freemius "Direct link to Merging different free and premium product versions into one with Freemius")
```
is_premium() ) {
require_once dirname(__FILE__) . 'path/to/free/loader.php';
}
if ( my_fs()->is__premium only() ) {
if ( my_fs()->can_use_premium_code() ) {
require_once dirname(__FILE__) . 'path/to/premium/loader.php';
}
}
```
## Disable deactivation feedback form[](#disable-deactivation-feedback-form "Direct link to Disable deactivation feedback form")
```
add_filter( 'show_deactivation_feedback_form', '__return_false' );
```
## Disable after deactivation subscription cancellation window[](#disable-after-deactivation-subscription-cancellation-window "Direct link to Disable after deactivation subscription cancellation window")
warning
A subscription cancellation window will only show up when deactivating a product associated with a license that is only activated on the current site. Over the years, we learned that some users assume a product deactivation is also a cancelation of its subscription, which may cause an unexpected charge that can lead to a dispute. Therefore, we generally recommend keeping the default behavior as it reduces payment reversals and dispute fees.
```
add_filter( 'show_deactivation_subscription_cancellation', '__return_false' );
```
---
# Gutenberg Block Integration
Developing blocks for the Gutenberg editor is quite [straightforward](https://developer.wordpress.org/block-editor/tutorials/create-block/) these days as the project continues to mature; and there are numerous [tutorials](https://www.google.com/search?q=gutenberg+block+development\&oq=gutenberg+block+development\&aqs=chrome..69i57j69i60l3j0l2j0i22i30l2.5615j1j7\&sourceid=chrome\&ie=UTF-8) available to help create almost any type of block you want.
However, knowing how to *monetize* a block is not so immediately obvious and requires some approaches that you may not be familiar with. Using the techniques outlined in this guide will help you to overcome any potential issues, you might come across, during the integration process.
note
Note: It's assumed that you're already familiar with developing blocks for the Gutenberg editor. If not then we highly recommended working through the official [Gutenberg handbook](https://developer.wordpress.org/block-editor/) before attempting integration with the Freemius platform.
## Integrating a Block with Freemius[](#integrating-a-block-with-freemius "Direct link to Integrating a Block with Freemius")
There isn't just one single method available when developing Gutenberg blocks, and each one requires a different approach to complete Freemius integration successfully. We'll look at the common types of block in this section and how you can make them work properly with Freemius.
For **paid only** blocks, integration is quite simple as there's no free version at all. Which means there aren't any features that need removing during deployment. This type of block can be structured pretty much as you wish as long as the code to load premium scripts etc., are wrapped with the usual Freemius [licensing logic](https://freemius.com/help/help/documentation/wordpress-sdk/software-licensing/.md).
For **freemium** blocks, integration is usually more complicated as the paid features need separating out from the free code. If you've chosen to render block content via PHP then integration is also much easier as paid features can be wrapped with the same logic you're already used to.
e.g.
```
if( fs()->can_use_premium_code__premium_only() ) {
// paid logic here
}
```
Any paid block features written solely in JavaScript should be implemented via (JavaScript) [hooks](https://developer.wordpress.org/block-editor/packages/packages-hooks/) (see next section) so that they can be removed from the free plugin during [deployment to Freemius](https://freemius.com/help/help/documentation/release-management/deployment/.md).
To help understand how to effectively add paid features to a block written in JavaScript only, we've added a [new Gutenberg-integrated plugin](https://github.com/Freemius/hello-dolly/tree/master/freemium%20v.3/hello-dolly) to our [Hello Dolly Freemium](https://github.com/Freemius/hello-dolly) repository that contains a fully working block, which monetizes certain features.
Specifically, the free features included in the plugin are:
1. One song ('**Hello Dolly**') available.
2. Refresh the displayed lyric for the current song via block inspector controls.

And the **paid** features are:
1. Choose to display lyrics from three extra songs, depending on the current active plan ('**Summertime**', '**Wonderful World**', or '**Dream a Little Dream**').

2. Optionally show the line number of the current song lyric (available in any paid plan).

We'll use snippets of code from [this block plugin](https://github.com/Freemius/hello-dolly/tree/master/freemium%20v.3/hello-dolly) throughout this guide to demonstrate the most relevant techniques you can use when monetizing your own blocks.
## The Need for JavaScript Hooks[](#the-need-for-javascript-hooks "Direct link to The Need for JavaScript Hooks")
When you need to extend a block feature written in JavaScript then hooks are extremely useful and allow you to seamlessly add paid features.
They're available in WordPress core so just import them into your block code just like any other package:
```
const { applyFilters, doAction } = wp.hooks;
```
Usage is similar to PHP hooks, where you pass in a default value to a hook:
```
let some_option = applyFilters('some-option', [{ label: 'Some Option' }]);
```
Then the default filter value can be modified elsewhere in the codebase:
```
const { addFilter } = wp.hooks;
addFilter('some-option', 'my-plugin/some-option', function (options) {
// Add another options object to the array.
options.push({ label: 'Another Option' });
return options;
});
```
Normally, all block code (for one or more blocks) is compiled into a single file containing vanilla JavaScript. But when using hooks to implement paid features this isn't really useful as we need the paid features compiled to a *separate* file so it can be conditionally enqueued, and removed from the free version completely during deployment.
All you need to do is make sure your build process is set up to produce *separate* files for free and paid features.
In the Hello Dolly plugin the [@wordpress/scripts](https://developer.wordpress.org/block-editor/packages/packages-scripts/) package is used to compile the JavaScript code. To make sure code is compiled to separate files a couple of new scripts were added to `[package.json](https://github.com/Freemius/hello-dolly/blob/master/freemium%20v.3/hello-dolly/package.json)` for development and production builds:
```
"build:custom": "wp-scripts build src/blocks/song-lyrics/index.js src/pro/extend-blocks.js",
"start:custom": "wp-scripts start src/blocks/song-lyrics/index.js src/pro/extend-blocks.js",
```
## Localizing Freemius Data[](#localizing-freemius-data "Direct link to Localizing Freemius Data")
To enable/disable paid block features we need to access Freemius license, and plan data, via JavaScript.
This isn't directly possible at the moment, but in the future, we plan to provide a richer JavaScript SDK which will handle license-related logic in a similar manner to how our PHP SDK does it.
In the meantime though, we can [localize](https://developer.wordpress.org/reference/functions/wp_localize_script/) Freemius data and manually pass it to JavaScript via PHP.
Data can be localized as follows:
```
$licensing = array( 'can_use_premium_code' => my_fs()->can_use_premium_code() );
wp_localize_script('my-script', 'my_block_licensing_data', $licensing);
```
Add whatever data to the PHP array that you need to access via JavaScript. This localized data can then be used directly in your block code (as it's automatically available via a global JavaScript object).
```
const licensing = my_block_licensing_data ? my_block_licensing_data : null;
if (licensing.can_use_premium_code) {
// ... premium only logic ...
}
```
The major benefit of this method is that you can fully test paid features locally **before** deploying. Switching licenses that have different active plans will enable that functionality automatically. Or, deactivating the license will just revert the block to free features only.
Being able to test block features locally like this can really speed up development time!
## Monetizing JavaScript Rendered Blocks[](#monetizing-javascript-rendered-blocks "Direct link to Monetizing JavaScript Rendered Blocks")
Blocks written purely in JavaScript need extra considerations compared to PHP blocks. Any JavaScript feature can be monetized but the key is *how* it's implemented. Some features are easier to monetize than others because of the way JavaScript blocks are rendered.
If your block implements extra features via changes to the [`edit()`](https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#edit) function then you won't have any issues. You can go ahead and use JavaScript hooks to separate out paid features as shown in the previous section.
But if you're adding paid features that update the blocks [`save()`](https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#save) function directly as soon as the license is activated, then (when the block is loaded inside the post editor) you'll almost certainly see a block invalid content warning.

This dialog box does actually allow you to update the block to the new version that includes paid features just by clicking the **Attempt Block Recovery** button.
However, the UX is still pretty unfriendly. You wouldn't really want to show this to a user when updating block content directly as it looks like some unidentified error has occurred.
There are plans to [address](https://github.com/WordPress/gutenberg/issues/25436) this issue and improve the UX but the proposal has no definite time-frame for implementation. So, until then it's probably best to avoid triggering the block invalid content warning wherever possible.
### Managing Direct Changes to Block Markup[](#managing-direct-changes-to-block-markup "Direct link to Managing Direct Changes to Block Markup")
Fortunately there are other methods you can use to avoid the block invalidation warning.
For very small changes to the save content, that are enabled in paid plans, you can simply include the markup in the free version and disable UI controls to access/update them.
In the Hello Dolly Freemium plugin this method is used to show the line number of the lyric for the currently selected song:
```
const { applyFilters, doAction, createHooks } = wp.hooks;
const { Fragment } = wp.element;
export function Markup(props) {
const { song, lyric, lineNumber, showLineNumber } = props;
return (
{song}: {lyric}{' '}
{showLineNumber && (
({lineNumber})
)}
);
}
```
The `showLineNumber` attribute is set to `false` initially so that it doesn't display until you upgrade to a paid plan and enable it. And if the license is downgraded to the free version or is deactivated then `showLineNumber` is reset to `false`.
The inspector controls to modify `showLineNumber` status are only shown in the paid version and these are actively stripped out from the free version (see next section). It's important to stress that only the bare minimum content is included with the free version and there is no easy way it can be accidentally enabled.
This approach is fine for **very small** changes to the save function, but is not recommended for substantial changes. If you wish to directly modify the rendered save content significantly (e.g. for license status changes) then you'll need to use another method.
One way to do this is to use the blocks `componentDidMount()` lifecycle method (or React hook equivalent).
If Freemius license/plan data is localized then you could store the current license status and active plan in block attributes and use it to compare against the passed in data every time the editor (re)loads. Then, if the license status (or paid plan) has changed then you could then trigger a block update via `componentDidMount()`.
This would **bypass the block invalid content warning** and update the block content automatically to the new version. The only caveat with this method is that a manual post update is required to persist changes (as new block changes have been detected). If you leave the editor without saving changes then the block will be auto-updated the next time it's viewed in the editor.
To summarise here are the main choices you have to manage block rendering when the blocks save content has been directly modified:
2. For **very small** markup changes include the bare minimum markup in the blocks save function needed for the paid feature and set the relevant block attribute(s) to `false`. Strip out inspector controls from the free version via JavaScript hooks, and don't forget to manually remove the block source code before deploying to Freemius.
3. Modify the blocks save content via the `componentDidMount()` lifecycle method (or React hook equivalent). A manual post update is required to persist changes.
4. Use PHP to render the save function content. This is about as bullet-proof as you can get and is an ideal solution if you also need to provide a shortcode as well as a block (e.g. to support 3rd party page-builders). However, there are many benefits to coding your blocks in JavaScript with the most obvious being that block changes update in the editor in real-time opposed to a small delay when using PHP as it needs to render on the server.
5. Do nothing! Allow the block invalid content warning message to be displayed when the block's save content is directly updated and leave it to users to update (a manual post update is required with this method too). Until the UX is improved then it's not really recommended that you use this method as you'll probably see a spike in support requests (e.g. reporting the block content is broken etc.)!
### Managing Non-direct Changes to Block Markup[](#managing-non-direct-changes-to-block-markup "Direct link to Managing Non-direct Changes to Block Markup")
Blocks can also be monetized by adding options and controls to the block toolbar, or inspector panel. This involves making changes to the blocks `edit` function via JavaScript hooks. And because altering the `edit` function won't **ever** trigger the block invalid content warning this makes it a completely safe way to add paid block features!
In the [Hello Dolly Freemium](https://github.com/Freemius/hello-dolly/tree/master/freemium%20v.3/hello-dolly) block we've added three new songs that become available (depending on the current active plan) as soon as the license is activated. And whenever the license is deactivated the songs are no longer available.
To do this a filter was added for the songs available in the block inspector:
\[RadioControl.js]
```
let song_options = applyFilters('song-list', [
{ label: 'Hello Dolly', value: 'Hello Dolly' },
]);
```
We can then use the filter to extend the list of songs available:
\[extend-blocks.js]
```
addFilter('song-list', 'hello-dolly/song-lyrics', function (songs) {
if (!hello_dolly_editor_data) {
return 'Error, Freemius data not found!';
}
const { can_use_premium_code, plan } = hello_dolly_editor_data;
if (can_use_premium_code) {
if (
plan === 'summertime' ||
plan === 'wonderful_world' ||
plan === 'dream'
) {
songs.push({ label: 'Summertime', value: 'Summertime' });
}
if (plan === 'wonderful_world' || plan === 'dream') {
songs.push({ label: 'Wonderful World', value: 'Wonderful World' });
}
if (plan === 'dream') {
songs.push({
label: 'Dream a Little Dream',
value: 'Dream a Little Dream',
});
}
}
return songs;
});
```
This won't do anything apart from filter which radio buttons are available in the block inspector panel. We also need to actively filter the lyrics for each song so that they're available in the paid plans.
First, add the filter as we did before:
[\[lyrics.js\]](https://github.com/Freemius/hello-dolly/blob/master/freemium%20v.3/hello-dolly/src/blocks/song-lyrics/lyrics.js#L35)
```
lyrics = applyFilters('song-lyrics', lyrics);
```
And then extend the song lyrics too, similar to how we did before:
[\[extend-blocks.js\]](https://github.com/Freemius/hello-dolly/blob/master/freemium%20v.3/hello-dolly/src/pro/extend-blocks.js#L35-L133)
```
addFilter('song-lyrics', 'hello-dolly/song-lyrics', function (lyrics) {
if (!hello_dolly_editor_data) {
return 'Error, Freemius data not found!';
}
const { can_use_premium_code, plan } = hello_dolly_editor_data;
if (can_use_premium_code) {
if (
plan === 'summertime' ||
plan === 'wonderful_world' ||
plan === 'dream'
) {
lyrics['Summertime'] = `Summertime and the livin' is easy
...
Ah, said it's summertime`;
}
if (plan === 'wonderful_world' || plan === 'dream') {
lyrics['Wonderful World'] = `I see trees of green, red roses, too,
...
What a wonderful world`;
}
if (plan === 'dream') {
lyrics['Dream a Little Dream'] = `Stars shining bright above you
...
Dream a little dream of me`;
}
}
return lyrics;
});
```
This works really well and all our paid features are completely stripped from the plugin during deployment.
You might have noticed that the JavaScript logic to check if the current active plan is valid is a little verbose:
```
if (plan === 'summertime' || plan === 'wonderful_world' || plan === 'dream') {
// add lyrics
}
```
Compared to it's PHP counterpart:
```
if ( hd_fs()->is_plan( 'summertime' ) ) {
// add lyrics
}
```
This is because we're localizing *just* the current active plan name and so more manual checks are required in the JavaScript code.
We could easily match the PHP logic by passing in more localized data to include specific plan data:
```
$data = array(
'current_plan' => hd_fs()->get_plan_name(),
'can_use_premium_code' => hd_fs()->can_use_premium_code(),
'is_plan_summertime' => hd_fs()->is_plan( 'summertime' ),
'is_plan_wonderful_world' => hd_fs()->is_plan( 'wonderful_world' ),
'is_plan_dream' => hd_fs()->is_plan( 'dream' ),
);
wp_localize_script('hello-dolly-block-script', 'hello_dolly_editor_data', $data);
```
And then changed the paid feature code to:
```
addFilter('song-lyrics', 'hello-dolly/song-lyrics', function (lyrics) {
if (!hello_dolly_editor_data) {
return 'Error, Freemius data not found!';
}
const {
can_use_premium_code,
is_plan_summertime,
is_plan_wonderful_world,
is_plan_dream,
} = hello_dolly_editor_data;
if (can_use_premium_code) {
if (is_plan_summertime) {
lyrics['Summertime'] = `Summertime and the livin' is easy
...
Ah, said it's summertime`;
}
if (is_plan_wonderful_world) {
lyrics['Wonderful World'] = `I see trees of green, red roses, too,
...
What a wonderful world`;
}
if (is_plan_dream) {
lyrics['Dream a Little Dream'] = `Stars shining bright above you
...
Dream a little dream of me`;
}
}
});
```
The system is quite flexible. You can choose which approach suits you best, you're not restricted to any particular implementation.
## Downgrading Issues[](#downgrading-issues "Direct link to Downgrading Issues")
Sometimes when downgrading from the paid version to free you need to be aware of scenarios that could cause issues and unexpected behaviour in your blocks.
For example, in our Hello Dolly Freemium plugin if the currently selected song is only available in the paid version then it will suddenly disappear if the license is deactivated.
In this case we need to specifically reset the song and lyrics block attributes to the free one (Hello Dolly). Notice we're also resetting `lineNumber` as we can't guarantee the current line number will exist in Hello Dolly, and `showLineNumber` is reset too as this is a paid only feature.
```
const {
attributes: { song, lyric },
setAttributes,
} = this.props;
// if downgrading and the currently selected song is not available
if (!lyrics.hasOwnProperty(song)) {
setAttributes({
song: 'Hello Dolly',
lyric: lyrics['Hello Dolly'].split(/\n/)[0],
lineNumber: 1,
showLineNumber: false,
});
}
```
## Deployment to Freemius[](#deployment-to-freemius "Direct link to Deployment to Freemius")
Before deploying the plugin to Freemius make sure that you run the build script to produce compiled and minified block code.
Paid block features can be stripped out during deployment using the `@fs_premium_only` Freemius directive.
e.g. `@fs_premium_only /build/extend-blocks.js`
It's also recommended that you keep all your block source code in a single folder so that it can be easily removed prior to deployment if required.
## Testing the Hello Dolly Freemium Plugin[](#testing-the-hello-dolly-freemium-plugin "Direct link to Testing the Hello Dolly Freemium Plugin")
The [Hello Dolly Freemium](https://github.com/Freemius/hello-dolly/tree/master/freemium%20v.3/hello-dolly) plugin contains a fully working example of a block integrated with the Freemius platform.
To install locally, clone the plugin repository: `git clone https://github.com/Freemius/hello-dolly.git`
The plugin uses the WordPress SDK as a submodule so you'll need to pull it after cloning using the following command: `git submodule update --init`
Then, copy the `freemium v.3\hello-dolly` folder to your local WordPress plugins folder (`/wp-content/plugins/`) and activate.
Upon activation we'll be prompted with a license activation screen. Since it's a Freemium plugin, you can click the *Activation Free Version* link to continue, and then choose whether to opt-in to Freemius usage-tracking or not.
To test the paid plans you can use the following license keys:
* *Summertime* plan license key: `sk_cOG+nk&tsEYLVo74~W*SqwJcLq4;V`
* *Wonderful World* plan license key: `sk_4#:r099AfRm8m*QUgN&K%s6Ds_Si5`
* *Dream* plan license key: `sk_6&Db*Nn#IVZ2oj7qh=ZHEkZv5H3a6`
If you already skipped the license activation prompt, a license can be activated from the plugins page in the WP Admin, by clicking the *Activate License* link under the **Hello Dolly Freemium** plugin.
To create and configure your own Hello Dolly Freemium plugin using Freemius from scratch, follow [this video tutorial](https://www.youtube.com/watch?v=gj6aoYG4fUI\&t=101s) that takes you through the entire process step-by-step.
If you plan on making changes to blocks then use either of these two commands to recompile the block source code:
* Development: `npm run start:custom`
* Production: `npm run build:custom`
---
# Integration & Configuration
Integrating the Freemius SDK into your product is a quick step you need to complete in order to be able to take advantage of all the benefits that come with using Freemius. After completing these instructions your product will be all set up and ready to sell.
**Here’s how to do it:**
After logging in to your Freemius account and clicking on the **'Add product / bundle'** button at the top left hand corner you will be able to provide some basic settings for your product, including Slug and Title:

Upon hitting the **‘Create New’** button, you are then redirected to the **5-minute integration guide**.
The next step is to download the ZIP file that contains the Freemius SDK and all of the Freemius magic that comes with it. You can download the latest release from our [GitHub repository](https://github.com/Freemius/wordpress-sdk), unzip it, rename it to ‘freemius’.
Move the renamed `freemius` folder as-is in to the root folder of your WordPress theme or plugin.

With the Freemius SDK folder included in your product, you can complete the quick customization process according to the instructions on the [SDK Integration](https://dashboard.freemius.com) page of the Developer Dashboard.
When you’re done, your customized ‘easy SDK access’ function will be auto-generated for you.
note
This snippet of code initializes the Freemius SDK using an array of key/value pairs that contains specific information about your Freemius product and unlocks the full range of features available. Access to the Freemius SDK features comes via a global variable defined as part of the snippet.
For **WordPress plugins**, copy & paste it into the top of your main plugin’s PHP file, right after the plugin’s header comment and `if ( ! defined( 'ABSPATH' ) ) exit;` check.
For **WordPress themes**, copy & paste it into the top of your theme’s `functions.php` file.
Here’s how the code should look:

Under normal circumstances, we don't recommend editing the snippet directly once it's added to your WordPress product. However, there are times when you may need to tweak it manually. We'll cover when you might want to do this in a [later section](#manually-editing-the-sdk-integration-snippet).
However, in all cases, you should begin by copying the snippet from the SDK Integration page of the Developer Dashboard and pasting it into your plugin or theme. Don't be tempted to try and create the snippet from scratch as this method is prone to errors. It's always best to start with the auto-generated snippet and tweak it when necessary.
## Where to Add the SDK Integration Snippet[](#where-to-add-the-sdk-integration-snippet "Direct link to Where to Add the SDK Integration Snippet")
Depending on the structure of your plugin you may wish to add the SDK snippet to another location than your main plugin file. For themes, the SDK relies on the snippet to be placed in `functions.php`, if you place it elsewhere you are risking unexpected behavior - so please don’t do it.
Regardless of where it’s added, it's recommended that initialization of the Freemius SDK should be completed as soon as possible and preferably before any other code is executed inside your WordPress product.
This will help to minimize issues with trying to access SDK features before they are initialized. Also, SDK initialization should not occur **after** the WordPress `'plugins_loaded'` hook. This is incorrect and will break some of the SDK's logic due to the execution order of other logic contained in WordPress core.
If you experience problems when placing the SDK snippet in a file alternative location than the recommended ones, then try adding it back in the main plugin file, to rule out any implementation errors.
## Snippet Settings Reference[](#snippet-settings-reference "Direct link to Snippet Settings Reference")
The array that's passed to `fs_dynamic_init()` consists of key-value pairs, including nested arrays for some settings. All possible values are covered as a reference guide, but not all will necessarily be relevant to your particular plugin or theme.
Specific settings will be generated depending on the information entered in the Developer Dashboard. Don't worry if your SDK snippet doesn't include all of the settings. This is perfectly normal.
All settings are listed below along with a description.
note
All settings apply to both plugins and themes unless otherwise stated.
`id`[](#id "Direct link to id")
number
Example: 1234
Auto-generated Freemius product ID which uniquely identifies your plugin or theme. This is created when the product is first created and cannot be changed.
`slug`[](#slug "Direct link to slug")
string
Example: 'my-plugin'
Used to create the product folder name during deployment and in the updates mechanism. Also used in dynamically generated URLs (e.g. EULA).
`premium_slug`[](#premium_slug "Direct link to premium_slug")
string
Example: 'my-plugin-pro'
Used for freemium products to create the premium product’s version folder name during deployment. This needs to be different from the free version slug. Also used in dynamically generated URLs (e.g. EULA).
`type`[](#type "Direct link to type")
string
Default: `'plugin'`
Allowed Values: `'plugin'` | `'theme'`
Type of WordPress product.
`public_key`[](#public_key "Direct link to public_key")
string
Example: 'pk\_n56hrjss36joj632vgyy345ggv555'
Public key is a unique key prefixed with 'pk\_' specific to the Freemium product. This is typically used for authentication with public Freemius API endpoints.
`is_premium`[](#is_premium "Direct link to is_premium")
boolean
Specify if the product’s codebase is the free or premium version.
`premium_suffix`[](#premium_suffix "Direct link to premium_suffix")
string
Example: 'Pro'
Default: `'plugin'`
The suffix added to a premium plugin’s paid version title. This option is relevant for freemium products.
`is_premium_only`[](#is_premium_only "Direct link to is_premium_only")
boolean
Default: `false`
Specify if the plugin or theme is premium-only (i.e. no free version). This setting is added automatically if the default free plan is deleted and there's at least one paid plan.
`has_premium_version`[](#has_premium_version "Direct link to has_premium_version")
boolean
Default: `false`
Does the plugin or theme have a premium version available?
`has_addons`[](#has_addons "Direct link to has_addons")
boolean
Default: `false`
Does the plugin or theme have any associated addons?
`has_paid_plans`[](#has_paid_plans "Direct link to has_paid_plans")
boolean
Default: `false`
Does the plugin or theme have any paid plans?
`is_org_compliant`[](#is_org_compliant "Direct link to is_org_compliant")
boolean
Default: `true`
Specifies whether the free version of the product is WordPress.org compliant.
warning
Please leave the flag as it is. When submitting your product to WordPress.org, this flag is required for the product to be accepted into the repository.
`has_affiliation`[](#has_affiliation "Direct link to has_affiliation")
boolean|string
Default: `false`
Allowed Values: `'selected'` | `'customers'` | `'users'`
This setting is included if the affiliate program for the product is enabled (via the Developer Dashboard [Affiliation page](https://freemius.com/help/help/documentation/affiliate-platform/affiliate-program-activation/.md)).
Its value is determined by the "Who can be an affiliate?" setting. When `customers` is selected, it means the seller only allows customers to become affiliates so the admin notice and menu item will only show up for customers. From the SDK's point of view, `users` and `selected` are identical, since only users will be exposed to the settings of the product in the WordPress admin.
`menu`[](#menu "Direct link to menu")
array
An array that denotes which Freemius pages to display in the WordPress admin menu, and how the plugin settings page menu is handled. See [how to configure for the menu array](#configuration-for-menu-array).
`opt_in_moderation`[](#opt_in_moderation "Direct link to opt_in_moderation")
array
An associative array containing configuration for opt-in moderation of your product. [More on this later](#configuration_for_opt_in_moderation_array).
`secret_key`[](#secret_key "Direct link to secret_key")
string
Example: 'sk\_84gbhR634gb732vy796h4gTY43'
Default: `'plugin'`
Secret key is a unique key prefixed with 'sk\_' specific to the Freemium product. When present it enables sandbox mode for development & testing. It also elevates additional permissions that are helpful for development and testing modes.
note
Note: The secret key is automatically included in the SDK snippet for convenience. As per the comments in the snippet, this should be removed from production code, but don't worry if you forget as the PHP processor removes it automatically in the Deployment mechanism
### Configuration for `'menu'` array[](#configuration-for-menu-array "Direct link to configuration-for-menu-array")
Possible settings for the `'menu'` array are:
`slug`[](#slug "Direct link to slug")
string
Example: 'my-plugin-menu'
WordPress admin menu slug used for the plugin settings page.
`parent`[](#parent "Direct link to parent")
string
Example: array('slug' => 'options-general.php')
The parent menu slug to use if the plugin settings page is a submenu of another menu. Even though this setting expects an array the `slug` field is the only option currently available.
`first-path`[](#first-path "Direct link to first-path")
string
Example: 'admin.php?page=my-plugin-menu'
This is where the plugin, or theme, will redirect to after activation.
`contact`[](#contact "Direct link to contact")
boolean
Default: `true`
Display the Freemius **Contact Us** page in the WordPress admin menu for the plugin or theme.
`support`[](#support "Direct link to support")
boolean
Default: `true`
Display the Freemius **Support Forum** page in the WordPress admin menu for the plugin or theme.
`addons`[](#addons "Direct link to addons")
boolean
Default: `true`
Display the Freemius **Add Ons** page in the WordPress admin menu for the plugin or theme.
`affiliation`[](#affiliation "Direct link to affiliation")
boolean
Default: `false`
Display the Freemius **Affiliation** page in the WordPress admin menu for the plugin or theme.
`account`[](#account "Direct link to account")
boolean
Default: `true`
If you set this to `false` then the SDK will not include any links to the **Account** page. If you are selling with Freemius the **Account** page is essential, therefore, make sure to manually link to that page from your settings interface using `my_fs()->get_account_url()`.
### Configuration for `'opt_in_moderation'` array[](#configuration-for-opt_in_moderation-array "Direct link to configuration-for-opt_in_moderation-array")
The moderation configuration allows you to control how the Freemius WordPress SDK is activated for your customers. This is particularly useful when integrating the SDK into an existing product with active users, and you prefer not to display the Opt-In screen to all users at once. In such cases, we recommend performing a phased rollout of the integration to identify and resolve any issues before deploying it widely to your user base. Ideally,
1. You control what percentages of new installations will see the [Opt-In screen](https://freemius.com/help/help/documentation/wordpress-sdk/opt-in-message/.md).
2. You can also control what percentage of updated installations will see it.
This can be configured with the following parameters:
```
'opt_in_moderation' => array(
'new' => 100,
'updates' => 0,
'localhost' => true,
),
```
`new`[](#new "Direct link to new")
int|boolean
Default: `true (as 100%)`
An integer from `0` to `100` to control the exposure percentage of the opt-in to new product installs. It also supports a boolean value. When set to `true`, then assume 100% exposure; if set to `false`, then no exposure at all.
`updates`[](#updates "Direct link to updates")
int|boolean
Default: `true (as 100%)`
This is the same as the `'new'` parameter, but controls the opt-in exposure for product updates (i.e., what % of websites will see the opt-in after upgrading to a new version with the Freemius SDK, when the previous version didn’t include the SDK).
`localhost`[](#localhost "Direct link to localhost")
boolean
Default: `true`
Whether to show the opt-in on localhost sites.
note
Note: This setting was introduced in [version 2.5.4 of the SDK](https://github.com/Freemius/wordpress-sdk/releases/tag/2.5.4). With the removal of the connectivity test, the moderation capabilities have been transitioned to the SDK settings. You can read more about it in our new [release note](https://freemius.com/blog/release-notes-wordpress-sdk-connectivity-test-stopped).
## Manually Editing the SDK Integration Snippet[](#manually-editing-the-sdk-integration-snippet "Direct link to Manually Editing the SDK Integration Snippet")
There are certain fields that aren't updated or included by default in the auto-generated SDK snippet. Therefore, if you need to support a certain feature exposed by this type of field it will need to be added manually. This is best done after the snippet has been inserted into your WordPress product.
It's not usually recommended to edit any fields directly from the auto-generated snippet. Instead, the snippet should be updated by changing the settings for your plugin or theme in the Developer Dashboard.
Here's a list of the fields that can be manually inserted/edited:
`navigation`[](#navigation "Direct link to navigation")
string
Allowed Values: `'menu'` | `'tabs'`
Enable [tabbed layout](https://freemius.com/help/help/documentation/wordpress-sdk/tabs-navigation/.md) support for Freemius pages rather than display them as menu items. This is a very useful option to reduce clutter in your plugin’s admin menu structure.
`pricing`[](#pricing "Direct link to pricing")
boolean
Default: `true`
Turn off all upgrade/pricing links except the account page. To enable this option it needs to be added to the `'menu'` array which is automatically added to the snippet. If, for some reason, this doesn't exist, then you can add it manually and create a single entry for `'pricing'` as follows: `'menu' => array( 'pricing' => false,),`.
`has_premium_version`[](#has_premium_version "Direct link to has_premium_version")
boolean
If your product is a Serviceware (a SaaS wrapped into a plugin or theme), set this option to false. This is an example of an option that is initially set in the auto-generated snippet but can be updated manually.
`bundle_id`[](#bundle_id "Direct link to bundle_id")
number
Example: 1234
Auto-generated Freemius ID which uniquely identifies a specific bundle.
`bundle_public_key`[](#bundle_public_key "Direct link to bundle_public_key")
string
Example: 'pk\_hrjss36joj632vgyy345ggv555'
Bundle key is a unique key, and is specific to the Freemium bundle.
`bundle_license_auto_activation`[](#bundle_license_auto_activation "Direct link to bundle_license_auto_activation")
boolean
Default: `false`
When set to `true`, an activation of a bundle license key will automatically activate the license key for all the other bundle's paid products installed on the site that are not yet associated with a license key.
note
The two bundle settings are required for products that are also sold as part of a bundle, when you want to sell the bundle via the WordPress admin instead of just the paid version of that product.
**Example:** For a freemium plugin with 10 paid add-ons, and you wish to sell a bundle of the plugin's paid version and 5 of the paid add-ons.
Without setting the bundle params in the SDK snippet, the in-dashboard pricing and checkout will be for the paid version of the plugin. On the other hand, when the params are set, the pricing page will show the prices of the bundle in context and that's what the user will purchase if they go through the checkout.
`permissions`[](#permissions "Direct link to permissions")
array\
Default: `false`
This is a key/value array where the key is a string (the permission name), and the value a boolean variable - which determines whether that permission is requested or not. Currently the only supported key is `'newsletter'` and will explicitly ask a permission for "Updates, announcements, marketing, no spam".
note
Additional permissions may be available in the future to make it easy for you to pick and choose what permission their product needs on opt-in for. It's also possible for you to introduce your own [custom permissions](https://freemius.com/help/help/documentation/wordpress-sdk/opt-in-message/.md) via the `permission_list` Freemius filter.
`enable_anonymous`[](#enable_anonymous "Direct link to enable_anonymous")
boolean
If your plugin is a Serviceware and cannot be used before creating an account, you can set this option to `true` and it will require users to opt-in after activation by removing the option to skip the opt-in.
`anonymous_mode`[](#anonymous_mode "Direct link to anonymous_mode")
boolean
When set to `true` it will automatically simulate a skip of an opt-in. This flag can be useful if you don’t want to ask users to opt-in to your free version or want to create your own custom opt-in interface, yet still utilize the power of the Freemius WordPress SDK (e.g. deactivation feedback form, in-dashboard checkout, etc.).
We highly recommend to use the SDK’s high-performing and multi-site network ready default opt-in, except if your product comes with an onboarding wizard and you’d want to custom add the opt-in UI and decision as part of that wizard.
To reset the “anonymous mode”, you can programmatically call `my_fs()->connect_again()`, which will also automatically redirect to the opt-in screen after resetting the flag. You can programmatically trigger a skip using `my_fs()->skip_connection()` or an opt-in consent using `my_fs()->opt_in()`.
`parallel_activation`[](#parallel_activation "Direct link to parallel_activation")
array
Use this to support parallel activation of both the free and premium versions of a WordPress plugin. Read more about it [below](#parallel-activation-of-free-and-premium-versions)
### Parallel activation of Free and Premium versions[](#parallel-activation-of-free-and-premium-versions "Direct link to Parallel activation of Free and Premium versions")
By default, our SDK automatically deactivates the free version of a plugin when the premium version is activated. However, you can modify this behavior using the following configuration:
```
'parallel_activation' => array(
'enabled' => true,
'premium_version_basename' => 'premium-slug/file-name.php',
),
```
With this configuration, the free version will remain active when the premium version is activated. Additionally, the SDK will display the license activation page if the free version has not yet been activated with a license.
#### Benefits of Parallel Activation[](#benefits-of-parallel-activation "Direct link to Benefits of Parallel Activation")
Parallel activation allows the “Active installs” counter on WordPress.org to include sites using the paid version without deactivating the free version. This is only useful if you distribute the free version on WordPress.org. If you don’t, this feature won’t provide you any benefits.
#### Drawbacks of Parallel Activation[](#drawbacks-of-parallel-activation "Direct link to Drawbacks of Parallel Activation")
This setup can create the perception that users need to manage updates for both the free and paid versions, leading to confusion about which version should be updated first. Additionally, there’s a risk of breaking functionality if major, non-backward compatible changes are made to one version. For this reason, we generally discourage parallel activation from a user experience (UX) perspective. Furthermore, since the typical conversion rate from free to paid is around 2%, the impact on the active installs counter is minimal.
note
While some of these manual settings may eventually be supported in the auto-generated snippet, for now, they must be added manually if needed.
## Updating the SDK Integration Snippet[](#updating-the-sdk-integration-snippet "Direct link to Updating the SDK Integration Snippet")
Whenever you make changes to your plugin or theme settings in the Developer Dashboard, this may trigger an update to the auto-generated SDK Integration snippet. It's important that whenever this is updated that you manually copy/paste it into your plugin or theme so that your product is always up to date with the latest version.
If the SDK integration snippet (minus any manual changes) doesn't match the Developer Dashboard version, then you could run into problems, so it's best to always keep it up-to-date and in sync when changes are made in the Developer Dashboard that affect the snippet.
The main reason why we don't recommend manually editing the SDK integration snippet (unless you have to) is because every time the snippet is updated inside your plugin or theme, you'll have to re-apply any manual changes you made previously. This can be tedious and prone to errors.
One final thing to remember is that if you have your SDK integration snippet in an alternative location (i.e. other than the main plugin file) then you'll also need to update the path reference to the Freemius SDK `start.php` file every time the snippet is updated.
## Activate Opt-ins[](#activate-opt-ins "Direct link to Activate Opt-ins")
Once you've completed the SDK integration, you can go back to the WordPress admin and activate & opt-in to your product (plugin or theme). If everything was done correctly, you should be able to see the data flowing into your Freemius dashboard in real time.

---
# Known License Activation Issues
In some cases, a license activation can fail unexpectedly without showing any errors.
Typical symptoms:
* The license activation page refreshes without anything happening.
* The *Activate* button gets "stuck," and nothing happens.
* The license remains activated on a site that is no longer accessible, and the activations limit is reached.
Here are the most common cases in which this happens, including how to work around the issues:
## 1. License Activations Limit Reached[](#1-license-activations-limit-reached "Direct link to 1. License Activations Limit Reached")
The issue typically arises when a website instance is deleted or becomes inaccessible without the license being deactivated through the standard process, or when the license is already activated on another site.
### Solution[](#solution "Direct link to Solution")
#### Have WordPress Site Access[](#have-wordpress-site-access "Direct link to Have WordPress Site Access")
If you have access to the WordPress site’s `Account` page for the relevant plugin/theme, navigate to it and click the `Deactivate License` link. After that, you can activate the license on another site.

Give Your Customers More Control
Additionally, you can ask your customers to restrict the sites that have access to the license by whitelisting those from the self-service [Customer Portal](https://freemius.com/help/help/documentation/users-account-management/license-security/.md#url-whitelisting-aka-license-firewall).
#### No WordPress Site Access (Single-Site License)[](#no-wordpress-site-access-single-site-license "Direct link to No WordPress Site Access (Single-Site License)")
If you no longer have access to the WordPress site and your license is for a single site, follow these steps:
* Log in to the [Customer Portal](https://customers.freemius.com/).
* Navigate to the `Licenses` section (beneath the `Websites` menu item).
* Click the `Deactivate` button.

#### No WordPress Site Access (Multi-Site License)[](#no-wordpress-site-access-multi-site-license "Direct link to No WordPress Site Access (Multi-Site License)")
If you no longer have access to the WordPress site and your license supports multiple site activations, follow these steps:
* Log in to the [Customer Portal](https://customers.freemius.com/).
* Navigate to the `Websites` section.
* Find the relevant site (you can check the domain).
* Click on it to open its details panel.
* Scroll down to find the license section and click the `Deactivate` button.

## 2. ISP Blockage[](#2-isp-blockage "Direct link to 2. ISP Blockage")
We noticed that some ISPs, especially in Turkey and Russia, are often blocking websites that utilize Cloudflare, which can block connectivity to our API servers.
### Solution[](#solution-1 "Direct link to Solution")
You can activate the license using a proxy by following these simple steps:
1. Go to [us-proxy.org](https://www.us-proxy.org/)
2. Get the 1st proxy details available and add the following code to the `functions.php` of the currently active theme:
```
add_action('http_api_curl', function( $handle ){
curl_setopt($handle, CURLOPT_PROXY, "REPLACE_ME_WITH_PROXY_IP");
curl_setopt($handle, CURLOPT_PROXYPORT, REPLACE_ME_WITH_PROXY_PORT);
}, 10);
```
3. After a successful license-activation, comment out the newly added lines of code to avoid running future cURL requests through a proxy.
## 3. Caching Layer Issue[](#3-caching-layer-issue "Direct link to 3. Caching Layer Issue")
For performance reasons, many in-memory caching solutions like Memcached or Redis require setting a Byte-limit to the values they store, and to optimize performance, all Freemius settings are stored in a single record in the `wp_options` table, which means that if the length of the settings is greater than the configured limit, it may cause one of the following unexpected behaviors:
* The settings won't be stored at all
* The settings will fail to properly load
* The settings will be stored incompletely, causing data corruption
This will prevent the settings from properly propagating to (or from) the Database.
### Solution[](#solution-2 "Direct link to Solution")
Try to temporarily disable the caching layer just for the license activation process.
## 4. Database Encoding Issue[](#4-database-encoding-issue "Direct link to 4. Database Encoding Issue")
In some cases, we noticed that misconfigured encoding of the `option_value` column in the `wp_options` table could fail to store the required data after license activation. E.g. - Even though the license gets properly activated, WordPress may not “remember” it. Typically this happens when there are any UTF-8 or UTF-16 characters in the license owner's name or address.
### Solution[](#solution-3 "Direct link to Solution")
Try changing the encoding of the `option_value` column in the `wp_options` table to UTF-8 (or UTF-16) and then try activating the license again.
## 5. Security Layer Blockage[](#5-security-layer-blockage "Direct link to 5. Security Layer Blockage")
Freemius’ license key generator randomly creates 32-char keys using alphanumeric characters and some special characters for enhanced security. We noticed that some security plugins/layers/modules (e.g., ModSecurity) might block a license activation process if the combination of the license’s special characters “look” suspicious.
### Solution[](#solution-4 "Direct link to Solution")
Try to temporarily disable any security plugins/layers/modules you have just for the license activation process. After the license is activated, make sure to reactivate the security components you disabled.
---
# Opt-in Screen
The Freemius SDK comes with a default opt-in screen that has gone through many improvement iterations to maximize clarity and optimize conversion rate while adhering to the standard WP Admin UI styles.
As every plugin/theme is unique, you may want to customize the opt-in screen to better match your product's style/tone/brand and audience. You can leverage one or more of the opt-in hooks [actions & filters](https://developer.wordpress.org/plugins/hooks/) to achieve that.
## Default Opt-In Messages[](#default-opt-in-messages "Direct link to Default Opt-In Messages")
The opt-in message consists of a header and a message and varies according to the state of the admin.
Here’s the default opt-in message that **new users** that activate your product will see:

> **Never miss an important update** Opt in to get email notifications for security & feature updates, and to share some basic WordPress environment info. This will help us make the `{product_type}` more compatible with your site and better at doing what you need it to.
The default opt-in message that your **existing users** (people who have already had your product activated, prior to updating to Freemius-integrated version) will see is:

> **Thank you for updating to Awesome Plugin v`{product_version}`!** We have introduced this opt-in so you never miss an important update and help us make the `{product_type}` more compatible with your site and better at doing what you need it to. Opt in to get email notifications for security & feature updates, and to share some basic WordPress environment info. If you skip this, that's okay! **`{product_title}`** will still work just fine.
## Opt-In Message Customization[](#opt-in-message-customization "Direct link to Opt-In Message Customization")
To customize either of these default messages - simply use the following filters we’ve created:
* `connect-header_on-update` and `connect_message_on_update` to override the default message **existing users** will see
* `connect-header` and `connect_message` to override the default message **new users** will see
Here’s an example of how that can be done:
```
function my_fs_custom_connect_header_on_update( $header_html ) {
$user = wp_get_current_user();
return sprintf( __( 'Thanks %s for updating to our latest release!', 'my-text-domain' ), $user->user_firstname );
}
my_fs()->add_filter( 'connect_header_on_update', 'my_fs_custom_connect_header_on_update' );
function my_fs_custom_connect_message_on_update( $message, $user_first_name, $product_title, $user_login, $site_link, $freemius_link ) {
return sprintf( __( 'Please help us improve %2$s! If you opt-in, some data about your usage of %2$s will be sent to %5$s. If you skip this, that\'s okay! %2$s will still work just fine.', 'my-text-domain' ), $user_first_name, '' . $product_title . '', '' . $user_login . '', $site_link, $freemius_link );
}
my_fs()->add_filter( 'connect_message_on_update', 'my_fs_custom_connect_message_on_update', 10, 6 );
```
## Opt-In Icon Customization[](#opt-in-icon-customization "Direct link to Opt-In Icon Customization")
When you are running your plugin or theme on a localhost environment, if your product is hosted on WordPress.org, the SDK will automatically attempt to download the featured profile icon from WordPress.org and store it in `/freemius/assets/img/{slug}.{png|jpg|gif|svg}`. If it works, all you need is to deploy the zip with the downloaded file and you are good.
If you don't have a free product version on WordPress.org or would like to load an alternative icon, the best way is to use the `plugin_icon` filter:
```
function my_fs_custom_icon() {
return dirname( __FILE__ ) . '/local/path/to/your/icon.{png|jpg|gif|svg}';
}
my_fs()->add_filter( 'plugin_icon', 'my_fs_custom_icon' );
```
## Opt-In Button Labels Customization[](#opt-in-button-labels-customization "Direct link to Opt-In Button Labels Customization")
You can easily customize the button labels by using Freemius i18n override function. Example:
```
if ( function_exists( 'fs_override_i18n' ) ) {
fs_override_i18n( array(
'opt-in-connect' => __( 'Ok - I am in!', 'your-text-domain' ),
'skip' => __( 'Maybe later', 'your-text-domain' ),
), 'your-slug' );
}
```
This will set the primary opt-in button label to **Ok - I am in!** and replace the default skip label with **Maybe Later**.
## Opt-In Permissions Customization[](#opt-in-permissions-customization "Direct link to Opt-In Permissions Customization")
In addition to the message, the opt-in screen lists in detail all the permissions that will be granted to the product upon an opt-in.

If you'd like to track additional events/data or require additional permissions that are relevant to your product, you can leverage the `permission_list` filter to add custom permissions.
Here's an example that shows how to add custom *Newsletter* permission:
```
function add_helpscount_permission( $permissions ) {
$permissions['helpscout'] = array(
'icon-class' => 'dashicons ',
'label' => my_fs()->get_text_inline( 'Help Scout', 'helpscout' ),
'desc' => my_fs()->get_text_inline( 'Rendering Help Scout\'s beacon for easy support access', 'permissions-helpscout' ),
'priority' => 16,
);
return $permissions;
}
my_fs()->add_filter( 'permission_list', 'add_helpscount_permission' );
```
The `'priority'` parameter determines the visual order of the permission on the permissions list. The default permissions priorities are: `5`, `10`, `13`, and `20`. So when using `15`, the SDK will add the permission just before the last one (between permission `13` and `20`).
## License Activation Screen[](#license-activation-screen "Direct link to License Activation Screen")
While the default opt-in screen is shown for the free version of your product, the SDK displays a license activation screen for the premium version. This screen is very similar to the opt-in screen, with the key difference being the inclusion of a license key field and an "Activate License" button.

The license activation screen supports the same customization options as the opt-in screen, allowing you to use the same filters. It also shares the same template files.
## Advanced Opt-In Customization Actions[](#advanced-opt-in-customization-actions "Direct link to Advanced Opt-In Customization Actions")
The SDK now supports a collection of advanced actions to allow you to inject text and visual components at various stages of screen rendering. All actions receive a single associative array argument with the opt-in state (check the example below).
### `connect/before`[](#connectbefore "Direct link to connectbefore")
For adding text or visual elements before/above the opt-in container.
```
function add_custom_header( $activation_state ) {
if ( $activation_state[ 'is_license_activation' ] ) {
// The opt-in is rendered as license activation.
}
if ( $activation_state[ 'is_pending_activation' ] ) {
// The user clicked the opt-in and now pending email confirmation.
}
if ( $activation_state[ 'is_network_level_activation' ] ) {
// A network-level opt-in after network activation of the plugin (only applicable for plugins).
}
if ( $activation_state[ 'is_dialog' ] ) {
// The opt-in is rendered within a modal dialog (only applicable for themes).
}
return '
Awesome Plugin
';
}
my_fs()->add_filter( 'connect/before', 'add_custom_header' );
```
### `connect/after`[](#connectafter "Direct link to connectafter")
For adding text or visual elements after/below the opt-in container.
### `connect/before_message`[](#connectbefore_message "Direct link to connectbefore_message")
For adding text or visual elements before/above the opt-in message header (within the opt-in container).
### `connect/after_message`[](#connectafter_message "Direct link to connectafter_message")
For adding text or visual elements after/below the opt-in message (within the opt-in container).
### `connect/before_actions`[](#connectbefore_actions "Direct link to connectbefore_actions")
For adding text or visual elements before/above the opt-in action buttons.
### `connect/after_actions`[](#connectafter_actions "Direct link to connectafter_actions")
For adding text or visual elements after/below the opt-in action buttons.
## Additional Customization[](#additional-customization "Direct link to Additional Customization")
If the customization options above are still insufficient for your goals, you can leverage the `templates/connect.php` filter, which enables you to filter the HTML of the opt-in screen.
If you want to take the customization further, the opt-in template is located under `/freemius/templates/connect.php`. The WordPress SDK is an open-source project, so you are free to modify the file as you wish. However, we strongly recommend you refrain from doing that. Here are some good reasons as to why:
1. The current opt-in UI and behavior was officially approved and is compliant with the WordPress.org guidelines. If you change it, the compliance with the guidelines is under your responsibility.
2. We ran many experiments to optimize the opt-in CTR (Click Through Rate) until we achieved this enhanced variation. There's a good reason why it looks like an OAuth screen - most users have already connected their Facebook or Google to 3rd party services before and are familiar with this UI concept.
3. Freemius is running on hundreds of plugins and themes. Therefore, there's a good chance that a user that installs your product had come across our opt-in before and recognizes the Freemius icon. It's easier to trust a UI people are already familiar with.
4. You'll need to make those modifications again whenever you want to update to a new version of the SDK.
---
# Safe Mode & Clone Resolution
Upon license key activation, Freemius assigns a unique ID and pair of public/secret keys tied to the website's URL. These are used for securely extending the license expiration date after subscription renewals, checking if there are new plugin/theme updates, and allowing customers to execute other account-related remote (or local) actions that get synced back to a website (or Freemius).
## What is a website clone?[](#what-is-a-website-clone "Direct link to What is a website clone?")
A clone is a website (or a subsite in a multisite network) assigned with an ID and pair of keys identical to another website (or subsite).
## How's a clone created?[](#hows-a-clone-created "Direct link to How's a clone created?")
There are several cases in which a site's URL could change:
* The site is updated to use a newly purchased domain name.
* A clone of a site from production to staging (or the other way around).
* Creation of a subsite by replicating an existing template subsite in a multisite network.
* WaaS multisite network environment where users can spin a website that is created by duplicating a template subsite.
* A network of websites/brands that are intentionally identical and automatically synced.
## What is the problem with clone websites?[](#what-is-the-problem-with-clone-websites "Direct link to What is the problem with clone websites?")
If two websites are assigned with the same ID and keys, actions on one website will impact the other, potentially triggering unexpected behaviors.
For example, when a plugin is deactivated, the Freemius SDK automatically notifies the licensing engine to detach the license from the website to free up the license activations counter for reuse on another website. However, if there's a clone, the license deactivation would propagate to the clone, which will deactivate the license, and therefore, automatic updates will stop working.
## Clone Website Resolution[](#clone-website-resolution "Direct link to Clone Website Resolution")
When a plugin/theme uses SDK v.2.5.0 or higher, it has a built-in clone identification and resolution mechanism.
### Clone Identification[](#clone-identification "Direct link to Clone Identification")
The identification mechanism simply checks for unexpected mismatches between the URL of the website and the URL that is currently linked to the license on our backend (the "source of truth").
### Automatic Clone Resolution[](#automatic-clone-resolution "Direct link to Automatic Clone Resolution")
Once a clone is identified, the mechanism will attempt to resolve it automatically based on different heuristics and the status of the buyer's account to reduce interruptions to the minimum.
For example, we know that it's common to have a template subsite on multisite networks for spinning new subsites using replication. Therefore, when a subsite is a clone of another subsite in the same multisite network, and the license's quota wasn't fully utilized, the clone subsite would be resolved automatically as a new website. It will be issued with a new ID and pair of keys, and the license will be auto-activated in the background.
This automatic resolution comes with 3 retries to overcome temporary connectivity issues and other unexpected server problems.
### Programmatic Clone Resolution[](#programmatic-clone-resolution "Direct link to Programmatic Clone Resolution")
If cloning websites is part of your workflow (e.g., using a template for client sites), you can leverage the `FS__RESOLVE_CLONE_AS` flag to programmatically guide the clone resolution mechanism on how to resolve clones in your environment. Simply add the flag with one of the following optional values to your template site’s `wp-config.php` (or `functions.php`):
```
// A temporary duplication for the purpose of testing, staging, or development.
define( 'FS__RESOLVE_CLONE_AS', 'temporary_duplicate' );
// The new site is replacing the old one – this will migrate the license activation to the new site to resolve the clone.
define( 'FS__RESOLVE_CLONE_AS', 'new_home' );
// The cloned site is a new and different one – this will auto activate the license on the new site to resolve the clone.
define( 'FS__RESOLVE_CLONE_AS', 'long_term_duplicate' );
```
### Safe Mode[](#safe-mode "Direct link to Safe Mode")
If an automatic resolution isn't possible, the plugin/theme gets into a *Safe Mode*.
During *Safe Mode*, data syncing, as well as some account actions, will be temporarily disabled. However, automatic updates will continue working until the actual expiration date of the license or the one stored on the website, whichever comes first.
In addition, an admin notice for manual clone resolution will appear in the WP Admin:
### Manual Clone Resolution[](#manual-clone-resolution "Direct link to Manual Clone Resolution")

The clone resolution notice is pretty straightforward and empowers users to choose how to resolve the clone issue.
#### Duplicate Website[](#duplicate-website "Direct link to Duplicate Website")
If the website is a duplicate for testing, staging, or development, clicking the "Duplicate Website" will resolve the situation. The plugin/theme will remain fully functional, and automatic updates will be served as usual for 2 weeks (or until license expiration, whichever comes first).
In addition, the following dismissible notice will appear to remind about the 2-week period:

Suppose the duplicate testing/staging/development site is required for an extended period. In that case, the notice offers an option to activate a license to keep the paid functionality and automatic updates for more than 2 weeks.
If the duplicate website remains active for a long period, the manual resolution notice will reappear after 2 weeks with one difference. Instead of the “Duplicate Website” button a “Long-Term Duplicate” button will appear.

Clicking that option will activate a license and enable keeping the paid functionality and automatic updates for as long as the license is valid.
#### License Migration[](#license-migration "Direct link to License Migration")
If the new site URL is replacing an old site’s URL, clicking the “Migrate License” (or “Migrate”) will migrate the site’s data, including the license, to the new website. And place the old site, if it still exists, into a safe mode. The most common case for this is taking a site from development (or staging) to production.
Cannot Migrate License?
If clicking the button triggers a page reload but the clone resolution notice still appears, the migration failed. We are improving the migration process so the WordPress SDK can catch the error and notify users.
This can happen if the license cannot be migrated to the new URL, for example, due to [license security settings](https://freemius.com/help/help/documentation/users-account-management/license-security/.md#url-whitelisting-aka-license-firewall) or insufficient [license quota](https://freemius.com/help/help/documentation/wordpress/license-utilization/.md) when migrating from staging to production.
#### New Website[](#new-website "Direct link to New Website")
If the new site is separate & standalone, clicking the "Activate License" (or "New Website") will create a new site ID & pair of keys and will activate the license. The most common case for this would be to create a copy of a site from an existing template.
#### Taking No Action[](#taking-no-action "Direct link to Taking No Action")
If no action is taken, the plugin/theme will remain fully functional, and automatic updates will be served as usual, for a grace period of 2 weeks (or until license expiration, whichever comes first).
---
# Handling Licensing
It’s quite easy to be able to have your product support a licensing mechanism with Freemius. All you need to do is add some logic that will help Freemius understand which parts (code blocks) of your product belong to which of your payment plans. Once you have that logic in place Freemius will automatically use it to determine which of your product’s capabilities should be included or excluded on each plan.
## Plan vs. Code Type[](#plan-vs-code-type "Direct link to Plan vs. Code Type")
Before jumping into the code, it's important to understand the difference between a **Plan** and a **Code Type**.
### Plan[](#plan "Direct link to Plan")
Every product on Freemius can have as many plans as you wish. A plan is a set of features or services. Multi-site license offers not considered as additional plans.
### Code Type[](#code-type "Direct link to Code Type")
Every plugin, theme, or add-on on Freemius can have two code types at most: *Free Version* and *Premium Version*. The *Free Version* is the product's code version stripped from ANY of the product's paid features. On the other hand, the *Premium Version* is the product's code version including ALL of the product's code. It doesn't necessarily mean that all the code will be available for use, it just means that all the code included in the *Premium Version*.
## License Related Methods[](#license-related-methods "Direct link to License Related Methods")
`can_use_premium_code()`[](#can_use_premium_code "Direct link to can_use_premium_code")
Check if a user is on a trial or has an activated license that enables premium features.
`is_free_plan()`[](#is_free_plan "Direct link to is_free_plan")
Check if the user is on the free plan of the product.
`is_plan($plan, $exact = false)`[](#is_plan "Direct link to is_plan")
Check if the user is on a specified plan. If the user is on a trial of that plan the result will be `false`.
`is_trial()`[](#is_trial "Direct link to is_trial")
Check if the user is on a trial.
`is_trial_plan($plan, $exact = false)`[](#is_trial_plan "Direct link to is_trial_plan")
Check if the user is on a specific trial plan.
`is_plan_or_trial($plan, $exact = false)`[](#is_plan_or_trial "Direct link to is_plan_or_trial")
Check if the user is on a specific plan or on that plan's trial.
`is_paying()`[](#is_paying "Direct link to is_paying")
Check if the user owns an activated and valid (paid) license.
`is_not_paying()`[](#is_not_paying "Direct link to is_not_paying")
Check if the user has an activated non-expired license that was purchased. Manually issued licenses will return `false`.
`is_paying_or_trial()`[](#is_paying_or_trial "Direct link to is_paying_or_trial")
Check if the user is paying or on a trial.
`is_premium()`[](#is_premium "Direct link to is_premium")
Check if user is running the product's *Premium Version*.
`is_trial_utilized()`[](#is_trial_utilized "Direct link to is_trial_utilized")
Check if the trial has already been used.
`is_payments_sandbox()`[](#is_payments_sandbox "Direct link to is_payments_sandbox")
Check if user is running payments in sandbox mode.
The SDK also comes with several handy license-related methods that in addition to performing their logic, all the logic that will be included within their `if` scope will be automatically stripped from the *Free Version* after the deployment process (by the Freemius' PHP preprocessor).
`is__premium_only()`[](#is__premium_only "Direct link to is__premium_only")
Check if user is only running the product's *Premium Version* code.
`is_paying__premium_only()`[](#is_paying__premium_only "Direct link to is_paying__premium_only")
Check if user is on a trial or on the free plan (not paying).
`can_use_premium_code__premium_only()`[](#can_use_premium_code__premium_only "Direct link to can_use_premium_code__premium_only")
Check if a user is on a trial or has an activated license that enables premium features.
`is_plan__premium_only($plan, $exact = false)`[](#is_plan__premium_only "Direct link to is_plan__premium_only")
Check if the user is on a specified plan. If the user is on a trial of that plan the result will be `false`.
`is_plan_or_trial__premium_only($plan, $exact = false)`[](#is_plan_or_trial__premium_only "Direct link to is_plan_or_trial__premium_only")
Check if the user is on a specific plan or on that plan's trial.
`is_paying_or_trial__premium_only()`[](#is_paying_or_trial__premium_only "Direct link to is_paying_or_trial__premium_only")
Check if the user is paying or on a trial.
### Recommended Methods[](#recommended-methods "Direct link to Recommended Methods")
If your product only has a single paid plan, the primary methods that you'll be using are:
* `can_use_premium_code()`
* `is__premium_only()`
* `can_use_premium_code__premium_only()`
If your product has multiple paid plans, the recommended methods to start with are:
* `is_plan_or_trial()`
* `is__premium_only()`
* `is_plan_or_trial__premium_only()`
### Examples[](#examples "Direct link to Examples")
Here are some concrete examples to help clarify this better:
warning
Note that in the following examples the product's shortcode is referred to as `my_fs` and the paid plan’s name is **professional**. It will obviously be different for you when you come to customize it.
To check if a website is eligible to use any of the premium defined logic (if the user is either currently in a trial period or simply owns a valid license), simply use:
```
// This IF block will be auto removed from the Free version.
if ( my_fs()->is__premium_only() ) {
// This IF will be executed only if the user in a trial mode or have a valid license.
if ( my_fs()->can_use_premium_code() ) {
// ... premium only logic ...
}
}
```
Alternatively, you can use the following code which is equivalent:
```
// This IF block will be auto removed from the Free Version and will only get executed if the user on a trial or have a valid license.
if ( my_fs()->can_use_premium_code__premium_only() ) {
// ... premium only logic ...
}
```
Here’s how you can add customized **marketing content** to encourage your users to upgrade from your **free** to your your **paid** version:
```
if ( my_fs()->is_not_paying() ) {
echo '
' . esc_html__('Awesome Professional Features', 'my-text-domain') . '
';
echo '' . esc_html__('Upgrade Now!', 'my-text-domain') . '';
echo '';
}
```
Here’s an easy way for you to add logic which will only be available in your premium plugin version:
```
// This "if" block will be auto removed from the Free version.
if ( my_fs()->is__premium_only() ) {
// ... premium only logic ...
}
```
To add a function which will only be available in your premium plugin version, simply add ***premium\_only*** as a suffix to the function’s name. Make sure that all lines that call that method either directly, or by using hooks, are also wrapped in the **premium only** logic:
```
class My_Plugin {
function init() {
...
// This "if" block will be auto removed from the free version.
if ( my_fs()->is__premium_only() ) {
// Init premium version.
$this->admin_init__premium_only();
add_action( 'admin_init', array( &$this, 'admin_init_hook__premium_only' );
}
...
}
// This method will be only included in the premium version.
function admin_init__premium_only() {
...
}
// This method will be only included in the premium version.
function admin_init_hook__premium_only() {
...
}
}
```
Here’s how you can add logic which will only be executed for customers on one of your paid plans. Simply switch the string `professional` with whatever name you gave the plan you wish to apply this logic to:
```
if ( my_fs()->is_plan('professional', true ) ) {
// .. logic related to professional plan only ...
}
```
Add logic which will only be executed for customers on a specific plan you choose, OR any **higher tier**. Simply switch the string `professional` with whatever name you gave your plan:
```
if ( my_fs()->is_plan('professional') ) {
// ... logic related to professional plan and higher plans ...
}
```
Add logic which will only be executed for customers on a specific plan you choose, AND any **higher tier**. Simply switch the string `professional` with whatever name you gave your plan:
```
// This "if" block will be auto removed from the Free version.
if ( my_fs()->is_plan('professional') ) {
// ... logic related to professional plan and higher plans ...
}
```
Add logic which will only be executed for customers on a specific plan you choose, AND any **higher tier**. Simply switch the string `professional` with whatever name you gave your plan:
```
// This "if" block will be auto removed from the Free version.
if ( my_fs()->is_plan__premium_only('professional') ) {
// ... logic related to professional plan and higher plans ...
}
```
Add logic only for users in a **trial period:**
```
if ( my_fs()->is_trial() ) {
// ... logic for users in trial ...
}
```
Add logic for any specified paid plan. Simply switch the word `professional` with whatever name you gave your plan:
```
// This "if" block will be auto removed from the Free version.
if ( my_fs()->is__premium_only() ) {
if ( my_fs()->is_plan( 'professional', true ) ) {
// ... logic related to professional plan only ...
} else if ( my_fs()->is_plan( 'business' ) ) {
// ... logic related to Business plan and higher plans ...
}
}
```
## Excluding files and folders from the free plugin version:[](#excluding-files-and-folders-from-the-free-plugin-version "Direct link to Excluding files and folders from the free plugin version:")
There are two ways to exclude files from your **free version**:
1. Add ***premium\_only*** just before the file extension. For example: **functions\_\_premium\_only.php** will be only included in the premium plugin version. *This works for all type of files, not only PHP.*
2. Add `@fs_premium_only`, a special meta tag, to the plugin's main PHP file header. Example:
```
/**
* Plugin Name: My Very Awesome Plugin
* Plugin URI: http://my-awesome-plugin.com
* Description: Create and manage Awesomeness right in WordPress.
* Version: 1.0.0
* Author: Awesomattic
* Author URI: http://my-awesome-plugin.com/me/
* License: GPLv2
* Text Domain: myplugin
* Domain Path: /langs
*
* @fs_premium_only /lib/functions.php, /premium-files/
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// ... my code ...
```
The file `/lib/functions.php` and the directory `/premium-files/` will be removed from the free plugin version.
## WordPress.org Compliance[](#wordpressorg-compliance "Direct link to WordPress.org Compliance")
FInally, regarding **WordPress.org Compliance:**
Based on [WordPress.org Guidelines](https://wordpress.org/plugins/about/guidelines/) you are not allowed to submit a plugin that has premium code in it:
> "All code hosted by WordPress.org servers must be free and fully-functional. If you want to sell advanced features for a plugin (such as a "pro" version), then you must sell and serve that code from your own site, we will not host it on our servers."
Therefore, if you want to deploy your free plugin's version to WordPress.org, make sure you wrap all your premium code with an `if` statement like so:
```
if ( my_fs()->is__premium_only() ) {
// ... my premium only logic ...
}
```
or use the other methods provided to exclude premium features & files from the free version.
## Handling Licensing in JavaScript[](#handling-licensing-in-javascript "Direct link to Handling Licensing in JavaScript")
If you need to control licensing in your JavaScript files, you can easily do that by applying the following approach:
```
'tabs',
```
warning
**Important:** This currently has to be done manually as it isn't automatically added when the SDK integration snippet is generated. Don't forget to re-add this whenever your SDK integration code snippet changes.
This displays Freemius pages as tabs instead of menu items.

By default, your plugin settings won't be displayed inside a dedicated tab. This needs to be added manually.
## Adding a Custom Tab[](#adding-a-custom-tab "Direct link to Adding a Custom Tab")
To blend your plugin settings page in with the Freemius tabs you can add a custom tab with a little custom HTML.
warning
**Important:** For the tabs to work correctly you need to make sure your plugin settings are contained in a `div.wrap` element, which is considered [standard practice](https://developer.wordpress.org/plugins/settings/custom-settings-page/).
Modify your existing plugin settings markup to include the following HTML. Make sure you replace the placeholder link with a full link to your plugin settings page.
```
```

As you can see Freemius automatically adds styles needed to blend in with core Freemius tabs (as long as you include the CSS classes in the snippet above).
You might notice that when you click on a Freemius tab (e.g. **Contact Us**) a submenu item is displayed under the plugin settings menu to indicate which tab is currently visible. No submenu is displayed for the main plugin settings page.

If you need to fit the tabs with your own styling you do have some flexibility. The minimum markup needed to integrate with Freemius tabs is:
```
```
Depending on the size and complexity of your plugin settings page you might occasionally see a slight 'blip' when the tabs are rendered. This can be due to the custom tab being displayed immediately, whereas the Freemius tabs are added via JavaScript a few milliseconds later.
This is not normally an issue but if it's noticeable then you can try hiding the tabs initially with CSS, and displaying via JavaScript once the Freemius tabs have finished loading.
CSS (add as inline style or via style sheet)
```
h2.nav-tab-wrapper {
display: none;
}
```
JavaScript (enqueue in footer)
```
jQuery(document).ready(function ($) {
document.querySelector('h2.nav-tab-wrapper').style.display = 'block';
});
```
## Adding a Custom Tab Via JavaScript[](#adding-a-custom-tab-via-javascript "Direct link to Adding a Custom Tab Via JavaScript")
It's also possible to add a custom tab purely via JavaScript instead of HTML which has some inherent advantages.
Namely, it avoids the 'blip' issue completely when adding a custom tab with HTML as all tabs are rendered to the DOM *before* they're displayed. And almost no alterations are necessary to your existing plugin settings page markup.
The JavaScript code should be enqueued to run in the page footer (to ensure it runs after the Freemius tabs JavaScript), and *only* on the plugin settings page and Freemius pages (Account, Contact Us, Upgrade).
To help whitelist admin pages you can use the [Freemius Menu Slugs](https://github.com/Freemius/wordpress-menu-slugs-plugin) plugin to find the menu slugs.
Here's an example snippet to add a custom tab via JavaScript:
```
jQuery(document).ready(function ($) {
// add a 'Settings' tab via JS
const navTabWrapper = $('.nav-tab-wrapper');
const currentTabs = $('.nav-tab-wrapper a');
let activeTab = '';
if (!currentTabs.hasClass('nav-tab-active')) {
activeTab = ' nav-tab-active';
}
navTabWrapper.prepend(
'Settings'
);
});
```
Again, make sure you replace the placeholder link with a full link to your plugin settings page.
It's also recommended that you add the `fs-section fs-full-size-wrapper` classes to the `div.wrap` element to your plugin settings page to help blend in with Freemius tabs.
## Clearing the Freemius Cache[](#clearing-the-freemius-cache "Direct link to Clearing the Freemius Cache")
When editing custom tab code you might occasionally come across your tab changes not being updated as expected for core Freemius tab pages (Account, Contact Us, Upgrade, etc.).
This is usually due to Freemius caching the page markup. To solve this simply clear the cache via the Freemius [debugging admin page](https://freemius.com/help/help/documentation/wordpress-sdk/debugging/.md).

---
# Testing your product before releasing to production
After integrating the Freemius SDK into your WordPress product, you should test it to ensure everything works properly before releasing a version to production.
Here is what Freemius recommends for testing the functionality and integration:
## Setting Freemius into Development Mode[](#setting-freemius-into-development-mode "Direct link to Setting Freemius into Development Mode")
During development and testing, add the following constant to your testing environment's `wp-config.php` file to display the **Freemius Debug** menu item.
```
/* Set Freemius into development mode */
define( 'WP_FS__DEV_MODE', true );
```
tip
These definitions are added under the **WordPress debugging mode** section before the *WordPress Engine* starts, i.e. before `/* That is all, stop editing! Happy blogging. */` comment.
When you refresh the WP Admin, you should notice a new top-level menu item. If you click it, you will be able to see all the Freemius data associated with your local environment.

Important Warning
Activating development mode also sets the SDK for debugging, which, among other things, will turn the SDK's logging system on:
* Logs are stored in the DB, so activating logging on a highly-active production site can quickly fill up the DB storage.
* Logs are also printed out to the console log, so if a highly-active site utilizes a filesystem caching plugin while running in debug mode, the site’s disk may fill up quickly.
Therefore, do not forget to turn development mode off after completing troubleshooting.
## Testing User Opt-in[](#testing-user-opt-in "Direct link to Testing User Opt-in")
The [opt-in screen](https://freemius.com/help/help/documentation/wordpress-sdk/opt-in-message/.md) appears only during product activation.

After opting in or skipping the process, the screen will not appear again.
### Reset Opt-in after Opting in[](#reset-opt-in-after-opting-in "Direct link to Reset Opt-in after Opting in")
To display the opt-in screen after opting in initially, you will need to [delete the installation from Freemius](https://freemius.com/help/help/documentation/wordpress-sdk/wp-admin-account/.md#deactivate-a-license-from-the-website) under the **Account** submenu item.

### Reset Opt-in after Skipping[](#reset-opt-in-after-skipping "Direct link to Reset Opt-in after Skipping")
To display the opt-in screen after skipping it initially, follow these steps:
1. Enable the [Freemius debugging](https://freemius.com/help/help/documentation/wordpress-sdk/debugging/.md) page.
2. Then clear Freemius data in the admin dashboard by selecting the **Delete All Accounts** button.

## Skipping Email Activation[](#skipping-email-activation "Direct link to Skipping Email Activation")
For security and data integrity reasons, if an already opted-in user attempts to opt in to Freemius again, you will first receive an activation email to complete the process. This helps prevent users from hijacking other user accounts.
However, this step might slow down the development and testing process. To skip it, add the following constant to your testing environment's `wp-config.php` file in addition to the previously added constants:
```
/* Skip email activation when a user with the same email already opted-in */
define( 'WP_FS__SKIP_EMAIL_ACTIVATION', true );
```
## Sandbox Payments[](#sandbox-payments "Direct link to Sandbox Payments")
The Freemius WordPress SDK embeds a full checkout system that allows you to monetize your products via the **Upgrade** or pricing page. This checkout process can be fully tested in a sandbox environment before going live.
To set Freemius Checkout to sandbox payment mode, make sure your `wp-config.php` file contains the following constant with your secret key:
```
/* Set Freemius into development mode */
define( 'WP_FS__DEV_MODE', true );
/*
* Set your plugin / theme Freemius secret key for super powers!
* 1. Replace `my-slug` with your product's lowercase slug.
* 2. Replace `sk_mySecretKey` with your product's secret key.
*/
define( 'WP_FS__my-slug_SECRET_KEY', 'sk_mySecretKey' );
```
With this setup, choose the desired plan from the pricing page. When the Checkout appears, complete the payment using these [credit cards](https://freemius.com/help/help/documentation/checkout/integration/testing/.md#testing-credit-cards) and [PayPal](https://freemius.com/help/help/documentation/checkout/integration/testing/.md#testing-paypal-accounts) accounts in sandbox mode.
---
# Text & Strings Customization
All the SDK's text and strings are customizable. Every string has its own unique key which you can use to override it.
You can use the `fs_override_i18n()` function as following:
```
fs_override_i18n( array(
'opt-in-connect' => __( "Yes - I'm in!", '{your-text_domain}' ),
'skip' => __( 'Not today', '{your-text_domain}' ),
), '{your_slug}' );
```
Or the `override_i18n()` public method of the `Freemius` class:
```
my_fs()->override_i18n( array(
'opt-in-connect' => __( "Yes - I'm in!", '{your-text_domain}' ),
'skip' => __( 'Not today', '{your-text_domain}' ),
) );
```
---
# WP Admin Account
By utilizing the [WordPress SDK](https://freemius.com/help/help/documentation/wordpress-sdk/.md) an account section will be automatically added to your plugin/theme settings right within the WP Admin dashboard.

tip
If your **Account** menu item isn't available, you can access it via `/wp-admin/admin.php?page=my-slug-account`.
## Managing your account[](#managing-your-account "Direct link to Managing your account")
The in-dashboard Account page allows users to manage their profile, license, subscription, and billing, right within the comfort of the WP Admin dashboard.

note
If the license is white-labeled, sensitive information like billing details and license key will be hidden, and user actions will be restricted. A license holder can unlock all options by clicking the **Start Debug** link and entering the license key.
### Deactivate a license from the website[](#deactivate-a-license-from-the-website "Direct link to Deactivate a license from the website")
To deactivate a license from the website, an admin can click the **Deactivate License** link.

Users can go further by clicking the **Disconnect** link to disconnect the site completely from Freemius and remove it from their User Dashboard.


## White Label Mode[](#white-label-mode "Direct link to White Label Mode")
Freelancers and agencies often use multi-site licenses when working on client sites, where revealing sensitive account information or allowing client access to certain account actions may be undesirable.
To address this, Freemius offers a built-in license white-labeling feature. This feature can be easily activated by users via the WP Admin dashboard and User Dashboard, or by you, the product owner, through the Developer Dashboard.
### How to activate white-label mode for a license?[](#how-to-activate-white-label-mode-for-a-license "Direct link to How to activate white-label mode for a license?")
#### White-label mode via WP Admin Dashboard[](#white-label-mode-via-wp-admin-dashboard "Direct link to White-label mode via WP Admin Dashboard")
Upon license activation, an admin notice will appear, allowing users to enable white-label mode directly from the WP Admin dashboard before handing over a site to their clients.

#### White-label mode via User Dashboard[](#white-label-mode-via-user-dashboard "Direct link to White-label mode via User Dashboard")
Your customers can also activate [white-label mode through the User Dashboard](https://freemius.com/help/help/documentation/users-account-management/license-security/.md#white-label-mode) by:
* Logging into their accounts
* Selecting **Licenses** from the menu
* Choosing the relevant license to open its editing panel:

* Checking the white-labeling box to hide confidential account and license information:

#### White-label mode via Developer Dashboard[](#white-label-mode-via-developer-dashboard "Direct link to White-label mode via Developer Dashboard")
As a product owner, you can also enable white-label mode for a customer’s license from the Developer Dashboard:
* Navigate to the **Licenses** section.
* Locate the desired license.
* Toggle the white-labeling switch for that license:

### What exactly is hidden in white-label mode?[](#what-exactly-is-hidden-in-white-label-mode "Direct link to What exactly is hidden in white-label mode?")
When a license is set to white-label mode, the following information is hidden:
* License owner information
* Billing details and invoices
* License key
* Pricing page
* Add-on prices (if you sell add-ons)
* Contact Us page
Additionally, these actions will be unavailable on the WP Admin account page in white-label mode:
* Subscription cancelation
* Plan changes
* License owner name and email edits
---
# In-dashboard Upgrading in WP Admin
When you [integrate Freemius’ WordPress SDK](https://freemius.com/help/help/documentation/wordpress/integration-with-sdk/.md) into your plugin or theme, the free version automatically includes a pricing table and a purchase button, enabling customers to purchase directly from their WP Admin dashboard. We call this *In-Dashboard Upgrading*.
The pricing and features on this table automatically sync with what you’ve configured in the **Plans** section of your Developer Dashboard, keeping everything up-to-date and consistent.

## Customizing the Pricing Table[](#customizing-the-pricing-table "Direct link to Customizing the Pricing Table")
If you’d like to hide a plan from the WP Admin pricing table, you can do so by toggling the **Is Hidden** switch in the **Plans** section.
[](/help/videos/freemius-dashboard-hide-pricing-plan.mp4)
## Customizing the Pricing Page[](#customizing-the-pricing-page "Direct link to Customizing the Pricing Page")
You can customize the appearance of the pricing table displayed in the WP Admin dashboard by modifying the CSS styles. This allows you to match the look and feel of your plugin or theme.
Alternatively, you can create a completely custom pricing page by following the steps outlined in our [Custom Pricing Page](https://github.com/Freemius/pricing-page#how-to-create-my-own-pricing-app-version) public repo.
## Customizing with Hooks[](#customizing-with-hooks "Direct link to Customizing with Hooks")
The WordPress SDK provides several hooks that allow you to customize the pricing table and purchase process. You can use these hooks to modify the content, appearance, and behavior of the pricing table to better suit your needs.
* Show pricing besides [default monthly setting](https://freemius.com/help/help/documentation/wordpress-sdk/filters-actions-hooks/.md#pricingshow_annual_in_monthly).
* Override the default Freemius WordPress SDK [pricing page url](https://freemius.com/help/help/documentation/wordpress-sdk/filters-actions-hooks/.md#pricing_url)
* Controls the [visibility of the pricing link for the product](https://freemius.com/help/help/documentation/wordpress-sdk/filters-actions-hooks/.md#is_pricing_page_visible)
More hooks and filters can be found under the [Filters & Actions Hooks](https://freemius.com/help/help/documentation/wordpress-sdk/filters-actions-hooks/.md) section.
---
# Setup for WordPress
Integrating Freemius into your WordPress plugin or theme is fast and easy.
This section will walk you through everything you need to get started: setting up products and plans, [integrating your code with the Freemius WP SDK](https://freemius.com/help/help/documentation/wordpress/integration-with-sdk/.md), and configuring key features like the opt-in prompt, licensing, automatic updates, and deployments. You’ll also learn how to sell [bundles, memberships](https://freemius.com/help/help/documentation/wordpress/selling-bundles-and-memberships/.md), [add-ons, and extensions](https://freemius.com/help/help/documentation/wordpress/selling-add-ons-extensions/.md).
On top of that, we’ll cover in-dashboard payments, free trials, and creating a smooth upgrade path, so your users can buy without friction.
If you’re a WordPress developer aiming to turn your plugin or theme into a reliable revenue stream, this is the place to start.
---
# Deployment Process
With the integration process completed, you're ready to [Deploy](https://freemius.com/help/help/documentation/release-management/deployment/.md) your plugin or theme to the Freemius Developer Dashboard.
First, compress (Zip) the root folder of your product that will be uploaded to the Freemius Developer Dashboard.
Begin the deployment process by:
1. Going to ***Deployment*** page
2. Click the ***Add New Version*** button.
3. Then upload the product ZIP file.
4. The product code will then be parsed and checked before the final free/premium versions of the product are generated and compressed into production ready Zip files.

For products that have a free plan defined, Freemius generates both free and premium versions of your WordPress product directly from the Zip you upload. Code wrapped in premium-only conditional logic (in the previous step) will only be included in the premium version. It will be stripped out entirely from the free version.
The free version can be downloaded manually and uploaded to WordPress.org via SVN. The premium version, along with all future updates, will be served directly from the Developer Dashboard to paying customers with an active license (they'll see update notifications via the WP Admin).
Once you've integrated your WordPress product with Freemius and deployed it, you're ready to start selling!
---
# Offering Free Trials
The Freemius WordPress SDK allows you to seamlessly offer free trials for your freemium WordPress plugins and themes. This enables potential customers to experience your premium features before making a purchase.

The image above shows how the built-in pricing page looks when a trial is available for a product plan.
## Offering Free Trials[](#offering-free-trials "Direct link to Offering Free Trials")

If you haven't already, please follow our [documentation](https://freemius.com/help/help/documentation/selling-with-freemius/set-up-trials/.md) to activate and configure trials from your Freemius Developer Dashboard.
## Integrating with the Freemius WordPress SDK[](#integrating-with-the-freemius-wordpress-sdk "Direct link to Integrating with the Freemius WordPress SDK")
If you haven't yet integrated the Freemius WordPress SDK into your product, get started [here](https://freemius.com/help/help/documentation/wordpress-sdk/integrating-freemius-sdk/.md).
If you are using the [licensing methods](https://freemius.com/help/help/documentation/wordpress-sdk/software-licensing/.md) `is_plan()` or `is_plan__premium_only()` in your integration with the WP SDK, then transition to the `is_plan_or_trial()` and `is_plan_or_trial__premium_only()` methods accordingly.
Finally, you need to update the SDK integration snippet in your product to enable trial support. This is done by adding a `trial` array to the integration snippet, as follows:
```
'trial' => array(
'days' => 14,
'is_require_payment' => false,
),
```
The exact code will differ based on your product's configuration. The best way to get the correct code snippet is to copy it from the [Freemius Developer Dashboard](https://freemius.com/help/help/documentation/wordpress/integration-with-sdk/.md#insert-the-auto-generated-sdk-integration-snippet).
Now the SDK will automatically handle trial related logic and UI.
### Trial Admin Notice[](#trial-admin-notice "Direct link to Trial Admin Notice")
For a freemium product integrated with the Freemius WordPress SDK, a dismissible admin notice with a trial offer will automatically appear in the WP admin dashboard **24 hours after installing the free product (plugin/theme) version**.

In addition to the admin notice, the Upgrade submenu item changes its label to **Start Trial**, and its color matches the blue of the promotional admin notice:

Clicking the **Start Trial** button redirects the user to the product's in-dashboard pricing page with the option to start a trial for any of the plans.
Once the user selects a plan and starts the trial, the premium version becomes securely accessible through a download link that appears in an admin notice and in an automated customer email.

### Product Admin Menu Item[](#product-admin-menu-item "Direct link to Product Admin Menu Item")
If you have a top-level menu item, the SDK will automatically add a counter to attract additional attention:

### Custom Trial Upgrade Links[](#custom-trial-upgrade-links "Direct link to Custom Trial Upgrade Links")
Since the Freemius WordPress SDK automatically handles the trial offer display and logic, you can easily add links to the trial page in your free product version. You might want to add such links in various locations, such as under the plugins menu or in your plugin's settings page.

The SDK includes a built-in method to provide the trial upgrade link, which you can wrap in your HTML anchor tags.
```
// Make sure to change my_fs() to your Freemius instance name.
my_fs()->get_trial_url();
```
You need to replace `my_fs()` with your actual Freemius instance name.
## Customizing the Free Trial Notice[](#customizing-the-free-trial-notice "Direct link to Customizing the Free Trial Notice")
The free trial promotional offer includes default text and descriptions.

These strings are customizable using the following filters:
```
override_i18n( array(
'hey' => 'Hey',
'trial-x-promotion-message' => 'Thank you so much for using %s!',
'already-opted-in-to-product-usage-tracking' => 'How do you like %s so far? Test all our %s premium features with a %d-day free trial.',
'start-free-trial' => 'Start free trial',
// Trial with a payment method required.
'no-commitment-for-x-days' => 'No commitment for %s days - cancel anytime!',
// Trial without a payment method.
'no-cc-required' => 'No credit card required',
) );
```
## Customizing the Free Trial Offer Timing[](#customizing-the-free-trial-offer-timing "Direct link to Customizing the Free Trial Offer Timing")
### Initial Display Timing[](#initial-display-timing "Direct link to Initial Display Timing")
Instead of the notice appearing the default 24 hours after installation, you can control when it appears using the following filter:
```
// Show the 1st trial promotion after 7 days instead of 24 hours.
function my_show_first_trial_after_7_days( $day_in_sec ) {
// 7 days in sec.
return 7 * 24 * 60 * 60;
}
my_fs()->add_filter( 'show_first_trial_after_n_sec', 'my_show_first_trial_after_7_days' );
```
### Resurfacing the Free Trial Offer[](#resurfacing-the-free-trial-offer "Direct link to Resurfacing the Free Trial Offer")
Customers who have dismissed the free trial offer admin notice can be shown the offer again after a certain period. By default, this period is set to 30 days. You can customize this period using the following filter:
```
function my_reshow_trial_after_every_60_days( $thirty_days_in_sec ) {
// 60 days in sec.
return 60 * 24 * 60 * 60;
}
my_fs()->add_filter( 'reshow_trial_after_every_n_sec', 'my_reshow_trial_after_every_60_days' );
```
---
# Freemius for WordPress
If you are using WordPress with the Block Editor, the Freemius for WordPress plugin enables you to integrate the Freemius checkout directly and quickly while building landing/sales pages for your Freemius product. No JavaScript, theme hacking, or coding required!

Not using the Block Editor?
See how to use the [overlay checkout](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md) or the [hosted checkout](https://freemius.com/help/help/documentation/checkout/integration/hosted-checkout/.md).
Out of the box, Freemius for WordPress gives you:
* **Native Block Editor support (Gutenberg):** Drop checkout buttons or pricing tables directly into your page layouts - just like adding any other block. *For example: Add a "Buy Now" button to your landing page in seconds.*
* **Dynamic pricing tables:** Display different plans with clear comparisons and calls-to-action. *Example: Showcase "Starter / Pro / Agency" tiers side by side, with checkout built in.*
* **Plan switching and trials:** Let customers upgrade, downgrade, or start with a free trial seamlessly. *Example: Offer a 14-day free trial that auto-converts to a paid plan without extra coding.*
* **Full compatibility with modern WordPress block themes and plugins:** Works out of the box with your existing WordPress block site setup - no theme hacks required.
* **100% free and open source:** Transparent, community-driven, and extensible. *Example: Extend the plugin with your own block variations, or contribute improvements back to the repo.*
## Resources[](#resources "Direct link to Resources")
* [Download from WordPress.org](https://wordpress.org/plugins/freemius/).
* [GitHub repo](https://github.com/Freemius/freemius-wp-plugin).
* ️[Test now on Playground](https://wordpress.org/plugins/freemius/?preview=1).
* [Features, roadmap and the changelog](https://wordpress.org/plugins/freemius/#developers).
## How do I set up a Freemius checkout button?[](#how-do-i-set-up-a-freemius-checkout-button "Direct link to How do I set up a Freemius checkout button?")
1. [Download](https://wordpress.org/plugins/freemius/), install, and activate the plugin on your WordPress site.
2. Add a button block to your page or post, then enable the Freemius checkout option in the button settings.
3. Configure your product details, and the button will automatically handle the checkout process.
Here is a quick video demonstrating how to set up a Freemius checkout button:
## FAQs[](#faqs "Direct link to FAQs")
### Can I customize the checkout experience?[](#can-i-customize-the-checkout-experience "Direct link to Can I customize the checkout experience?")
Yes, you can customize various aspects of the checkout process through the plugin settings, including product details, pricing, and the checkout flow.
---
# Integration with SDK
The [Freemius WordPress SDK](https://github.com/Freemius/wordpress-sdk) contains an impressive set of features to help you sell your plugins and themes, and it's regularly updated with [new features](https://github.com/Freemius/wordpress-sdk/releases) and improvements.
tip
To get notified about new SDK releases, it is recommended to **Watch** the GitHub repository. You can also **Star** the repository to bookmark it on your GitHub account.
After setting up your product’s plans and pricing, go to the ***SDK Integration*** page to add the latest WordPress SDK into your Product.

This part of the integration process involves adding Freemius specific code to your plugin or theme. This is split into three parts:
* [Add the core Freemius WordPress SDK](#add-the-core-freemius-wordpress-sdk).
* Add an auto-generated snippet of code to your product’s main PHP file.
* Wrap your product’s premium code with the proper SDK logic.
## Add the core Freemius WordPress SDK[](#add-the-core-freemius-wordpress-sdk "Direct link to Add the core Freemius WordPress SDK")
* Download the newest version of the [WordPress SDK](https://github.com/Freemius/wordpress-sdk/archive/master.zip) and extract the ZIP file.
* Rename the `wordpress-sdk-master` extracted folder to `freemius`.
* Create a new directory called `vendor` (if it does not exists).
* Move the renamed `freemius` folder as-is into the vendor folder of your WordPress theme or plugin.

## Fill out the SDK Integration Form[](#fill-out-the-sdk-integration-form "Direct link to Fill out the SDK Integration Form")
With the WordPress SDK inside your plugin or theme, fill out all the form fields in the **Init SDK** section. It's pretty straightforward as each field is self-explanatory, but if you have questions, please email us or ask for details in the `#integration` channel in our Freemius Slack.

note
Note: The SDK integration page screenshot above is for a plugin and is very similar to the one for the theme, as you can see in the screenshot below.

note
Note: There is no save button on the SDK integration page because ALL changes are auto-saved when any field is edited, which regenerates the SDK code snippet on demand.
## Insert the Auto-Generated SDK Integration Snippet[](#insert-the-auto-generated-sdk-integration-snippet "Direct link to Insert the Auto-Generated SDK Integration Snippet")
Once you've filled out the fields and created your pricing plans, the next step is to add the auto-generated SDK integration snippet to the plugin’s main file or theme’s `function.php` file.

## Designate Your Premium Only Code[](#designate-your-premium-only-code "Direct link to Designate Your Premium Only Code")
The last step in the integration process is to designate sections of your code that are premium-only so that Freemius knows what features are associated with each plan.
The benefit of this is that you can manage a single codebase and use the deployment feature to effectively “strip” premium-only features out of your Free version for easy deployment on WordPress.org. This way, you don’t have to manage two versions of your product separately.
There are [various methods available](https://freemius.com/help/help/documentation/wordpress-sdk/software-licensing/.md) to specify your premium code. For PHP, JavaScript and CSS code you can wrap specific sections of code with the corresponding premium logic.
This is usually in the form of conditional statements for PHP code and special meta comments for JavaScript and CSS. You can also choose to exclude entire files and folders from specific plans. This gives you a lot of flexibility in how to define premium-only features in your plugin or theme.
In addition, you can selectively ignore individual files and entire folders that should be included in all plans. This is ideal for 3rd party libraries (e.g. your `vendor` folder).
You can view the full range of functionality for designating premium-only sections of code with Freemius logic [here](https://freemius.com/help/help/documentation/release-management/deployment/.md).
Once completed, this step forms the full premium version of your plugin or theme. This deployment logic is a powerful tool that results in a very efficient workflow, allowing you to maintain a single codebase.
This tool is core to the Freemius concept, enabling a natural workflow of development and release cycles for both free and premium versions of your product.
tip
If you already developed free and premium versions that are fundamentally different, you can either [merge the versions into a single code base](https://freemius.com/help/help/documentation/wordpress-sdk/gists/.md#merging-different-free-and-premium-product-versions-into-one-with-freemius) or ignore the free version generated by Freemius. If you go with the 2nd option, make sure also to integrate the SDK into your free version and set the `is_premium` flag to `false` to indicate the SDK that it is running in the scope of the free version.
---
# License Recovery Tool for WordPress Customers
Sometimes, customers may misplace their license keys or download links for your WordPress plugins or themes.
While this information can be found in [their account](https://customers.freemius.com), to help them recover this information quickly and easily using only their email address, Freemius offers a License Recovery Self-Service tool.

## How It Works[](#how-it-works "Direct link to How It Works")
When a customer submits the form, Freemius automatically sends them an email on your behalf, containing download links for their purchased products along with all associated license keys.

This lightweight recovery flow gives your buyers a quick way to help themselves, which can significantly reduce routine support tickets.
## Setting Up Links[](#setting-up-links "Direct link to Setting Up Links")
We offer two types of links to the license recovery tool.
### Product-Specific License[](#product-specific-license "Direct link to Product-Specific License")
For a specific product, create a button or link using the URL pattern below:
```
https://dashboard.freemius.com/license-recovery/{product_id}/{product_slug}/
```
For example, using a product with ID `11707` and slug `my-awesome-plugin`, the URL would be:
```
https://dashboard.freemius.com/license-recovery/11707/my-awesome-plugin/
```
We recommend adding these links to your product's FAQ or support pages for easy access.
Where to Find Your Product ID and Slug
You can find the `product_id` and `product_slug` in your [Developer Dashboard](https://dashboard.freemius.com/) under the **Settings** page of the desired product.
* To get your `product_slug`, select the **Information** tab and check the `slug` field.
* To get your `product_id`, select the **API & Keys** tab and check the `ID` field under the **Product Keys** section.
### Developer Account-Wide License[](#developer-account-wide-license "Direct link to Developer Account-Wide License")
If you prefer offering a unified recovery page for your entire developer account, use:
```
https://dashboard.freemius.com/license-recovery/{developer_id}/
```
Where to Find Your Developer ID
Your `developer_id` is available via [your Profile page](https://dashboard.freemius.com/#!/profile/). Scroll to the bottom of the page under the **Keys** section.
---
# License Utilization
In this guide we will explain how Freemius license utilization works and how to configure it from the Developer Dashboard.
## Using One License for Live / Production, Staging, Dev and Localhost Sites[](#using-one-license-for-live--production-staging-dev-and-localhost-sites "Direct link to Using One License for Live / Production, Staging, Dev and Localhost Sites")
When creating a new plan, by default, generated licenses can be activated on localhost/staging/dev environments without the need to purchase any additional licenses.

If you keep that option set as recommended, one license should keep your customers covered. Localhost, staging and development environments won't get counted toward the license’s maximum allowed sites if its domain name is clearly a dev or staging site.
### TLDs that are considered as dev or staging:[](#tlds-that-are-considered-as-dev-or-staging "Direct link to TLDs that are considered as dev or staging:")
* `*.dev`
* `*.dev.cc` (DesktopServer)
* `*.test`
* `*.local`
* `*.staging`
* `*.example`
* `*.invalid`
### Subdomains that are considered as dev or staging:[](#subdomains-that-are-considered-as-dev-or-staging "Direct link to Subdomains that are considered as dev or staging:")
* `local.*`
* `dev.*`
* `test.*`
* `stage.*`
* `staging.*`
* `stagingN.*` (SiteGround; `N` is an unsigned int)
* `*.myftpupload.com` (GoDaddy)
* `*.cloudwaysapps.com` (Cloudways)
* `*.wpsandbox.pro` (WPSandbox)
* `*.ngrok.io` (tunneling)
* `*.mystagingwebsite.com` (Pressable)
* `*.tempurl.host` (WPMU DEV)
* `*.wpmudev.host` (WPMU DEV)
* `*.websitepro-staging.com` (Vendasta)
* `*.websitepro.hosting` (Vendasta)
* `*.instawp.co` (InstaWP)
* `*.instawp.link` (InstaWP)
* `*.instawp.xyz` (InstaWP)
* `*.wpengine.com` (WP Engine)
* `*.wpenginepowered.com` (WP Engine)
* `dev-*.pantheonsite.io` (Pantheon)
* `test-*.pantheonsite.io` (Pantheon)
* `staging-*.kinsta.com` (Kinsta)
* `staging-*.kinsta.cloud` (Kinsta)
* `*-dev.10web.site` (10Web)
* `*-dev.10web.cloud` (10Web)
Additionally, if your domain is `localhost` (with any port), it will also be treated as a localhost domain.
If your customer wants to utilize a license on a dev site that doesn’t fit the criteria listed above, they can deactivate the license from within the Account page in their WP Admin dashboard, and then reuse the license.
### Custom Staging / Development / Testing / Localhost Patterns[](#custom-staging--development--testing--localhost-patterns "Direct link to Custom Staging / Development / Testing / Localhost Patterns")
If a customer uses custom URL/pattern for their staging/development/testing/localhost environment, you can now easily add the URL/pattern in their profile on the Developer Dashboard in the **Custom localhost URLs** section:

warning
The *Custom localhost URLs* section is only available for customers who have at least one purchased & non-expired license from your store.
## Multisite Networks / WaaS[](#multisite-networks--waas "Direct link to Multisite Networks / WaaS")
[Freemius WordPress SDK](https://freemius.com/help/help/documentation/wordpress-sdk/.md) comes with an advanced multisite network integration that can simplify the license activation experience for customers with multisite networks.
### How to activate the multisite network integration?[](#how-to-activate-the-multisite-network-integration "Direct link to How to activate the multisite network integration?")
To activate the network-level integration, go to the product's ***SDK Integration*** page in the Freemius Dashboard, where you’ll notice a new checkbox to activate the network integration:

### Network-Level License Activation[](#network-level-license-activation "Direct link to Network-Level License Activation")
Once the multisite network integration is enabled, super-admins can activate a license across their entire network, delegate the license activation to site admins, or handpick which subsites to activate the license for:

### Multisite Network License Utilization[](#multisite-network-license-utilization "Direct link to Multisite Network License Utilization")
When using the Freemius WordPress SDK, by default, each subsite in a multisite setup requires an activation. Therefore, if for example, a user with a 20-subsite network would like to activate a license for their entire network, they'll need to purchase a license that can be activated on at least 20 sites, or multiple licenses whose combined activations limit is 20 or more.
#### Why a license activation on a multisite network isn't considered as a single activation?[](#why-a-license-activation-on-a-multisite-network-isnt-considered-as-a-single-activation "Direct link to Why a license activation on a multisite network isn't considered as a single activation?")
Multisite networks are way more complex than standard WordPress installations, have significantly more edge cases, and a higher chance for conflicts as subsites in the network can run different combinations of plugins and themes.
Also, multisite-networks are largely used by bigger entities like agencies, boutique hosting providers and WaaS, who generate revenue from their networks and therefore, have higher financial bandwidth.
Consequently, it's only fair to charge them more for protecting your business from the potentially increased support load and allowing you to offer even better support for those customers.
### Network-Level Management[](#network-level-management "Direct link to Network-Level Management")
Super-admins that didn’t delegate the activation to the site admins will now have an Account page on the network WP Admin, enabling them to manage the Account on the network level without the need to open the page for every subsite:

## Blocking or Allowing Features after License Expiration[](#blocking-or-allowing-features-after-license-expiration "Direct link to Blocking or Allowing Features after License Expiration")
With Freemius, you can easily configure whether to block or allow features after a license expires. This configuration is applied per plan and can be set in the plan settings in the Freemius Developer Dashboard.
Go to the plan page and scroll down to the **Features** section. There you can configure the behavior as needed.

You can choose to block features, allow features, or selectively block features for monthly subscriptions. Usually WordPress makers choose to block features for monthly subscriptions and allow features for yearly subscriptions. Please note that the Freemius WP SDK will automatically block updates and support for expired licenses, regardless of this setting.
## Overriding License Settings Per License[](#overriding-license-settings-per-license "Direct link to Overriding License Settings Per License")
If you want to customize these settings for an individual license, go to the **Licenses** page and click the specific license to open the advanced side-pane with the relevant license details.

From there, you can override the default plan settings for that specific license. For example, you can enable features for a particular license even if the plan blocks features after expiration, or restrict localhost, staging, or development usage for a license even if those environments are allowed at the plan level.
---
# Next Steps
You now have all the information needed to get started with Freemius!
It's time for action: Open an account if you haven't already done so and you can start integrating. Before you know it, you'll have the Freemius platform working with your own WordPress plugin or theme.
Let's recap the steps for a successful integration:
1. Open a Freemius account.
2. Add a new plugin, theme, or bundle.
3. Create your pricing plans.
4. Add the auto-generated SDK snippet and the core Freemius WordPress SDK to your plugin or theme.
5. Wrap premium only code in Freemius’ conditional logic or use special meta comments.
6. Deploy your WordPress product to the Developer Dashboard.
7. Release the auto-generated free/premium versions to your users.
Once your new product has been released, there are a few more things to consider that may prove useful in ensuring a [successful product launch](https://freemius.com/blog/100k-wordpress-product-launch/).
* Introduce yourself to the Freemius community via the `#general` Freemius Slack channel - and announce your first integration and product launch. We have special `#product-launches` and `#upvoters-mafia` channels to help get the word out and celebrate!
* Get started on [marketing](https://freemius.com/blog/marketing-plugin-theme-developers/) and [promoting](https://freemius.com/blog/ultimate-sites-promote-wordpress-plugins-themes/) your product. Our [Blog](https://freemius.com/blog/) has plenty of useful advice, tips, and tricks to help you on your road to success.
* Make sure to read the remaining sections where we talk about where to get help, advanced topics, and other essential resources.
---
# Selling Add-Ons / Extensions
Historically, many WordPress developers chose to monetize with the add-ons/extensions freemium business model, trying to replicate the success of popular products like WooCommerce.
In most cases, the add-ons model is actually not a good fit and we are seeing a market shift towards plans and add-on bundles (similar to plans). Therefore, if you are just starting out, don’t rush into add-ons only because you see others doing it. Be certain it makes sense for your product first.
tip
We wrote an in-depth article "[Premium vs. Add-ons - Which is the best monetization model for your plugin?](https://freemius.com/blog/premium-vs-add-ons-which-is-the-best-monetization-model-for-your-wordpress-plugin/)" that will give you a solid insight before you make your decision. If you’d like to see an example of a plugin that sells add-ons, check out [RatingWidget](https://wordpress.org/plugins/rating-widget/).
## How to Sell Add-Ons with Freemius?[](#how-to-sell-add-ons-with-freemius "Direct link to How to Sell Add-Ons with Freemius?")
Freemius fully supports the add-ons business model, and the integration only takes a few minutes. You can sell add-ons for plugins and themes.
tip
Freemius also supports [selling bundles](https://freemius.com/help/help/documentation/wordpress/selling-bundles-and-memberships/.md) which is particularly useful when selling add-ons.
1. Login to your [Freemius dashboard](https://dashboard.freemius.com/).
2. If you already created your “parent” product on Freemius, skip to #4. If not, create your “parent” plugin or theme by clicking the **Add product / bundle** button on the left side nav:

3. Follow the instructions to complete the “parent” product creation. (We will integrate the SDK later).
4. In the “parent” product menu, click on the ***Add Ons*** menu item:

5. Click the **Create Add-On** button and fill in all the required details

6. Click the **Get Started** button. This will create the add-on product.
7. Unlike in your parent product, you’ll need to add some marketing material like: description, selling points, screenshots, and visual banners. These can be added via the ***Settings*** of the add-on product.

8. After you fill up the marketing related material, head over to the add-on ***Plans*** and create your plans, pricing, and features list:

You can price your plans based on monthly, annual, as well as lifetime prices (we recommend using all of them). Here are some well researched and helpful articles if you’re not experienced with pricing:
* [Careful! This Simple Pricing Experiment Cost Us $2,000 in Revenue](https://freemius.com/blog/wordpress-plugin-monthly-pricing-experiment/)
* [Lifetime license for WordPress plugins – the right way!](https://freemius.com/blog/lifetime-license-for-wordpress-plugins-the-right-way/)
You can also leverage our [trials functionality](https://freemius.com/help/help/documentation/wordpress/free-trials/.md) to offer potential customers to try out your add-on before committing.
## Preparing the Add-On for Sale[](#preparing-the-add-on-for-sale "Direct link to Preparing the Add-On for Sale")
To ensure your add-on is ready for sale in production, verify the following:
1. Under the **Plans** page of the add-on, the "Release plans to users" toggle is turned on.

2. Under the **Settings** page of the add-on, the "Show add-on in the WP Admin Add-Ons marketplace" toggle is turned on.

While you're developing and testing the add-on, you can keep these toggles off and put the SDK in [testing mode](https://freemius.com/help/help/documentation/wordpress-sdk/testing/.md) until you are ready to go live.
tip
If your "parent" product is already in the wild, we recommend disabling the add-on until the code has been deployed and you are ready to sell the product. For this you can just turn off the "Show add-on in the WP Admin Add-Ons marketplace" toggle in the add-on settings.
## Selling Add-Ons from Your Website[](#selling-add-ons-from-your-website "Direct link to Selling Add-Ons from Your Website")
Selling the add-on from your site is a piece of cake.
* Go back to the ***Plans*** page, and click on **Get Checkout** the button to get the checkout JavaScript snippet:

* Copy the code and paste it into the add-on’s marketing page source on your site, and you are pretty much ready to go.
## Selling Add-Ons from within the WP Admin Dashboard[](#selling-add-ons-from-within-the-wp-admin-dashboard "Direct link to Selling Add-Ons from within the WP Admin Dashboard")
Now it’s time to integrate!
1. Switch back to your parent product’s context, and go to the ***SDK Integration*** section:

2. Follow the [SDK integration](https://freemius.com/help/help/documentation/wordpress/integration-with-sdk/.md) process carefully, step by step. If you already integrated Freemius into the parent product, you’ll need to update the generated SDK snippet (you'll notice a new flag named `has_addons` added). Copy the new snippet and replace the one you’ve integrated previously.
3. To verify the new snippet, install the plugin or theme on your development environment, and check if a new submenu item is added under your product’s menu item:

4. Clicking on the “Add Ons” menu item in your testing environment should show a mini-marketplace with all the product’s add-ons and you should already be able to test the purchase process

5. After validating the parent’s product integration, it’s time to integrate the SDK with your add-on. Switch the context to the add-on by clicking on the **Products** tab under the Freemius logo and select the add-on by name. Then, go to the ***SDK Integration***, and follow the integration steps.
6. When your add-on is ready for production, deploy it via the ***Deployment*** section.
7. And when you are ready to show the add-ons marketplace to your users simply release a new version of the plugin/theme with the updated SDK integration snippet.
tip
Whenever you want to release a new add-on just create it in the dashboard as described and it will appear automatically in the add-ons marketplace inside the admin dashboard. There is no need to update the parent product to make changes in the add-ons collection.
note
The in-dashboard add-ons marketplace has a 24-hours caching while running in production. Therefore, if you release a new add-on, it may take up to 24-hours until your users see it live.
---
# Selling Multiple WordPress Products as Bundles & Memberships
For WordPress plugin and theme makers, offering bundles and memberships can be a powerful way to increase revenue and provide more value to your customers. With Freemius, you can easily create and manage bundles and memberships, allowing you to sell multiple products together at a discounted price or offer ongoing access to a collection of products.
## Bundle vs Membership: What Is the Difference?[](#bundle-vs-membership-what-is-the-difference "Direct link to Bundle vs Membership: What Is the Difference?")
A **bundle** license grants access only to the specific products that were included in the bundle at the time of purchase. If the product maker adds new products to the bundle later, the original license will not cover those additions.
A **membership** license is more dynamic and allows ongoing access. If the maker adds new products to the membership, existing licenses automatically gain access to those products. This model supports a continuously evolving product offering under a single license.
Learn how to [configure a membership in Freemius](#configuring-a-membership).
## Create a Bundle "Product" on Freemius[](#create-a-bundle-product-on-freemius "Direct link to Create a Bundle \"Product\" on Freemius")
To demonstrate how you can start selling bundles, let’s assume that you have:
A freemium core plugin called **Freemius Starter Bundle** with two paid plans (Starter: $100 per year; Professional: $150 per year) and two add-ons:
* **Catwalk Addon 1** - a freemium add-on with one Pro plan for $50 per year.
* **Catwalk Addon 2** - a premium-only add-on for $50 per year.
For a customer to access the **Freemius Starter Bundle** Starter plan with the two paid add-on versions, the total annual price would be $200 per year. However, you can create a bundle of all three products (core + two add-ons) for $150, which is a $50 / 25% discount.
### The Process[](#the-process "Direct link to The Process")
1. Click the "+" button next to the profile avatar. Select **New Product**.
2. Select the **Bundle & Membership** product type.
3. Choose your store and input your bundle's product details like title (e.g. *Freemius Starter Bundle*) and featured icon.

4. Click the **Get Started** button. You’ll be automatically redirected to the Product’s checklist page.

5. Click the **Go to Products** button to open up the bundle's products management page.
6. Click the **Add product** button and associate the add-on products you want to include in the bundle. Every bundle can include a subset of products.
[](/help/videos/freemius-dashboard-bundle-product-addition.mp4)
7. Once all the products are added, the ***Products*** page should look like this:

8. In the left sidebar menu, navigate to the ***Plans*** page to set the bundle's plans and prices.
9. Click the **Add Plan** button to create a new plan for the bundle. Add the plan name and slug. In this case "Premium" and "premium" respectively.
10. Choose the products and their plans to include in the bundle’s plan from the dropdown. Click the **Add** button to add the product plan to the bundle plan.
[](/help/videos/freemius-dashboard-bundle-products-plan-selection.mp4)
11. Finally, set up the pricing for the bundle plan. In this case, we want $150 for the annual billing cycle for the "Professional" plan.

That’s it. The bundle is ready for sale.
Next, set up a [checkout link](https://freemius.com/help/help/documentation/getting-started/making-your-first-sale/.md#hosted-checkout) on any website or use the [Buy Button JavaScript API](https://freemius.com/help/help/documentation/checkout/integration/overlay-checkout/.md).
## Configuring a Membership[](#configuring-a-membership "Direct link to Configuring a Membership")
When you configure a [membership](#bundle-vs-membership-what-is-the-difference), any existing subscriptions or licenses will automatically gain access to these products, regardless of when they were purchased.
This configuration is applied per plan within your bundle, offering greater flexibility in how you structure plans and pricing.
To enable this for a bundle:
1. Navigate to the product’s plan.
2. Click to edit the desired plan, and select the **Membership** option:

3. The setting will be automatically saved, and the plan will be converted to a membership plan.
## Selling Bundles from the WP Admin Dashboard[](#selling-bundles-from-the-wp-admin-dashboard "Direct link to Selling Bundles from the WP Admin Dashboard")
Integrating with the [Freemius WordPress SDK](https://freemius.com/help/help/documentation/wordpress-sdk/integrating-freemius-sdk/.md) will automatically include a pricing page within your users' WP Admin dashboards. By default, the rendered pricing page is associated with the plugin or theme with which the SDK is integrated.
If you're using the [add-ons architecture](https://freemius.com/help/help/documentation/wordpress/selling-add-ons-extensions/.md) and selling bundles or memberships for your add-ons collection, it is highly recommended to configure the [SDK integration snippet](https://freemius.com/help/help/documentation/wordpress-sdk/integrating-freemius-sdk/.md#manually-editing-the-sdk-integration-snippet) to render the pricing of your bundle or membership with these parameters:
* [`bundle_id`](https://freemius.com/help/help/documentation/wordpress-sdk/integrating-freemius-sdk/.md#bundle_id)
* [`bundle_public_key`](https://freemius.com/help/help/documentation/wordpress-sdk/integrating-freemius-sdk/.md#bundle_public_key)
* [`bundle_license_auto_activation`](https://freemius.com/help/help/documentation/wordpress-sdk/integrating-freemius-sdk/.md#bundle_license_auto_activation): This setting saves customers the hassle of manually activating a bundle license for every add-on.
Here's how the WP SDK integration snippet could look like:
```
function my_fs() {
global $my_fs;
if ( ! isset( $my_fs ) ) {
// Include Freemius SDK.
...
$my_fs = fs_dynamic_init(
array(
...// Other product key-value pairs,
'bundle_id' => 12345,
'bundle_public_key' => 'pk_hrjss36joj632vgyy345ggv555',
'bundle_license_auto_activation' => true
)
);
}
}
```
tip
If your core plugin/theme is free, make sure to add it in the bundle's *Products* section. Then update the SDK integration snippet by setting the value of `'has_paid_plans' => true` for the pricing page to show up.
warning
When a bundle is configured, it replaces the upgrade pricing view with the bundle pricing. You can either show the premium core product pricing options or bundles, but not both at the same time.
## How Customers Can Upgrade from Individual Products to the Bundle[](#how-customers-can-upgrade-from-individual-products-to-the-bundle "Direct link to How Customers Can Upgrade from Individual Products to the Bundle")
If a customer has already purchased an individual product that is included in a bundle, they can easily upgrade to the bundle without losing any of their existing benefits. This enables them to access all the products in the bundle at a discounted price while retaining their current license key and activations.
To start the upgrade from an individual product to a bundle, a customer needs to:
1. Open the Checkout of the bundle either through a [hosted link](https://freemius.com/help/help/documentation/checkout/integration/hosted-checkout/.md#setting-up-hosted-checkout) or [pricing buttons](https://freemius.com/help/help/documentation/checkout/integration/overlay-checkout/.md#getting-the-checkout-overlay-code) on your website.
2. Enter the current license key for the individual product.

The checkout will automatically recognize the license as part of the bundle's offering and apply the appropriate prorated discount. This enables you to craft attractive upsell incentives by offering both bundle and plan upgrades in one step.

3. Complete the Checkout, and the upgrade process is done.
Upon upgrade, the license becomes a bundle license, unlocking all products included in that bundle plan.
note
All existing activations on the customer's website(s) remain intact. There is no need to re-activate anything.
---
# WordPress Product Plans & Pricing
In this guide, we'll walk you through setting up pricing plans for your WordPress plugin or theme using Freemius. You can create multiple plans with different pricing options, features, and license counts to cater to various user needs.
note
For SaaS pricing setup, see [set up SaaS](https://freemius.com/help/help/documentation/saas/saas-plans-pricing/.md).
## Creating Your First Plan[](#creating-your-first-plan "Direct link to Creating Your First Plan")
Go to the **Plans** page in the Developer Dashboard to create your first plan.

note
If you're adding a paid-only product, make sure to delete the Free plan. If it's your first commercial WordPress product, the [freemium business model](https://freemius.com/blog/freemium-business-model-wordpress/) can greatly benefit you by giving you the opportunity to [market your free product on WordPress.org](https://freemius.com/blog/seo-on-new-plugin-repository/), giving you access to a large number of potential users who can convert into paying customers. The WordPress SDK includes various sub-menu items that allow users to upgrade to your paid plans directly within the WP Admin dashboard (also known as "In-Dashboard Checkout").
Click **Add Plan** to create a new plan. Enter a unique name and specify a title. For example, 'pro' and 'Pro'.
Creating a new plan will immediately update the SDK’s integration snippet. For your convenience, the updated snippet appears in a pop-up window.
tip
You can close the pop-up window for now, as this is covered in the [Integration with SDK](https://freemius.com/help/help/documentation/wordpress/integration-with-sdk/.md) section. When you're comfortable with the integration steps, it's helpful to copy the updated snippet directly from the **Plans** page without having to go back to the **SDK Integration** page.
## Setup Pricing for the Plan[](#setup-pricing-for-the-plan "Direct link to Setup Pricing for the Plan")
Now we'll create different pricing options for the plan. A pricing option refers to a specific number of licenses or sites the configuration applies to. Each pricing option can have different billing cycles, such as monthly, annual, or lifetime.
For example, you can create a **5-Site** pricing option, meaning that after purchasing it, users will be able to activate the product on up to 5 different WordPress sites. More information can be found in the [License utilization](https://freemius.com/help/help/documentation/wordpress/license-utilization/.md) guide.
To get started, enter information about the plan, such as pricing for the **Single Site** option.

In the example above, you can see that we created pricing for monthly, annual, and lifetime billing cycles. You can choose to create pricing for any combination of these billing cycles.
You can also create pricing options for plans that offer more than one license activation for multiple sites. To do this, click **Add Bulk** and select the number of sites, then enter the relevant pricing options.

In the example above, we entered $49 as the price for an annual single-site license and added an additional option for a 3-site annual license at $79.
tip
If you are new to pricing, we recommend checking out our guide on [How to Price Your WordPress Plugin](https://freemius.com/blog/price-wordpress-plugin/).
The pricing plans will be displayed in the [Freemius Checkout](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md) when customers purchase your product, as seen here:

You can also customize your plans with additional settings, such as [additional currency](https://freemius.com/help/help/documentation/selling-with-freemius/multi-currency/.md), [free trials](https://freemius.com/help/help/documentation/selling-with-freemius/set-up-trials/.md), support channels, and whether features are blocked after license expiration.
Release Plans for Freemium Products
If you've been using Freemius to manage only the free version of your product without any paid plans, make sure to [release the plans to users](#release-the-plans-to-users) after creating your paid plans so that your users can see and purchase them.
## Configure a Plan's Support Channels[](#configure-a-plans-support-channels "Direct link to Configure a Plan's Support Channels")
Each created plan can enable support to your customers through a number of channels available in the Freemius Developer Dashboard.

These can be set up by:
1. Navigating to the **Plans** page.
2. Click the relevant plan to edit its details.
3. Scroll down to the **Support** section.
4. Enable the appropriate channels.
5. Set the corresponding link details.
note
The email address configured here will be included in the post-purchase emails sent to your product users as a support contact. For more reliable email delivery and management, we recommend setting up dedicated [support email addresses](https://freemius.com/help/help/documentation/marketing-automation/email-settings/.md).
## Configure a Plan's Features[](#configure-a-plans-features "Direct link to Configure a Plan's Features")
The plan's features can be listed in the **Features** section by scrolling down. Type the name of the feature and click the **\[+]** button or press Enter to add it to the plan. Repeat this step until you've added all the required features.

A typical freemium WordPress product contains a free and a pro plan, but you can create multiple plans if you wish.
tip
If your plugin or theme contains a fixed number of features across all paid plans and the only difference is the number of licenses, this is best handled with a single plan.
## Release the Plans to Users[](#release-the-plans-to-users "Direct link to Release the Plans to Users")
Finally, to make sure your plans are visible to users for purchase and license activations, toggle the **Release plans to users** option at the top right of the **Plans** page.

Having this option disabled will prevent your users from purchasing or activating licenses for your product. If you're testing Freemius integration with an existing product, you can keep this option disabled until you're ready to go live. In such cases, to test the purchase and license activation flows, you can put the SDK in [testing mode](https://freemius.com/help/help/documentation/wordpress-sdk/testing/.md).
Having this option disabled is also useful for products already in the wild. You can integrate your product with the Freemius SDK today and start deploying the code to your users without affecting their experience. Once you're ready to start selling, you can simply enable this option to make the plans available for purchase and license activations.
---
# Setup Recommended Refund Policy for your WordPress Products
You can tailor the refund policy for your WordPress products sold through Freemius to help build trust with your customers and reduce purchase anxiety.
The refund policy settings are accessible via the **Plans** page. Click the **Refund Policy** tab to reveal the settings.

To learn more about the available refund policy options visit our [Refund Policy Guide](https://freemius.com/help/help/documentation/selling-with-freemius/refund-policy/.md).
Here are our recommendations for your product:
## For Plugin Makers[](#for-plugin-makers "Direct link to For Plugin Makers")
A **[money-back guarantee](https://freemius.com/help/help/documentation/selling-with-freemius/refund-policy/.md#strict---money-back-guarantee)** is widely known to increase conversion rates for plugins, especially for first-time buyers who want to reduce purchase risk. For most plugins, we recommend offering a **14 to 30 day refund period**, which gives users enough time to install, test, and confirm that the plugin fits their needs.
However, refunds are **not always recommended** for plugins that deliver value immediately or are designed for short-term or one-time use. Examples include database export tools, migration plugins, backup utilities, cleanup tools, or similar solutions where the user can receive full value shortly after installation.
In these cases, offering refunds may significantly increase the risk of abuse, with limited upside in conversion gains.
## For Theme Makers[](#for-theme-makers "Direct link to For Theme Makers")
While money-back guarantees can increase conversions in general, we **do not recommend offering refunds for themes**.
Themes are visual products that can usually be evaluated in advance through demos, screenshots, and live previews. Once downloaded, themes can also be copied, reused, or modified with minimal effort, which makes refund abuse more likely.
For these reasons, a no-refund policy for themes tends to strike a better balance between protecting the maker and setting clear buyer expectations, without materially hurting conversion rates.
Disputes & Chargebacks
In the event, the buyer disputes a charge, see how we handle [disputes and chargebacks](https://freemius.com/help/help/documentation/selling-with-freemius/refund-policy/.md#disputes--chargebacks).
---
# Software Updates and Distribution
The [WordPress SDK](https://freemius.com/help/help/documentation/wordpress-sdk/.md) includes a complete software updates mechanism out-of-the-box, and is integrated with the [Software Licensing](https://freemius.com/help/help/documentation/wordpress-sdk/software-licensing/.md) engine, and the rest of Freemius suite of products.

warning
The software updates mechanism is only included for paid plugins and themes. Freemius doesn't support free products distribution, for now.
The auto updates mechanism is seamlessly integrated with the WP Core updates mechanism, but can only work when the product, plugin or theme, is actually active (otherwise, the custom code can't be executed).
## Who Is Eligible for Software Updates?[](#who-is-eligible-for-software-updates "Direct link to Who Is Eligible for Software Updates?")
Only users who are within a trial period, or customers holding a valid (and non-expired) license, activated on the WordPress site in context, will get access to premium software updates.
## Software Updates in a Multisite Network Environment[](#software-updates-in-a-multisite-network-environment "Direct link to Software Updates in a Multisite Network Environment")
Since the updates mechanism only works when the product is active - if the plugin or theme is active on a single site in a multisite network - only that site can execute the updates logic. However, since the updates mechanism is in a network environment, it's only triggered in the network admin scope. The updates mechanism will not work just by checking for updates in the network admin.
So, to get updates on that specific use-case, you must follow these steps:
1. Open the WP Admin of the site where the product is active.
2. Navigate to the product's *Account* page.
3. If you're eligible for updates and there's a pending update, you will see the `Install Update Now []` button. You can click that button to perform an immediate update.
note
Only a super-admin can update plugins and themes in a network environment.
4. After visiting the *Account* page, the network updates transient/cache will be updated with the required data for the product's update.
5. At this point, if you visit **WP Network Admin → Dashboard → Updates**, or the network plugins page, the update should be available.
---
# Popular
## What kind of products can I sell with Freemius?[](#what-kind-of-products-can-i-sell-with-freemius "Direct link to What kind of products can I sell with Freemius?")
You can sell any Software-as-a-Service (SaaS) or downloadable or static software. This includes, for example, any cloud-based software application accessed via the web, desktop or mobile apps, Chrome extensions, etc. See the full list [here](https://freemius.com/help/help/documentation/selling-with-freemius/allowed-prohibited-products/.md).
## Do I need a company to start selling my SaaS?[](#do-i-need-a-company-to-start-selling-my-saas "Direct link to Do I need a company to start selling my SaaS?")
No. You can start selling as an individual. Freemius handles payments, taxes, and compliance on your behalf, so you don't need to:
* Open a company
* Register for VAT
* Set up payment processors
* Many makers start this way and formalize later
## When should I choose an MoR like Freemius over Stripe or Paddle?[](#when-should-i-choose-an-mor-like-freemius-over-stripe-or-paddle "Direct link to When should I choose an MoR like Freemius over Stripe or Paddle?")
Use an MoR when you want to avoid managing tax compliance, legal liability, and payment operations across regions.
Stripe gives you full control but requires you to handle taxes and compliance; Paddle and Freemius act as MoRs, with Freemius being more tailored to SaaS, plugins, and software products.
## Can I use Freemius with Lovable?[](#can-i-use-freemius-with-lovable "Direct link to Can I use Freemius with Lovable?")
Yes! Freemius integrates with Lovable using one prompt. See more details [here](https://freemius.com/help/help/documentation/ai/lovable/.md)
You can use that prompt to generate a working checkout + subscription flow inside your Lovable app, including payments, licensing, and webhooks. This lets you skip building billing infrastructure while Freemius handles subscriptions, global taxes, and compliance as a Merchant of Record.
## Will you share the captured emails with someone else?[](#will-you-share-the-captured-emails-with-someone-else "Direct link to Will you share the captured emails with someone else?")
Your data is safe and sound. We will never-ever, never-ever share your captured user emails with any 3rd party.
## What payment methods can I accept with Freemius?[](#what-payment-methods-can-i-accept-with-freemius "Direct link to What payment methods can I accept with Freemius?")
We use iDeal (Netherlands), Stripe and PayPal, and therefore accept all major credit cards and PayPal payments. See the list of [supported countries](https://freemius.com/help/help/documentation/selling-with-freemius/supported-countries/.md).
## Do I need to set up a gateway to sell with Freemius?[](#do-i-need-to-set-up-a-gateway-to-sell-with-freemius "Direct link to Do I need to set up a gateway to sell with Freemius?")
Nope. No need for that because Freemius serves as your reseller.
## What currencies do you support?[](#what-currencies-do-you-support "Direct link to What currencies do you support?")
With Freemius you can sell and receive payments in USD (US dollars), GBP (British pounds), and EUR (Euros). See the list of [supported countries](https://freemius.com/help/help/documentation/selling-with-freemius/supported-countries/.md).
## Does Freemius store customer credit card numbers on Freemius servers?[](#does-freemius-store-customer-credit-card-numbers-on-freemius-servers "Direct link to Does Freemius store customer credit card numbers on Freemius servers?")
We aren't crazy 🙂 Credit Cards do not even “touch” our servers and “speak” directly to Stripe, a well trusted and secure gateway.
## Is Freemius Checkout PCI compliant?[](#is-freemius-checkout-pci-compliant "Direct link to Is Freemius Checkout PCI compliant?")
Yes, we are PCI compliant; we use PayPal Express Checkout and Stripe.js, and our checkout is secured with an HTTPS protocol.
## Is Freemius In-App/Dashboard checkout PCI compliant?[](#is-freemius-in-appdashboard-checkout-pci-compliant "Direct link to Is Freemius In-App/Dashboard checkout PCI compliant?")
Yes. Whether the housing site runs securely over HTTPS or not, our In-Dashboard Checkout is loaded securely via a PCI compliant HTTPS iframe.
## Can I customize the in-dashboard pricing and checkout pages?[](#can-i-customize-the-in-dashboard-pricing-and-checkout-pages "Direct link to Can I customize the in-dashboard pricing and checkout pages?")
The pricing page is automatically generated and styled by Freemius, following WordPress admin dashboard design practices. We make sure it looks natural and optimized for best conversation results.
We have years of experience optimizing conversion, and we use data to continuously improve on the pricing page.
We do provide an option to add custom CSS stylesheets to enable personalization.
You can add those stylesheets in ***Plans*** -> **Customization**:

> Note: For now, we do not recommend using custom CSS since changes in the page HTML structure might break your styles. We'll try to communicate any changes in the HTML DOM. Having said that, and for the sake of agile development, we do not guarantee notifying you about any changes – it's up to you to monitor changes.
## Do you support coupons?[](#do-you-support-coupons "Direct link to Do you support coupons?")
Yes – we support absolute amounts and percentage based [coupons](https://freemius.com/help/help/documentation/selling-with-freemius/coupon-discount/.md). You can set up the effective date range of a coupon's validity, whether the discount should apply for all payments or only for the initial payment, and multiple customization options.
## Does Freemius support EU VAT?[](#does-freemius-support-eu-vat "Direct link to Does Freemius support EU VAT?")
Yes. Avoid the hassles of EU VAT compliance by having Freemius handle VAT collection, compliance and payments. Utilize support for real-time VAT ID validation and exemption when selling to businesses.
## How much do I have to generate in order volume using Freemius before I can get paid?[](#how-much-do-i-have-to-generate-in-order-volume-using-freemius-before-i-can-get-paid "Direct link to How much do I have to generate in order volume using Freemius before I can get paid?")
The minimum account balance required for payment is $100.00.
## Can I refund my customers?[](#can-i-refund-my-customers "Direct link to Can I refund my customers?")
You can refund transactions up to 30 days after the transaction was successfully processed and payment was created.
We currently only offer full refunds (no partial refunds).
If you'd like to refund a customer after the 30-day refund window is over, we recommend resolving the refund request with your own payment services.
## Do you offer automated recurring/subscription-based billing?[](#do-you-offer-automated-recurringsubscription-based-billing "Direct link to Do you offer automated recurring/subscription-based billing?")
We support automatic renewals, and encourage software makers to sell subscriptions to build long term sustainable businesses. It increases renewal rate, removes the hassle of payment renewal and increases your bottom line. Freemius monthly and annual plans are both automatically renewed.
## How long will it take until the customer sees the refund?[](#how-long-will-it-take-until-the-customer-sees-the-refund "Direct link to How long will it take until the customer sees the refund?")
While some refunds may be instantaneous, credit refunds can take 5–10 business days to show up in their customer's credit card statement.
## What happens if a customer disputes a payment / chargeback?[](#what-happens-if-a-customer-disputes-a-payment--chargeback "Direct link to What happens if a customer disputes a payment / chargeback?")
The payment amount will be temporarily held and we'll contact you ASAP, giving you the chance to resolve the dispute directly with the customer.
If the dispute isn't resolved, we'll step in and handle each case individually.
* If the dispute is resolved in your favor, you are all good.
* If the dispute is resolved in favor of the customer, the held amount will be refunded to the customer.
* If it was a bank dispute (via Stripe), chargebacks incur a fee of $15, which will be deducted from your balance.
## Do you offer specialized pricing for micro-transactions like on Apple's App Store or Google's Android Market?[](#do-you-offer-specialized-pricing-for-micro-transactions-like-on-apples-app-store-or-googles-android-market "Direct link to Do you offer specialized pricing for micro-transactions like on Apple's App Store or Google's Android Market?")
The recommended minimum price per transaction is $3.99. Otherwise, your processing fee will be as high as 30%-50% due to the minimum per transaction rates charged by Stripe and PayPal. [Email us](mailto:support@freemius.com) to discuss your specific situation.
## How do you handle downloads and file hosting?[](#how-do-you-handle-downloads-and-file-hosting "Direct link to How do you handle downloads and file hosting?")
We securely deliver reliable downloads to valid license holders through our API, utilizing S3 hosting on AWS.
## What provision is there for users to export their data if they wanted to leave the platform?[](#what-provision-is-there-for-users-to-export-their-data-if-they-wanted-to-leave-the-platform "Direct link to What provision is there for users to export their data if they wanted to leave the platform?")
Many marketplaces choose to hide their customers' data from developers and “lock them” inside the platform.
We believe that the data belongs to the developers. If a developer decides to leave the platform for any reason whatsoever, he/she can easily download their user-list from our dashboard with one click, and they can access all of their data by leveraging our RESTful API for migration purposes.
## Can I customize the ‘From' addresses of the email messages sent by Freemius?[](#can-i-customize-the-from-addresses-of-the-email-messages-sent-by-freemius "Direct link to Can I customize the ‘From' addresses of the email messages sent by Freemius?")
Yep – you can do that in the ‘Email Addresses' section.
## Do you support affiliates?[](#do-you-support-affiliates "Direct link to Do you support affiliates?")
Yes, we do! Freemius comes with a fully-featured [Affiliation Platform](https://freemius.com/help/help/documentation/affiliate-platform/.md) which you can utilize to onboard and manage affiliates.
## What does the license renewal process look like for customers?[](#what-does-the-license-renewal-process-look-like-for-customers "Direct link to What does the license renewal process look like for customers?")
All plans (besides lifetime / one-time plan) are automatically renewed. For annual plans, the customer will receive a friendly reminder email a month prior to the renewal date, giving them enough time to cancel the subscription, if they wish to do so.
## How can I send emails to my users and customers?[](#how-can-i-send-emails-to-my-users-and-customers "Direct link to How can I send emails to my users and customers?")
We don't provide a custom emailing mechanism. We might offer this option in the future as an additional paid package.
For now, the best way is to either download the ‘users list' file from the ***Users*** section in the dashboard,

or leverage our [webhooks mechanism](https://freemius.com/help/help/documentation/saas/events-webhooks/.md) by pushing users' details, including email addresses, to mailing platforms such as MailChimp or Customer.io.
## Does Freemius support prorating?[](#does-freemius-support-prorating "Direct link to Does Freemius support prorating?")
Proration handles the increase or decrease to the subscription price when a price changes during any period of a subscription. By default, [Freemius prorates plan updates](https://freemius.com/help/help/documentation/checkout/features/proration/.md) (upgrades or downgrades).
## Can I offer a free trial to my users?[](#can-i-offer-a-free-trial-to-my-users "Direct link to Can I offer a free trial to my users?")
Yes. You can set a [trial period](https://freemius.com/help/help/documentation/selling-with-freemius/set-up-trials/.md#free-trial--without-a-payment-method) in your plan settings. Just make sure to turn the “Require Credit Card or PayPal” option off.
## Can I offer a paid trial to my users?[](#can-i-offer-a-paid-trial-to-my-users "Direct link to Can I offer a paid trial to my users?")
Yes. You can offer a [Free Trial with a required payment method](https://freemius.com/help/help/documentation/selling-with-freemius/set-up-trials/.md#paid-trial--requiring-a-payment-method) by setting a trial period in your plan settings and then turning on the “Require Credit Card or PayPal” option.
## Under the “Sites” menu, what is the difference between “Plan” and “Is Premium”?[](#under-the-sites-menu-what-is-the-difference-between-plan-and-is-premium "Direct link to Under the “Sites” menu, what is the difference between “Plan” and “Is Premium”?")
If you name your paid plan as **“Premium”**, you might get a little confused with the terminology.
The data under the plan column shows the current plan of the install. **“Is Premium”** tells if that site is running the *free* or the *premium* code version of your module.
## How to switch the language on the pricing and checkout pages in the dashboard?[](#how-to-switch-the-language-on-the-pricing-and-checkout-pages-in-the-dashboard "Direct link to How to switch the language on the pricing and checkout pages in the dashboard?")
You can append the [`language` query parameter](https://freemius.com/help/help/documentation/checkout/integration/freemius-checkout-buy-button/.md#language%20%7C%20locale) to the pricing and checkout page URLs. Adding more translations is part of our roadmap. Please [contact us](mailto:support@freemius.com) if you want to help us with translations or if you want to see a specific language supported.
## Can I offer a discount on license renewals?[](#can-i-offer-a-discount-on-license-renewals "Direct link to Can I offer a discount on license renewals?")
Yes, you can. Keep in mind though, that our annual and monthly plans renew automatically.
This means that customers don't have to take any proactive action to renew the license.
In other words, you don't need to convince the customer to renew. The decision is whether to cancel the license or continue. Unless a customer is unhappy with your plugin or has stopped using it, a renewal discount has no significant effect on that decision.
Therefore, as a rule of thumb, we do not recommend setting a discount for renewals unless your plugin does not provide a continued value (e.g. a migration plugin, or any one-time action plugin). In that case, maybe it is worth setting up a [lifetime price](https://freemius.com/blog/lifetime-license-for-wordpress-plugins-the-right-way/).
## Can I upsell multi-site licenses?[](#can-i-upsell-multi-site-licenses "Direct link to Can I upsell multi-site licenses?")
Yes. You can set up custom *monthly*, *annual* and *lifetime* pricing for any amount of licenses.
## Are there any additional fees for receiving payments through PayPal?[](#are-there-any-additional-fees-for-receiving-payments-through-paypal "Direct link to Are there any additional fees for receiving payments through PayPal?")
PayPal payment transfers to U.S.-based accounts are free. For payment transfers to non-U.S. accounts, PayPal incorporates a 2% fee (maximum of $20 USD).
## What will my customers see on their Credit-Card and PayPal statement?[](#what-will-my-customers-see-on-their-credit-card-and-paypal-statement "Direct link to What will my customers see on their Credit-Card and PayPal statement?")
Customers who purchase your product through Freemius will receive a credit card statement showing your product's name.
For PayPal transactions, the store name will appear as your product's name. However, PayPal email notifications will state Freemius and include your product description in the plan details. Unfortunately, PayPal does not offer a way to customize this further.
## I received a subscription notification without a payment notification, what's going on?[](#i-received-a-subscription-notification-without-a-payment-notification-whats-going-on "Direct link to I received a subscription notification without a payment notification, what's going on?")
Due to the way PayPal's recurring profiles mechanism works, it can take up to 24 hours until Freemius receives the approval notification for the subscriptions' initial payment. To keep a smooth purchase experience for customers, Freemius will email the license key and the download link to the paid product – right away.
To mitigate potential abuse, when a customer subscribes to your product the system will issue a 24-hour blocking license. Once we receive the payment notification from PayPal, the license will be extended based on the subscription billing cycle, and the blocking state of the license will be auto-adjusted based on the plan's configuration.
We could have designed the upgrade process slightly differently, emailing the downloadable product only after the payment approval, but we decided that we aren't going to “break” a proper subscription UX just because of a small group of trolls that can abuse this PayPal flaw.
## How can I get paid? How do I get my earnings?[](#how-can-i-get-paid-how-do-i-get-my-earnings "Direct link to How can I get paid? How do I get my earnings?")
There are currently four payment methods available for you to collect your earnings from Freemius:
1. PayPal (default payment method, using PayPal MassPay)
2. Payoneer
3. Wire transfer (international and domestic / IBAN / SWIFT)
4. Wise (formerly TransferWise)
International wires can be converted to your local currency. Due to our high volumes we've managed to negotiate great terms with our bank for currency conversion-rates and a complete fee waiver on foreign-currency international wire.
Learn more about the supported payment methods and how to set them up in the [Your Earnings](https://freemius.com/help/help/documentation/selling-with-freemius/your-earnings/.md) section.
---
# Analytics & Insights
## Can I use Insights in my Premium only plugin or theme?[](#can-i-use-insights-in-my-premium-only-plugin-or-theme "Direct link to Can I use Insights in my Premium only plugin or theme?")
Yes, you can! In addition, since you are not obligated to any guidelines, you can capture all the information and skip the opt-in screen. If you do so, you would need to explicitly mention that part in your privacy and terms of use. Having said that, since no one actually reads the privacy and terms, we recommend using the opt-in screen as an ethical transparency act.
## Can I customize the opt-in screen?[](#can-i-customize-the-opt-in-screen "Direct link to Can I customize the opt-in screen?")
We have crafted special filters to customize the messaging and buttons of the opt-in screen. You can also completely edit the PHP template file in the SDK. Make sure you keep it clear about what information is being captured and that it’s sent to *Freemius*. Otherwise, it won’t be compliant with the [WordPress.org guidelines](https://wordpress.org/plugins/about/guidelines/).
## Can I use Freemius Insights on CodeCanyon and ThemeForest?[](#can-i-use-freemius-insights-on-codecanyon-and-themeforest "Direct link to Can I use Freemius Insights on CodeCanyon and ThemeForest?")
Yes! It’s compliant with the marketplace rules. In fact, a similar analytics product, called *PressTrends*, was widely adapted by *ThemeForest* and *CodeCanyon* developers in early 2014.
## Can I use Freemius Insights with EDD or WooCommerce?[](#can-i-use-freemius-insights-with-edd-or-woocommerce "Direct link to Can I use Freemius Insights with EDD or WooCommerce?")
Absolutely YES! There’s no collision nor interaction between [Freemius Insights](https://freemius.com/wordpress/insights/) and other eCommerce solutions. [Freemius Insights](https://freemius.com/wordpress/insights/) does not depend on on our monetization solutions. You can add [Freemius Insights](https://freemius.com/wordpress/insights/) to both your free and premium plugin versions.
## Why is the active installs metric on WordPress.org different than the one on Freemius?[](#why-is-the-active-installs-metric-on-wordpressorg-different-than-the-one-on-freemius "Direct link to Why is the active installs metric on WordPress.org different than the one on Freemius?")
There’s a fundamental difference in the way WordPress.org and Freemius count active installs.
Based on publicly available responses from key people in the WordPress meta team, the WordPress.org active installs counter relies on the WordPress.org updates mechanism, which is sampled on a weekly basis. Updates are triggered only when there’s traffic to the site, so if a site installed your plugin or theme and did not get any traffic during that sampling period OR, if the updates mechanism is blocked (or turned off), this site will not get counted. Also, we suspect that the WordPress.org sampling relies on domains and not on IPs. In that case, for example, all of the VVV installs that installed your product will only be counted as a single site.
On the other hand, Freemius uses real actions such as an explicit opt-in or deactivation/uninstall events. One fallback of that tracking methodology is if a user hard-deletes the product from the filesystem, using FTP/SSH, it is still considered active.
---
# Emails
## How can I send emails to my users and customers?[](#how-can-i-send-emails-to-my-users-and-customers "Direct link to How can I send emails to my users and customers?")
We don’t provide a custom emailing mechanism. We might offer this option in the future as an additional paid package.
For now, the best way is to either download the ‘users list’ file from the ***Users*** section in the dashboard,

or leverage our [webhooks mechanism](https://freemius.com/help/help/documentation/saas/events-webhooks/.md) by pushing users’ details, including emails, to mailing platforms such as MailChimp or Customer.io.
## Can I customize the ‘From’ addresses of the email messages sent by Freemius?[](#can-i-customize-the-from-addresses-of-the-email-messages-sent-by-freemius "Direct link to Can I customize the ‘From’ addresses of the email messages sent by Freemius?")
Yep – you can do that in the ‘Email Addresses’ section.
## Will you share the captured emails with someone else?[](#will-you-share-the-captured-emails-with-someone-else "Direct link to Will you share the captured emails with someone else?")
Your data is safe and sound. We will never-ever, never-ever share your captured user emails with any 3rd party.
---
# Freemius WordPress SDK
## What languages is your WordPress SDK translated to?[](#what-languages-is-your-wordpress-sdk-translated-to "Direct link to What languages is your WordPress SDK translated to?")
You can check out all of the supported languages and help us translate the SDK (we know you want to be on our hall of fame 😉 ) on our [Transifex translations manager](https://app.transifex.com/freemius/wordpress-sdk/dashboard/).
## Is your SDK RTL compliant?[](#is-your-sdk-rtl-compliant "Direct link to Is your SDK RTL compliant?")
Yes, it is.
## What makes Freemius WordPress.org compliant?[](#what-makes-freemius-wordpressorg-compliant "Direct link to What makes Freemius WordPress.org compliant?")
We’ve built a special PHP preprocessor that looks into your project’s PHP files and uses the SDK calls in your code as annotations to understand what parts of it should be excluded from the free product version. Then, the preprocessor automatically generates a free version of your plugin by striping away all of its premium code. The free version is the version that should be uploaded to WordPress.org, in order to comply with the [WordPress.org guidelines](https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/).
---
# Licensing
## Can I upsell multi-site licenses?[](#can-i-upsell-multi-site-licenses "Direct link to Can I upsell multi-site licenses?")
Yes. You can set up custom *monthly*, *annual* and *lifetime* pricing for any amount of licenses.
## Can I offer a discount on license renewals?[](#can-i-offer-a-discount-on-license-renewals "Direct link to Can I offer a discount on license renewals?")
Yes, you can. Keep in mind though, that our annual and monthly plans renew automatically. This means that customers don’t have to take any proactive action to renew the license. In other words, you don’t need to convince the customer to renew. The decision is whether to cancel the license or continue. Unless a customer is unhappy with your plugin or has stopped using it, a renewal discount has no significant effect on that decision.
Therefore, as a rule of thumb, we do not recommend setting a discount for renewals unless your plugin does not provide a continued value (e.g. a migration plugin, or any one-time action plugin). In that case, maybe it is worth setting up a [lifetime price](https://freemius.com/blog/lifetime-license-for-wordpress-plugins-the-right-way/).
## Will selecting “deactivate license” under user account in WordPress cancel the billing / subscription?[](#will-selecting-deactivate-license-under-user-account-in-wordpress-cancel-the-billing--subscription "Direct link to Will selecting “deactivate license” under user account in WordPress cancel the billing / subscription?")
Deactivating the license will not cancel the subscription.
It’s there for cases in which the customer wants to migrate a license to a different installation (for example, when migrating to a different host or domain).
## Can I offer a paid trial to my users?[](#can-i-offer-a-paid-trial-to-my-users "Direct link to Can I offer a paid trial to my users?")
Yes. You can offer a [Free Trial with a required payment method](https://freemius.com/help/help/documentation/selling-with-freemius/set-up-trials/.md#paid-trial--requiring-a-payment-method) by setting a trial period in your plan settings and then turning on the “Require Credit Card or PayPal” option.
## Can I offer a free trial to my users?[](#can-i-offer-a-free-trial-to-my-users "Direct link to Can I offer a free trial to my users?")
Yes. You can set a [trial period](https://freemius.com/help/help/documentation/selling-with-freemius/set-up-trials/.md#free-trial--without-a-payment-method) in your plan settings. Just make sure to turn the “Require Credit Card or PayPal” option off.
## Does Freemius support prorating?[](#does-freemius-support-prorating "Direct link to Does Freemius support prorating?")
Proration handles the increase or decrease to the subscription price when a price changes during any period of a subscription. By default, [Freemius prorates plan updates](https://freemius.com/help/help/documentation/checkout/features/proration/.md) (upgrades or downgrades).
## What does the license renewal process look like for customers?[](#what-does-the-license-renewal-process-look-like-for-customers "Direct link to What does the license renewal process look like for customers?")
All plans (besides lifetime / one-time plan) are automatically renewed. For annual plans, the customer will receive a friendly reminder email a month prior to the renewal date, giving them enough time to cancel the subscription, if they wish to do so.
## Can I protect my premium offering if the user cancels the trial or if the trial is over and the user didn’t upgrade?[](#can-i-protect-my-premium-offering-if-the-user-cancels-the-trial-or-if-the-trial-is-over-and-the-user-didnt-upgrade "Direct link to Can I protect my premium offering if the user cancels the trial or if the trial is over and the user didn’t upgrade?")
If a user got your premium code via a trial but never paid for a license, you can block the premium logic and protect yourself from trial abuse by using the following code:
```
If ( my_fs()->can_use_premium_code() ) {
// … premium code ...
}
```
By default, all trials are “blocking” to prevent trial abuse. So if the trial is over/expired and the user didn’t upgrade (or canceled the trial), the premium features will be blocked even if you set your plan to keep the features and only block updates and support:

## How do trials work if I have a free version on WordPress.org?[](#how-do-trials-work-if-i-have-a-free-version-on-wordpressorg "Direct link to How do trials work if I have a free version on WordPress.org?")
If you have a free version, after 24 hours, a dismissable admin notice with a trial offer will automatically appear in the WP admin dashboard.

Clicking on the “Start free trial” button will redirect the user to the plugin’s in-dashboard pricing page with the option to start a trial with any of the plans. Once the user selects a plan and starts the trial, the premium version is securely accessible through a download link which will appear in an admin notice and in an automated email.
## How do you handle downloads and file hosting?[](#how-do-you-handle-downloads-and-file-hosting "Direct link to How do you handle downloads and file hosting?")
We securely deliver reliable downloads to valid license holders through our API, utilizing S3 hosting on AWS.
## Do you offer automated recurring/subscription-based billing?[](#do-you-offer-automated-recurringsubscription-based-billing "Direct link to Do you offer automated recurring/subscription-based billing?")
We support automatic renewals, and encourage software makers to sell subscriptions to build long term sustainable businesses. It increases renewal rate, removes the hassle of payment renewal and increases your bottom line. Freemius monthly and annual plans are both automatically renewed.
## I received a subscription notification without a payment notification, what’s going on?[](#i-received-a-subscription-notification-without-a-payment-notification-whats-going-on "Direct link to I received a subscription notification without a payment notification, what’s going on?")
Due to the way PayPal’s recurring profiles mechanism works, it can take up to 24 hours until Freemius receives the approval notification for the subscriptions’ initial payment. To keep a smooth purchase experience for customers, Freemius will email the license key and the download link to the paid product – right away.
To mitigate potential abuse, when a customer subscribes to your product the system will issue a 24-hour blocking license. Once we receive the payment notification from PayPal, the license will be extended based on the subscription billing cycle, and the blocking state of the license will be auto-adjusted based on the plan’s configuration.
We could have designed the upgrade process slightly differently, emailing the downloadable product only after the payment approval, but we decided that we aren’t going to “break” a proper subscription UX just because of a small group of trolls that can abuse this PayPal flaw.
---
# My Account
## Are there any additional fees for receiving payments through PayPal?[](#are-there-any-additional-fees-for-receiving-payments-through-paypal "Direct link to Are there any additional fees for receiving payments through PayPal?")
PayPal payment transfers to U.S.-based accounts are free. For payment transfers to non-U.S. accounts, PayPal incorporates a 2% fee (maximum of $20 USD).
## How to switch the language on the pricing and checkout pages in the dashboard?[](#how-to-switch-the-language-on-the-pricing-and-checkout-pages-in-the-dashboard "Direct link to How to switch the language on the pricing and checkout pages in the dashboard?")
This is a work in progress. Currently, the pricing and checkout pages are only in English. Making them translatable is part of our roadmap.
## Do you support affiliates?[](#do-you-support-affiliates "Direct link to Do you support affiliates?")
Yes we do! Freemius comes with a fully-featured [Affiliation Platform](https://freemius.com/help/help/documentation/affiliate-platform/.md) which you can utilize to onboard and manage affiliates.
## What provision is there for users to export their data if they wanted to leave the platform?[](#what-provision-is-there-for-users-to-export-their-data-if-they-wanted-to-leave-the-platform "Direct link to What provision is there for users to export their data if they wanted to leave the platform?")
Many marketplaces choose to hide their customers’ data from developers and “lock them” inside the platform.
We believe that the data belongs to the developers. If a developer decides to leave the platform for any reason whatsoever, he/she can easily download their user-list from our dashboard with one click, and they can access all of their data by leveraging our RESTful API for migration purposes.
---
# SaaS & Apps
## What kind of products can I sell with Freemius?[](#what-kind-of-products-can-i-sell-with-freemius "Direct link to What kind of products can I sell with Freemius?")
You can sell any Software-as-a-Service (SaaS) or downloadable or static software. This includes for example, any cloud based software application accessed via the web, desktop or mobile apps, Chrome extensions, etc. See the full list [here](https://freemius.com/help/help/documentation/selling-with-freemius/allowed-prohibited-products/.md).
## Do I need a company to start selling my SaaS?[](#do-i-need-a-company-to-start-selling-my-saas "Direct link to Do I need a company to start selling my SaaS?")
No. You can start selling as an individual. Freemius handles payments, taxes, and compliance on your behalf, so you don’t need to:
* Open a company
* Register for VAT
* Set up payment processors
* Many makers start this way and formalize later.
## When should I choose an MoR like Freemius over Stripe or Paddle?[](#when-should-i-choose-an-mor-like-freemius-over-stripe-or-paddle "Direct link to When should I choose an MoR like Freemius over Stripe or Paddle?")
Use an MoR when you want to avoid managing tax compliance, legal liability, and payment operations across regions.
Stripe gives you full control but requires you to handle taxes and compliance; Paddle and Freemius act as MoRs, with Freemius being more tailored to SaaS, plugins, and Software products.
## Can I use Freemius with Lovable?[](#can-i-use-freemius-with-lovable "Direct link to Can I use Freemius with Lovable?")
Yes! Freemius integrates with Lovable using one prompt. See [more details here](https://freemius.com/help/help/documentation/ai/lovable/.md)
You can use that prompt to generate a working checkout + subscription flow inside your Lovable app, including payments, licensing, and [webhooks](https://freemius.com/help/help/documentation/saas/events-webhooks/.md). This lets you skip building billing infrastructure while Freemius handles subscriptions, global taxes, and compliance as a Merchant of Record.
## How does using an MoR change my SaaS or App billing architecture?[](#how-does-using-an-mor-change-my-saas-or-app-billing-architecture "Direct link to How does using an MoR change my SaaS or App billing architecture?")
You offload payments, tax calculation, invoicing, and compliance to the MoR, while your app focuses on subscription logic and access control.
Instead of building complex billing infrastructure (like with Stripe), you integrate a checkout and receive [webhook events](https://freemius.com/help/help/documentation/saas/events-webhooks/.md) for lifecycle changes (purchase, renewal, cancellation).
## Do I lose control over pricing or subscriptions when using an MoR?[](#do-i-lose-control-over-pricing-or-subscriptions-when-using-an-mor "Direct link to Do I lose control over pricing or subscriptions when using an MoR?")
No. You still define pricing, plans, trials, and upgrade/downgrade logic; the MoR executes transactions and ensures compliance.
Using a Merchant of record significantly reduced operational and legal overhead.
## How does an MoR handle global taxes (VAT, GST, sales tax)?[](#how-does-an-mor-handle-global-taxes-vat-gst-sales-tax "Direct link to How does an MoR handle global taxes (VAT, GST, sales tax)?")
The MoR automatically calculates, collects, and remits taxes based on the customer’s location and applicable laws.
This matters if you sell internationally—without an MoR, you’re responsible for tax registration, rate updates, and filings in each jurisdiction.
Example: Selling to EU customers requires VAT handling (OSS/IOSS). An MoR abstracts this entirely.
## What’s the difference between one-time payments and subscriptions?[](#whats-the-difference-between-one-time-payments-and-subscriptions "Direct link to What’s the difference between one-time payments and subscriptions?")
One-time payments charge the customer once for a product or service, while subscriptions charge customers automatically on a recurring schedule (e.g., monthly or yearly).
One-time payments are simpler to implement and don’t require ongoing billing logic. Subscriptions, on the other hand, require handling renewals, upgrades, cancellations, and failed payments—but they better fit SaaS products that deliver continuous value over time.
Example:
* One-time: Pay $49 for lifetime access
* Subscription: Pay $10/month for ongoing access and updates
## How do I control user access after payment?[](#how-do-i-control-user-access-after-payment "Direct link to How do I control user access after payment?")
Freemius notifies your app about subscription events (like purchase, renewal, cancellation) via [webhooks](https://freemius.com/help/help/documentation/saas/events-webhooks/.md) or [API](https://docs.freemius.com/api/). You use those events to:
* Grant access to paid features
* Restrict access if a subscription expires
* Handle upgrades/downgrades
This gives you full control over your app logic, without handling billing complexity.
## What happens when a SaaS subscription payment fails?[](#what-happens-when-a-saas-subscription-payment-fails "Direct link to What happens when a SaaS subscription payment fails?")
Freemius automatically handles failed payments for you. This includes:
* Retry attempts
* Email notifications to the customer
* Updating subscription status
You’ll also get notified via [webhooks](https://freemius.com/help/help/documentation/saas/events-webhooks/.md), so you can adjust access in your app if needed.
## What do I pay Freemius to use the service?[](#what-do-i-pay-freemius-to-use-the-service "Direct link to What do I pay Freemius to use the service?")
Freemius has a progressive revenue share model price of 4.7% of (item price minus gateway fees), meaning we only earn when you do.
---
# Sales
## What payment methods can I accept with Freemius?[](#what-payment-methods-can-i-accept-with-freemius "Direct link to What payment methods can I accept with Freemius?")
We use iDeal (Netherlands), Stripe and PayPal, and therefore accept all major credit cards and PayPal payments. See the list of [supported countries](https://freemius.com/help/help/documentation/selling-with-freemius/supported-countries/.md).
## Do you support coupons?[](#do-you-support-coupons "Direct link to Do you support coupons?")
Yes – we support absolute amounts and percentage based [coupons](https://freemius.com/help/help/documentation/selling-with-freemius/coupon-discount/.md). You can set up the effective date range of a coupon’s validity, whether the discount should apply for all payments or only for the initial payment, and multiple customization options.
## Do I need to set up a gateway to sell with Freemius?[](#do-i-need-to-set-up-a-gateway-to-sell-with-freemius "Direct link to Do I need to set up a gateway to sell with Freemius?")
Nope. No need for that because Freemius serves as your reseller.
## What currencies do you support?[](#what-currencies-do-you-support "Direct link to What currencies do you support?")
With Freemius you can sell and receive payments in USD (US dollars), GBP (British pounds), and EUR (Euros). See the list of [supported countries](https://freemius.com/help/help/documentation/selling-with-freemius/supported-countries/.md).
## Does Freemius store customer credit card numbers on Freemius servers?[](#does-freemius-store-customer-credit-card-numbers-on-freemius-servers "Direct link to Does Freemius store customer credit card numbers on Freemius servers?")
We aren’t crazy 🙂 Credit Cards do not even “touch” our servers and “speak” directly to Stripe, a well trusted and secure gateway.
## Is Freemius Checkout PCI compliant?[](#is-freemius-checkout-pci-compliant "Direct link to Is Freemius Checkout PCI compliant?")
Yes, we are PCI compliant; we use PayPal Express Checkout and Stripe.js, and our checkout is secured with an HTTPS protocol.
## Is Freemius In-App/Dashboard checkout PCI compliant?[](#is-freemius-in-appdashboard-checkout-pci-compliant "Direct link to Is Freemius In-App/Dashboard checkout PCI compliant?")
Yes. Whether the housing site runs securely over HTTPS or not, our In-Dashbord Checkout is loaded securely via a PCI compliant HTTPS iframe.
## Does Freemius support EU VAT?[](#does-freemius-support-eu-vat "Direct link to Does Freemius support EU VAT?")
Yes. Avoid the hassles of EU VAT compliance by having Freemius handle VAT collection, compliance and payments. Utilize support for real-time VAT ID validation and exemption when selling to businesses.
## How much do I have to generate in order volume using Freemius before I can get paid?[](#how-much-do-i-have-to-generate-in-order-volume-using-freemius-before-i-can-get-paid "Direct link to How much do I have to generate in order volume using Freemius before I can get paid?")
The minimum account balance required for payment is $100.00.
## How can I get paid? How do I get my earnings?[](#how-can-i-get-paid-how-do-i-get-my-earnings "Direct link to How can I get paid? How do I get my earnings?")
There are currently four payment methods available for you to collect your earnings from Freemius:
1. PayPal (Default payment, using PayPal MassPay)
2. Payoneer
3. Wire Transfer (International and Domestic / IBAN)
4. TransferWise
International wires can be converted to your local currency. Due to our high volumes we’ve managed to negotiate great terms with our bank for currency conversion-rates and a complete fee waiver on foreign-currency international wire.
## Do you offer specialized pricing for micro-transactions like on Apple’s App Store or Google’s Android Market?[](#do-you-offer-specialized-pricing-for-micro-transactions-like-on-apples-app-store-or-googles-android-market "Direct link to Do you offer specialized pricing for micro-transactions like on Apple’s App Store or Google’s Android Market?")
The recommended minimum price per transaction is $3.99. Otherwise, your processing fee will be as high as 30%-50% due to the minimum per transaction rates charged by Stripe and PayPal. [Email us](mailto:support@freemius.com) to discuss your specific situation.
## Can I customize the in-dashboard pricing and checkout pages?[](#can-i-customize-the-in-dashboard-pricing-and-checkout-pages "Direct link to Can I customize the in-dashboard pricing and checkout pages?")
The pricing page is automatically generated and styled by Freemius, following WordPress admin dashboard design practices. We make sure it looks natural and optimized for best conversation results.
We have years of experience optimizing conversion, and we use data to continuously improve on the pricing page.
We do provide an option to add custom CSS stylesheets to enable personalization.
You can add those stylesheets in ***Plans*** -> **Customization**:

> Note: For now, we do not recommend using custom CSS since changes in the page HTML structure might break your styles. We’ll try to communicate any changes in the HTML DOM. Having said that, and for the sake of agile development, we do not guarantee notifying you about any changes – it’s up to you to monitor changes.
## Can I refund my customers?[](#can-i-refund-my-customers "Direct link to Can I refund my customers?")
You can refund transactions up to 30 days after the transaction was successfully processed and payment was created. We currently only offer full refunds (no partial refunds). If you’d like to refund a customer after the 30 days refund window is over, we recommend resolving the refund request with your own payment services.
## How long will it take until the customer sees the refund?[](#how-long-will-it-take-until-the-customer-sees-the-refund "Direct link to How long will it take until the customer sees the refund?")
While some refunds may be instantaneous, credit refunds can take 5–10 business days to show up in their customer’s credit card statement.
## What happens if a customer disputes a payment / chargeback?[](#what-happens-if-a-customer-disputes-a-payment--chargeback "Direct link to What happens if a customer disputes a payment / chargeback?")
The payment amount will be temporarily be held and we’ll contact you ASAP, giving you the chance to resolve the dispute directly with the customer.
If the dispute isn’t resolved, we’ll step inup and handle each case individually.
* If the dispute is resolved in your favor, you are all good.
* If the dispute is resolved in favor of the customer, the held amount will be refunded to the customer.
* If it was a bank dispute (via Stripe), chargebacks incur a fee of $15 which will be deducted from your balance.
## Do you support affiliates?[](#do-you-support-affiliates "Direct link to Do you support affiliates?")
Yes we do! Freemius comes with a fully-featured [Affiliation Platform](https://freemius.com/help/help/documentation/affiliate-platform/.md) which you can utilize to onboard and manage affiliates.
## Does Freemius support prorating?[](#does-freemius-support-prorating "Direct link to Does Freemius support prorating?")
Proration handles the increase or decrease to the subscription price when a price changes during any period of a subscription. By default, [Freemius prorates plan updates](https://freemius.com/help/help/documentation/checkout/features/proration/.md) (upgrades or downgrades).
## Under the “Sites” menu, what is the difference between “Plan” and “Is Premium”?[](#under-the-sites-menu-what-is-the-difference-between-plan-and-is-premium "Direct link to Under the “Sites” menu, what is the difference between “Plan” and “Is Premium”?")
If you name your paid plan as **“Premium”** you might get a little confused with the terminology. The data under the plan column shows the current plan of the install. **“Is Premium”** tells if that site is running the *free* or the *premium* code version of your module.
## Can I upsell multi-site licenses?[](#can-i-upsell-multi-site-licenses "Direct link to Can I upsell multi-site licenses?")
Yes. You can set up custom *monthly*, *annual* and *lifetime* pricing for any amount of licenses.
## Are there any additional fees for receiving payments through PayPal?[](#are-there-any-additional-fees-for-receiving-payments-through-paypal "Direct link to Are there any additional fees for receiving payments through PayPal?")
PayPal payment transfers to U.S.-based accounts are free. For payment transfers to non-U.S. accounts, PayPal incorporates a 2% fee (maximum of $20 USD).
## What will my customers see on their Credit-Card and PayPal statement?[](#what-will-my-customers-see-on-their-credit-card-and-paypal-statement "Direct link to What will my customers see on their Credit-Card and PayPal statement?")
Customers who purchase your product through Freemius will receive a credit card statement showing your product’s name.
For PayPal transactions, the store name will appear as your product’s name. However, PayPal email notifications will state Freemius and include your product description in the plan details. Unfortunately, PayPal does not offer a way to customize this further.
---
# WordPress
## Is Freemius a WordPress.org Compliant?[](#is-freemius-a-wordpressorg-compliant "Direct link to Is Freemius a WordPress.org Compliant?")
Mos-def! If you are familiar with the [guidelines](https://wordpress.org/plugins/about/guidelines/), you already know that “phoning home” is not allowed without the user's consent. Therefore, we don't capture any information without user opt-in. We probably have the most strict opt-in form in the wp.org repo.
## What makes Freemius WordPress.org compliant?[](#what-makes-freemius-wordpressorg-compliant "Direct link to What makes Freemius WordPress.org compliant?")
We've built a special PHP preprocessor that looks into your project's PHP files and uses the SDK calls in your code as annotations to understand what parts of it should be excluded from the free product version. Then, the preprocessor automatically generates a free version of your plugin by striping away all of its premium code. The free version is the version that should be uploaded to WordPress.org, in order to comply with the [WordPress.org guidelines](https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/).
## Can I use Freemius Insights with EDD or WooCommerce?[](#can-i-use-freemius-insights-with-edd-or-woocommerce "Direct link to Can I use Freemius Insights with EDD or WooCommerce?")
Absolutely YES! There's no collision nor interaction between [Freemius Insights](https://freemius.com/wordpress/insights/) and other eCommerce solutions. [Freemius Insights](https://freemius.com/wordpress/insights/) does not depend on on our monetization solutions. You can add [Freemius Insights](https://freemius.com/wordpress/insights/) to both your free and premium plugin versions.
## Can I use Freemius Insights on CodeCanyon and ThemeForest?[](#can-i-use-freemius-insights-on-codecanyon-and-themeforest "Direct link to Can I use Freemius Insights on CodeCanyon and ThemeForest?")
Yes! It's compliant with the marketplace rules. In fact, a similar analytics product, called *PressTrends*, was widely adapted by *ThemeForest* and *CodeCanyon* developers in early 2014.
## Can I use Insights in my Premium only plugin or theme?[](#can-i-use-insights-in-my-premium-only-plugin-or-theme "Direct link to Can I use Insights in my Premium only plugin or theme?")
Yes, you can! In addition, since you are not obligated to any guidelines, you can capture all the information and skip the opt-in screen. If you do so, you would need to explicitly mention that part in your privacy and terms of use. Having said that, since no one actually reads the privacy and terms, we recommend using the opt-in screen as an ethical transparency act.
## Can I use Freemius with my theme?[](#can-i-use-freemius-with-my-theme "Direct link to Can I use Freemius with my theme?")
Yes! *Freemius* is now fully integrated with themes!
## Can I customize the opt-in screen?[](#can-i-customize-the-opt-in-screen "Direct link to Can I customize the opt-in screen?")
We have crafted special filters to customize the messaging and buttons of the opt-in screen. You can also completely edit the PHP template file in the SDK. Make sure you keep it clear about what information is being captured and that it's sent to *Freemius*. Otherwise, it won't be compliant with the [WordPress.org guidelines](https://wordpress.org/plugins/about/guidelines/).
## Can I customize the in-dashboard pricing and checkout pages?[](#can-i-customize-the-in-dashboard-pricing-and-checkout-pages "Direct link to Can I customize the in-dashboard pricing and checkout pages?")
The pricing page is automatically generated and styled by Freemius, following WordPress admin dashboard design practices. We make sure it looks natural and optimized for best conversation results.
We have years of experience optimizing conversion, and we use data to continuously improve on the pricing page.
We do provide an option to add custom CSS stylesheets to enable personalization.
You can add those stylesheets in ***Plans*** -> **Customization**:

> Note: For now, we do not recommend using custom CSS since changes in the page HTML structure might break your styles. We'll try to communicate any changes in the HTML DOM. Having said that, and for the sake of agile development, we do not guarantee notifying you about any changes – it's up to you to monitor changes.
## Do you support coupons?[](#do-you-support-coupons "Direct link to Do you support coupons?")
Yes – we support absolute amounts and percentage based [coupons](https://freemius.com/help/help/documentation/selling-with-freemius/coupon-discount/.md). You can set up the effective date range of a coupon's validity, whether the discount should apply for all payments or only for the initial payment, and multiple customization options.
## How do you handle downloads and file hosting?[](#how-do-you-handle-downloads-and-file-hosting "Direct link to How do you handle downloads and file hosting?")
We securely deliver reliable downloads to valid license holders through our API, utilizing S3 hosting on AWS.
## How do trials work if I have a free version on WordPress.org?[](#how-do-trials-work-if-i-have-a-free-version-on-wordpressorg "Direct link to How do trials work if I have a free version on WordPress.org?")
If you have a free version, after 24 hours, a dismissable admin notice with a trial offer will automatically appear in the WP admin dashboard.

Clicking on the “Start free trial” button will redirect the user to the plugin's in-dashboard pricing page with the option to start a trial with any of the plans. Once the user selects a plan and starts the trial, the premium version is securely accessible through a download link which will appear in an admin notice and in an automated email.
## Is your SDK RTL compliant?[](#is-your-sdk-rtl-compliant "Direct link to Is your SDK RTL compliant?")
Yes, it is.
## Will selecting “deactivate license” under user account in WordPress cancel the billing / subscription?[](#will-selecting-deactivate-license-under-user-account-in-wordpress-cancel-the-billing--subscription "Direct link to Will selecting “deactivate license” under user account in WordPress cancel the billing / subscription?")
Deactivating the license will not cancel the subscription. It's there for cases in which the customer wants to migrate a license to a different installation (for example, when migrating to a different host or domain).
## What languages is your WordPress SDK translated to?[](#what-languages-is-your-wordpress-sdk-translated-to "Direct link to What languages is your WordPress SDK translated to?")
You can check out all of the supported languages and help us translate the SDK (we know you want to be on our hall of fame 😉 ) on our [Transifex translations manager](https://app.transifex.com/freemius/wordpress-sdk/dashboard/).
## Can I protect my premium offering if the user cancels the trial or if the trial is over and the user didn't upgrade?[](#can-i-protect-my-premium-offering-if-the-user-cancels-the-trial-or-if-the-trial-is-over-and-the-user-didnt-upgrade "Direct link to Can I protect my premium offering if the user cancels the trial or if the trial is over and the user didn't upgrade?")
If a user got your premium code via a trial but never paid for a license, you can block the premium logic and protect yourself from trial abuse by using the following code:
```
If ( my_fs()->can_use_premium_code() ) {
// … premium code ...
}
```
By default, all trials are “blocking” to prevent trial abuse. So if the trial is over/expired and the user didn't upgrade (or canceled the trial), the premium features will be blocked even if you set your plan to keep the features and only block updates and support:

## Can I offer a discount on license renewals?[](#can-i-offer-a-discount-on-license-renewals "Direct link to Can I offer a discount on license renewals?")
Yes, you can. Keep in mind though, that our annual and monthly plans renew automatically. This means that customers don't have to take any proactive action to renew the license. In other words, you don't need to convince the customer to renew. The decision is whether to cancel the license or continue. Unless a customer is unhappy with your plugin or has stopped using it, a renewal discount has no significant effect on that decision.
Therefore, as a rule of thumb, we do not recommend setting a discount for renewals unless your plugin does not provide a continued value (e.g. a migration plugin, or any one-time action plugin). In that case, maybe it is worth setting up a [lifetime price](https://freemius.com/blog/lifetime-license-for-wordpress-plugins-the-right-way/).
## Why is the active installs metric on WordPress.org different than the one on Freemius?[](#why-is-the-active-installs-metric-on-wordpressorg-different-than-the-one-on-freemius "Direct link to Why is the active installs metric on WordPress.org different than the one on Freemius?")
There's a fundamental difference in the way WordPress.org and Freemius count active installs.
Based on publicly available responses from key people in the WordPress meta team, the WordPress.org active installs counter relies on the WordPress.org updates mechanism, which is sampled on a weekly basis. Updates are triggered only when there's traffic to the site, so if a site installed your plugin or theme and did not get any traffic during that sampling period OR, if the updates mechanism is blocked (or turned off), this site will not get counted. Also, we suspect that the WordPress.org sampling relies on domains and not on IPs. In that case, for example, all of the VVV installs that installed your product will only be counted as a single site.
On the other hand, Freemius uses real actions such as an explicit opt-in or deactivation/uninstall events. One fallback of that tracking methodology is if a user hard-deletes the product from the filesystem, using FTP/SSH, it is still considered active.
---