Skip to content
Oxrion
← Blog

How to license a WordPress plugin per domain

Per-site licenses for WordPress plugins: the two models that work, how to verify a signed domain license in PHP, the GPL question you need to answer first, and the WordPress-specific gotchas.

By Oxrion · 4 min read

Selling a premium WordPress plugin usually means one license per site. A customer buys a license for shop.example.com, and that’s where the plugin should work — or at least where they get updates and support.

Before writing any code, there’s a question that decides which approach you can use at all: what license is your plugin under?

First: the GPL question

Most WordPress plugins are licensed under the GPL, like WordPress itself. The WordPress community’s GPL FAQ draws a clear line for distributed plugins:

  • Selling support and updates per site or per year doesn’t conflict with the GPL.
  • Requiring customers to buy a license to use the plugin on a set number of sites, or for a set time, does.

The GPL also gives everyone who receives the code the right to its source and to change it. That leaves two workable models.

Model A — GPL plugin, license keys for updates and support. The plugin’s code stays readable and runs anywhere; the license key unlocks automatic updates and support for the domain it was bought for. This is how most premium plugins sold to the general public work.

Model B — your own license, enforced per domain. If your code isn’t distributed under the GPL — for example, it’s licensed under your own commercial terms — you can restrict where it runs and protect the code so that restriction can’t simply be deleted. Whether a given WordPress plugin can be licensed this way is debated; if you’re unsure, get advice from a lawyer who knows software licensing.

Note also that the WordPress.org plugin directory doesn’t accept obfuscated code or functionality locked behind payment. Protected or per-domain-locked plugins are sold outside it.

The rest of this guide covers both models.

Model A: license keys with a license server

The usual setup is a license server — your own, or a licensing add-on for the store you sell through. The customer enters their key in the plugin’s settings; the plugin sends the key and the site’s domain to your server, which records the activation and answers whether updates are available.

Pros: you can see and revoke activations centrally, and it fits the GPL model. Cons: it depends on your server being online, and because the code is readable, the check can be removed — which is acceptable here, since the key gates updates, not use.

Model B: signed domain licenses, verified offline

For Model B you want licenses that a customer can’t forge or edit, and that the plugin can verify without calling home. Public-key signatures do exactly this: you sign a small license (domain + expiry) with a private key only you have, and the plugin verifies it with the matching public key.

PHP has this built in through the sodium extension, and WordPress ships a compatibility layer for it as well. Signing a license, on your own machine:

<?php
// Run once, keep $secret private, embed $public in the plugin.
$keypair = sodium_crypto_sign_keypair();
$secret  = sodium_crypto_sign_secretkey($keypair);
$public  = sodium_crypto_sign_publickey($keypair);

$license   = json_encode(['host' => 'shop.example.com', 'expires' => '2027-10-01']);
$signature = sodium_crypto_sign_detached($license, $secret);

file_put_contents('license.lic', base64_encode($license) . '.' . base64_encode($signature));

Verifying it inside the plugin:

<?php
function myplugin_license_is_valid(string $file, string $public): bool
{
    $raw = @file_get_contents($file);
    if ($raw === false || substr_count($raw, '.') !== 1) {
        return false;
    }
    [$payload, $signature] = array_map('base64_decode', explode('.', trim($raw)));
    if (!$payload || strlen((string) $signature) !== SODIUM_CRYPTO_SIGN_BYTES) {
        return false;
    }
    if (!sodium_crypto_sign_verify_detached($signature, $payload, $public)) {
        return false; // edited or forged
    }

    $license = json_decode($payload, true);
    $host    = strtolower((string) wp_parse_url(home_url(), PHP_URL_HOST));
    if (($license['host'] ?? '') !== $host) {
        return false; // licensed for a different site
    }
    if (!empty($license['expires']) && strtotime($license['expires'] . ' 23:59:59 UTC') < time()) {
        return false; // expired
    }
    return true;
}

This is solid cryptography: without your private key, nobody can create a valid license or change the domain in one.

The weak spot is the plugin itself. If its code is readable, a customer doesn’t need to forge a license — they can delete the call to myplugin_license_is_valid(). In Model B, the licensing check is only as strong as the protection of the code around it. That’s why encoders combine the two: the files are encrypted and signed, so the check can’t be found and removed without breaking them.

WordPress gotchas

Whichever model you use, these catch people out:

  • www and non-www are different hosts. If a site answers on both example.com and www.example.com, decide whether a license covers both, and normalize the host before comparing.
  • Staging and local copies. Customers test on staging.example.com or localhost. Issue a free development license for those hosts, or allow them explicitly, so you don’t get support tickets on day one.
  • Multisite. Subdomain networks have one host per site. Decide whether one license covers the whole network or each site.
  • Which host to check. home_url() reflects WordPress’s configured address; the request’s Host header reflects how the visitor reached the site. Pick one and be consistent.
  • Moving domains. Customers change domains. Make re-issuing a license for the new host quick, or you’ll do it by hand.

Doing Model B with Oxrion

Oxrion does the protection and the licensing together:

  1. Create a license keypair in the Vault of the free Oxrion Desktop app. The private key never leaves your computer.
  2. Protect the plugin’s PHP files with the License option (on paid plans or with credits), using the public key. Files are encrypted and signed; a modified file refuses to run.
  3. Issue a license per customer host in the Licenser — shop.example.com, with an optional expiry. It’s signed offline and saved as a small .lic file.
  4. The customer puts the .lic file in the project’s root folder (for a WordPress plugin, the plugin’s folder or the WordPress root). The free Oxrion loader checks it on every request for the host the site is reached on.

License History keeps a searchable record of every license you issue, renews them in one click, and can email them to customers through your own email account. See Licenses in the docs for the details, including how hosts are matched.

Oxrion protects any PHP — it’s up to you to make sure your plugin’s license allows it. If it does, you can try it on the free plan.

Try Oxrion on the free plan

20 files a month, licensing included. No credit card.