Plug-in licensing
Sell a native plug-in with Maru activation in one command: seats, trials, offline grace, refunds, tester copies and the dry-through gate, with no licensing code in your module.
Maru can license the VST3, AU, CLAP and standalone builds of your module. Buyers sign in once from the plug-in, approve the computer in their browser, and keep working offline. You choose how many computers a license covers and how long the trial runs. Maru issues, checks and withdraws the licenses. When a purchase is refunded, its activations end at the plug-in's next check.
You write no licensing code. The plug-in shell handles activation, background checks, the activation panel and the audio gate.
Quick start
In your module's folder:
maru module deploy # once, if you have not deployed yet: links the module to your Studio project maru module license on # turns licensing on and binds the build maru plugin build --release # every release build now asks buyers to activate
maru module license on finds your Studio project by the module's id (the
id in defineEffect or defineInstrument). It turns on licensing with the
defaults: 3 computers per license and a 14-day trial. Then it writes one line
into your project:
// package.json
"maru": {
"entry": "src/index.ts",
"license": { "provider": "maru", "projectId": "<your project id>" }
}
For a project with a plugin.config.json, the line goes there instead, as
"commercialLicense": { "provider": "maru", "projectId": "…" }. Commit it with
your code.
Choose other limits with flags:
maru module license on --seats 2 --trial 0
The flags set computers per license (1–10) and trial days (0, the default 14, or up to 30). You can change these at any time without rebuilding. A plug-in that is already activated keeps its seat, and trials already started keep their length.
You can also do all of this in Studio: open your module, then Settings › Plug-in licensing. It shows the same settings and the command to copy.
Development builds stay unlicensed
Only release builds (maru plugin build --release) include licensing.
Development builds stay unlicensed so they process audio without an
activation:
maru plugin buildwithout--release;maru dev;- format checks.
The build log says which kind it made.
| You want to | Do this |
|---|---|
| Try activation in a development build | MARU_LICENSE=1 maru plugin build |
| Build a release without licensing (an internal or free build) | MARU_LICENSE=0 maru plugin build --release |
A release build refuses to compile without Maru's public signing keys, so a licensed build can never accept a forged license.
Sell it and test it
-
Price it. Set the price on your module's listing as for any product. A purchase grants the buyer the module, and that grant is their license. A refund removes it.
-
Test it yourself. As the module's creator, you own its license without buying it. Activate a release build in your DAW as a buyer would. The same seat limit applies to you.
-
Give testers a copy:
maru module license grant tester@example.com maru module license revoke tester@example.com
The tester needs a Maru account. A module can have 25 testers, and Studio lists them under Plug-in licensing. Revoking a tester's copy stops their computers at the next check, unless they have since bought it.
Check the numbers:
maru module license status
It shows your settings, live seats (owned and trial), trials started, activations and refusals over 30 days by format, and your testers. It also checks that the binding in your project matches the project on Maru.
You see counts only. Buyers' computers, countries and networks stay private.
To stop licensing, run maru module license off. It turns licensing off and
removes the binding. A binding left behind would build a plug-in nobody can
activate.
Show the license in your face (optional)
The shell draws its own activation badge and panel, so a face needs nothing.
If you want the state in your own design, read license from the face props.
It is present only in a licensed native build:
import type { AudioModuleFaceProps } from "maruaudio/contracts"
export function Face({ license, ...props }: AudioModuleFaceProps) {
return (
<header>
{license?.state.status === "trial" ? (
<button onClick={license.activate}>Trial · {license.state.daysLeft} days left — Buy</button>
) : null}
{license && !license.active ? <span>Not activated — audio passes through</span> : null}
</header>
)
}
license.state.status is one of:
unchecked,unlicensed,licensed,trial;grace(offline 16–30 days);expired,invalid,unavailable.
license.active is true while the plug-in processes audio. The actions are:
activate();cancel();deactivate();refresh()("Check now");openAccount().
Each action is answered by a new license. Display fields cover:
- the trial's days left;
- seats used and total;
- which computers hold the seats when they are all used.
No token, key or account id ever reaches the face.
Check a license from your own server
Use this for an update server, a download gate for extra content, or a
support tool. A buyer's plug-in holds a maru-license/1 token.
Live check: asks Maru whether the token is genuine and still held now. It needs no account and names no person:
curl -s https://maruaudio.com/api/v1/licenses/verify \
-H 'content-type: application/json' \
-d '{ "token": <token>, "sku": "creator:module:<project id>" }'
The answer is { "ok": true, "result": { "valid": true, "license": { … } } }.
When valid is false, reason is one of:
invalid_tokenorexpired;ended(the computer was removed or deactivated);not_entitled(refunded);wrong_skuorwrong_bundle.
Offline check: verifies the signature and dates without a network call, in Node 20+, Workers, Deno or a browser:
import { verifyLicenseToken } from "@maruaudio/platform-auth-contract"
const keys = [{ kid: "prod-2026-10", x963: "04add2c939…" }] // license-keys.json, set "production"
const result = await verifyLicenseToken(token, keys, { sku: "creator:module:<project id>" })
if (result.ok && result.status !== "expired") serveDownload()
An offline check cannot see a refund or a removed computer until the token expires (at most 30 days). Use the live check when that matters.
What your buyers see
| State | What the plug-in does |
|---|---|
| Not activated | An Activate badge. An effect passes audio through unchanged. |
| Trial | A Trial · N days left badge, full processing. One trial per account. |
| Licensed | Full processing and a small License button (deactivate, manage computers). |
| Offline 16–30 days | Still working, with a reminder to go online. |
| Offline over 30 days, refunded, or removed | Passes audio through unchanged until it checks again. |
The activation panel lives in the plug-in window. It belongs to the shell, so it looks the same in every Maru plug-in and does not touch your face's code. Approval happens in the system browser. No password or token ever enters the plug-in window.
Buyers see and remove their computers at Plug-in activations.
Unactivated instruments keep full processing for now. Only effects have the dry-through gate. Ask before shipping a licensed instrument.
How a license works
-
Activation. The plug-in creates its own random key and signs in as the
maru-pluginclient, with scopelicenses:activateonly. It then asks/api/v1/licenses/activatefor a seat. The server checks two things:- that the account owns the product, or still has its trial;
- that a seat is free.
-
The license token. The server returns a
maru-license/1token, signed with Maru's key and bound to that computer's key. The plug-in checks it offline against public keys compiled into the build. A copied token does nothing on another computer. -
Renewal. The plug-in renews about once a day in the background. Each request is signed by the plug-in's own key, so the account sign-in is not needed again. Ownership is checked on every renewal, so a refund ends the license:
- within a day, for a computer that is online;
- within 30 days, for one that never connects.
-
Privacy. Maru stores:
- a hash of the device key;
- a one-way digest of the computer's hardware id (the id itself never leaves the computer);
- the computer name;
- the country and network of each check.
It never stores IP addresses. See the Privacy Policy.
Building outside the Maru shell
The licensing wire contract is public in @maruaudio/platform-auth-contract
(plugin-license.ts). A host other than the Maru plug-in shell can implement
it:
| Call | Auth | Body | Answer |
|---|---|---|---|
POST /api/v1/licenses/activate | Bearer syn_… with licenses:activate | sku, bundle, devicePublicKey (P-256 point, base64url), componentDigest, platform, label, os, format, clientVersion | { token, seats } |
POST /api/v1/licenses/refresh | device proof | token, proof, devicePublicKey | { token, seats } |
POST /api/v1/licenses/deactivate | device proof | token, proof, devicePublicKey | { ended: true } |
POST /api/v1/licenses/verify | none | token, optional sku, bundle | { valid, reason?, license? } |
Every answer is { ok: true, result } or
{ ok: false, error: { code, message } }. Codes include:
seat_limit(with the computers holding the seats);trial_usedandnot_entitled;revokedandseat_removed;rate_limited.
The signature rules:
- Tokens and proofs are signed with ECDSA P-256 / SHA-256 as raw 64-byte
r‖s, over the ASCII string
maru-license/1\n<base64url payload>. Proofs usemaru-license-req/1\n…instead. - A proof's payload is
{ op, lid, ts, nonce }.tsmust be within 5 minutes of server time, and a nonce works once. - Verify the token against the production public key in
packages/platform-auth-contract/license-keys.json, or withverifyLicenseToken.
The golden vectors in packages/platform-auth-contract/vectors/ show the
exact bytes.
Support and limits
- Sales and refunds: your Earnings page lists them. A refund ends that buyer's activations.
- A buyer locked out of a computer: they remove it at Plug-in activations and activate again. Maru support can also free a seat for them.
- Platforms: macOS (Apple silicon and Intel) for now. Windows and offline activation files are planned.