Installation
SDK 3.0 ships as an npm package and renders without an iframe in a Shadow DOM. For web you can also load it via ESM CDN (esm.sh/unpkg/jsDelivr); for Chrome extensions the only option is the bundled npm package (CWS review forbids remote code).
Website / SPA
npm (recommended)
pnpm add @monetize.software/sdk
# or: npm i @monetize.software/sdk / yarn add @monetize.software/sdkimport { PaywallUI } from '@monetize.software/sdk/ui';
const paywall = new PaywallUI({
paywallId: '3',
apiOrigin: 'https://YOUR_DOMAIN'
});
paywall.open(); // native Shadow DOM, no iframeChrome Extension (MV3)
For extensions there’s a dedicated package @monetize.software/sdk-extension. It wraps SDK 3.0 in an offscreen document so a single state (auth session, bootstrap cache, trial counter, analytics) is shared across the popup, content scripts in every tab, and the service worker.
Working reference extension:
github.com/monetize-software/sdk-examples/tree/main/browser-extension
— full MV3 setup: SW + offscreen + popup + content-script, floating
Shadow-DOM widget, ApiGatewayClient with quota-driven auto-open, and a
two-config vite build (ESM for SW/offscreen/popup, IIFE for content).
Clone, set two env vars, npm install && npm run build, load dist/
in chrome://extensions.
pnpm add @monetize.software/sdk-extension preactWhen to use @monetize.software/sdk-extension vs plain @monetize.software/sdk:
| Scenario | Package |
|---|---|
| Extension with popup + content scripts across tabs — sign-in in the popup should reflect on every tab instantly | @monetize.software/sdk-extension |
| Popup only, no content scripts; or only one extension page (options) | @monetize.software/sdk — sufficient |
Minimal manifest.json
{
"manifest_version": 3,
"name": "My Extension",
"version": "1.0.0",
"permissions": ["offscreen", "storage"],
"host_permissions": ["https://YOUR_DOMAIN/*"],
"background": { "service_worker": "sw.js", "type": "module" }
}Optional permissions:
"content_scripts"— if you want to render the paywall on third-party pages (not just the popup).
OAuth (Google / Apple / etc) does not require the "identity" permission — the SDK opens a popup window against your apiOrigin instead of using chrome.identity.
host_permissions — what to pick
host_permissions control two things: where the extension can fetch (from offscreen / SW / content-script) and which origins the content-script can inject into (together with content_scripts.matches).
This is the primary signal for Chrome Web Store review and AV vendors (Avast/Kaspersky/Norton). The narrower host_permissions, the less suspicion.
| Extension host scenario | Recommendation |
|---|---|
Extension already needs <all_urls> (recorder, all-site assistant, tool like Grammarly) | Keep <all_urls>. SDK works as-is. Note: CWS review with <all_urls> goes through a manual audit and takes longer; AV vendors are more likely to flag such extensions as PUA. That’s the cost of broad injection, not an SDK risk. |
| Extension only talks to your backend and paywalls its own features (popup tool, side-panel app) | Do NOT request <all_urls>. Your apiOrigin is enough: ["https://api.your-domain.com/*"]. No content-script injection on every site needed. |
| Hybrid — popup tool plus content script on a narrow list of domains | Constrain host_permissions + content_scripts.matches to those domains: ["https://*.your-target.com/*", "https://api.your-domain.com/*"]. |
Keep <all_urls> only when it’s genuinely needed for UX, and be ready to justify it in the CWS “Permission justification” field. “So the paywall works” is a bad justification; the SDK itself doesn’t require <all_urls>.
Initializing the three contexts
// service-worker.ts
import { installRouter } from '@monetize.software/sdk-extension/sw';
installRouter({ offscreenUrl: chrome.runtime.getURL('offscreen.html') });// offscreen.ts (loaded from offscreen.html)
import { startOffscreenServer } from '@monetize.software/sdk-extension/offscreen';
startOffscreenServer({
paywallId: '3',
apiOrigin: 'https://YOUR_DOMAIN',
auth: true
});// content-script.ts (or popup.ts / options.ts)
import { PaywallUI } from '@monetize.software/sdk-extension';
const paywall = new PaywallUI({
paywallId: '3',
apiOrigin: 'https://YOUR_DOMAIN',
auth: true
});
paywall.open(); // same API as plain @monetize.software/sdkUnder the hood, content-script PaywallUI is a proxy via a chrome port into the offscreen document. All network traffic, auth refresh, and storage happen in offscreen; content only paints UI.
Telegram Mini App
Use the same @monetize.software/sdk you’d use on the web — a Telegram WebView is a regular browser context. Bundle size matters (Mini App start is blocked by load), so import narrowly:
import { PaywallUI } from '@monetize.software/sdk/ui'; // not from the full rootOAuth inside Telegram has its own quirks — see Authentication → OAuth.