Figma Plugins with License Keys
Want to earn a few bucks on your Figma plugin? Sell access to your plugin using Polar, with the License Key benefit.
Prerequisites
- A Figma account
- A Polar account
- A Figma plugin with React & Typescript configured
Polar Figma Plugin Example
We've created a simple example Figma plugin that you can use as a reference
Creating a product with License Key Benefit
The best way to monetize your Figma plugin is to use a License Key Benefit. This benefit type allows you to sell access to your plugin to users.
The flow would look something like this:
- User installs your plugin
- User gets a prompt to enter a license key
- User buys a license key from your Polar product page
- User adds the license key to your plugin
- User can now use your plugin & you've made a few bucks!
Creating a product
Head over to your Polar dashboard and click on Products in the sidebar. Then click on New Product. Fill in the details for your product.
Adding the License Key Benefit
Once you've entered product details and pricing information, you'll see a section for Benefits. Expand the License Key tab & press Create New.
This will expand the License Key modal, where you can configure the license key settings. Configure how the license key should be used in your plugin.
Perfect! You've now created a product, which grants an associated license key to the customer.
If you need help configuring the license key settings, you can read more about it in the License Key Settings Guide.
Configuring the Figma Plugin Manifest
In order to communicate with the Polar API, you'll need to configure the Figma plugin manifest & add the polar domain to the allowed domains list.
{
"name": "...",
"id": "...",
"api": "1.0.0",
"main": "code.js",
"capabilities": [],
"enableProposedApi": false,
"documentAccess": "dynamic-page",
"editorType": [
"figma"
],
"ui": "ui.html",
"networkAccess": {
"allowedDomains": [
"https://*.polar.sh"
],
"reasoning": "This plugin needs to access the Polar API to validate license keys."
}
}
Installing the Polar SDK
Go to your Figma plugin code and install the Polar SDK.
npm install @polar-sh/sdk
Configuring the Polar SDK Client
You instantiate the Polar SDK client by creating a new instance of the Polar
class. You do not need to provide a personal access token to validate license keys.
// src/main/polar.ts
import { Polar } from '@polar-sh/sdk'
export const polar = new Polar()
UI
Go ahead and add an input field to your Figma plugin UI where users can enter their license key.
This example uses TailwindCSS for styling and React hooks for state management.
// src/ui/components/LicenseKeyInput.tsx
import type { Polar } from "@polar-sh/sdk";
import type {
LicenseKeyActivationRead,
ValidatedLicenseKey,
} from "@polar-sh/sdk/models/components";
import React, { useState, useCallback } from "react";
import { twMerge } from "tailwind-merge";
import { polar } from "../polar";
export interface LicenseKeyInputProps {
organizationId: string;
needsActivation?: boolean;
onValidation?: (validation: ValidatedLicenseKey) => void;
onActivation?: (activation: LicenseKeyActivationRead) => void;
}
export const LicenseKeyInput = ({
organizationId,
needsActivation,
onActivation,
onValidation,
}: LicenseKeyInputProps) => {
const [licenseKey, setLicenseKey] = useState("");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const handleError = useCallback((error: string) => {
setError(error);
}, []);
const handleActivation = useCallback(async () => {
setError(null);
setLoading(true);
try {
const activation = await polar.users.licenseKeys.activate({
key: licenseKey,
organizationId: organizationId,
label: "Figma Plugin",
});
onActivation?.(activation);
} catch (error) {
if (error instanceof Error) {
handleError(error.message);
}
} finally {
setLoading(false);
}
}, [licenseKey, organizationId, onActivation, handleError]);
const handleValidation = useCallback(async () => {
setError(null);
setLoading(true);
try {
const validation = await polar.users.licenseKeys.validate({
key: licenseKey,
organizationId: organizationId,
});
onValidation?.(validation);
} catch (error) {
if (error instanceof Error) {
handleError(error.message);
}
} finally {
setLoading(false);
}
}, [onValidation, licenseKey, organizationId, handleError]);
return (
<div className="flex flex-col gap-y-4 w-full">
<input
className="bg-gray-100 py-2 px-4 rounded-xl w-full"
placeholder="License Key"
value={licenseKey}
onChange={(e) => setLicenseKey(e.target.value)}
/>
{error && (
<div className="flex rounded-lg px-4 py-2 bg-red-50">
<p className="text-red-500 text-xs">License key is invalid</p>
</div>
)}
<button
className={twMerge(
"text-white py-2 px-4 rounded-xl",
loading ? "bg-gray-300" : "bg-blue-500",
)}
onClick={needsActivation ? handleActivation : handleValidation}
type="button"
disabled={loading}
>
{loading ? "Validating..." : "Validate"}
</button>
</div>
);
};
Activate License Key
In case you've setup license keys to have a maximum amount of activation instances, e.g associated Figma Accounts. You'll then need to create an activation instance prior to validating license keys.
This is why we've added the needsActivation
prop to the LicenseKeyInput
component.
No activation limit? You can pass false
to the needsActivation
prop.
Acticating a license key will automatically validate it, so it doesn't need to be validated separately.
Authenticated and Unauthenticated UI
Let's create two different UI components, one for when the user has a validated License Key and is considered Authenticated
, and one for when the user is not.
// src/ui/components/Authenticated.tsx
import * as React from "react";
// This is where you can add what you want the authenticated user to see
export const Authenticated = () => {
return (
<div className="flex flex-col items-center h-full w-full">
<h1 className="text-lg font-medium">Thanks for supporting this plugin</h1>
</div>
);
};
// src/ui/components/Unauthenticated.tsx
import type {
LicenseKeyActivationRead,
ValidatedLicenseKey,
} from "@polar-sh/sdk/models/components";
import { polar } from "../polar";
import { LicenseKeyInput } from "./LicenseKeyInput";
export interface UnauthenticatedProps {
organizationId: string;
onValidation: (
validation: ValidatedLicenseKey | LicenseKeyActivationRead,
) => void;
}
export const Unauthenticated = ({
organizationId,
onValidation,
}: UnauthenticatedProps) => {
return (
<div className="flex flex-col items-center justify-center h-full gap-y-16">
<div className="flex flex-col gap-y-2 items-center">
<h3 className="text-lg font-medium">Polar License Key Example</h3>
<p className="text-sm text-gray-500">
Enter your license key to continue
</p>
</div>
<LicenseKeyInput
organizationId={organizationId}
onValidation={onValidation}
onActivation={onValidation}
// Set to true if your configured Polar License Key has a limit of activations
needsActivation={false}
/>
</div>
);
};
Gatekeeping the UI
In order to gatekeep the UI, between the authenticated and unauthenticated UI, we need some logic to check if the user has a valid license key.
We can create an entry point in a file called main.tsx
for this example.
// src/ui/main.tsx
import React, { useState } from "react";
import type {
LicenseKeyActivationRead,
ValidatedLicenseKey,
} from "@polar-sh/sdk/models/components";
import { Authenticated } from "./components/Authenticated";
import { Unauthenticated } from "./components/Unauthenticated";
import { createRoot } from "react-dom/client";
import "./input.css";
// Replace this with your actual organization ID
// It can be found in your Polar Organization Settings page
const ORGANIZATION_ID = "<YOUR_ORGANIZATION_ID>";
function Plugin() {
const [validation, setValidation] = useState<ValidatedLicenseKey | LicenseKeyActivationRead>();
return (
<div className="flex flex-col h-full w-full bg-white p-8">
{validation ? (
<Authenticated />
) : (
<Unauthenticated
organizationId={ORGANIZATION_ID}
onValidation={setValidation}
/>
)}
</div>
);
}
const container = document.getElementById("root") ?? document.createElement("div");
const root = createRoot(container);
root.render(<Plugin />);
Persisting License keys
We recommend persisting the license key in the Figma's client storage. This way, the user doesn't have to re-enter the license key on every Figma session. This can be done in the "plugin"-code, which runs sandboxed and has access to the Figma API.
The Figma API has a clientStorage
API, which allows you to persist data in the Figma client. This data is unique to the Figma client, and isn't shared between different Figma clients. This is useful if you want to sell license keys that has a limit of activations per user. The user will then only be able to activate the license key once per Figma client.
Communicating with the Figma Plugin from the UI
As mentioned earlier, the Figma plugin code runs in a sandboxed environment and has access to the Figma API. This means that you need to communicate with the Figma plugin via the postMessage API to access the clientStorage API from the UI.
Let's implement a simple example where the UI code posts a message to the plugin code to save the license key to the clientStorage API.
// src/main/main.ts
figma.showUI(__html__, { themeColors: true, height: 480, width: 360 });
interface GetLicenseKey {
type: "getLicenseKey";
data: null;
}
interface SetLicenseKey {
type: "setLicenseKey";
data: string;
}
figma.ui.onmessage = async (message: GetLicenseKey | SetLicenseKey) => {
switch (message.type) {
case "getLicenseKey":
const licenseKey = await figma.clientStorage.getAsync("license_key");
figma.ui.postMessage({
type: "getLicenseKey",
data: licenseKey,
});
break;
case "setLicenseKey":
await figma.clientStorage.setAsync("license_key", message.data);
figma.ui.postMessage({
type: "setLicenseKey",
data: message.data,
});
break;
}
};
Now, let's update the UI code to listen for messages from the plugin code and persist the license key in the clientStorage API.
// src/ui/main.tsx
import { polar } from "./polar";
import React, { useCallback, useEffect, useState } from "react";
import type {
LicenseKeyActivationRead,
ValidatedLicenseKey,
} from "@polar-sh/sdk/models/components";
import { Authenticated } from "./components/Authenticated";
import { Unauthenticated } from "./components/Unauthenticated";
import { createRoot } from "react-dom/client";
import "./input.css";
// Replace this with your actual organization ID
// It can be found in your Polar Organization Settings page
const ORGANIZATION_ID = "<YOUR_ORGANIZATION_ID>";
function Plugin() {
const [validation, setValidation] = useState<ValidatedLicenseKey | LicenseKeyActivationRead>();
const onValidation = useCallback(
(licenseKeyValidation: ValidatedLicenseKey | LicenseKeyActivationRead) => {
const key = "key" in licenseKeyValidation ? licenseKeyValidation.key : licenseKeyValidation.licenseKey;
parent.postMessage({ pluginMessage: { type: "setLicenseKey", data: key } }, "*");
setValidation(licenseKeyValidation);
},
[],
);
const initialize = useCallback(async () => {
window.onmessage = async (e) => {
const persistedLicenseKey = e.data.pluginMessage.data;
switch (e.data.pluginMessage.type) {
case "getLicenseKey":
if (persistedLicenseKey) {
const validation = await polar.users.licenseKeys.validate({
organizationId: ORGANIZATION_ID,
key: persistedLicenseKey,
});
setValidation(validation);
}
break;
}
};
parent.postMessage({ pluginMessage: { type: "getLicenseKey", data: null } }, "*");
}, []);
useEffect(() => {
initialize();
}, [initialize]);
return (
<div className="flex flex-col h-full w-full bg-white p-8">
{validation ? (
<Authenticated />
) : (
<Unauthenticated
organizationId={ORGANIZATION_ID}
onValidation={onValidation}
/>
)}
</div>
);
}
const container = document.getElementById("root") ?? document.createElement("div");
const root = createRoot(container);
root.render(<Plugin />);
Conclusion
You've now created a Figma plugin that can validate license keys using the Polar SDK. You can now go forth and monetize your plugin! 🎉