Once your product has a price, something has to enforce who’s actually paid for it. That’s a license key system: a way to issue a key at purchase time, let the plugin activate against that key, tie the activation to a specific site, and check it periodically so updates and support stay tied to a valid license. You can build this yourself, and plenty of developers do, but two real, well-established services already solve most of it. This lesson covers both neutrally, plus what a license key system needs to do functionally regardless of which path you take.
This lesson assumes you’ve settled on a pricing model from Lesson 2. It doesn’t require you to have picked EDD or Freemius yet, that’s exactly the decision this lesson is for.
Step 1: What a license key system has to do, regardless of vendor
Strip away any particular product and a license key system is really four functions:
Everything else, renewal reminders, upgrade paths, refund handling, is built on top of these four functions. Whether you build them yourself against a custom REST endpoint, or use a service that already provides them, the plugin-side code your customers run looks broadly similar.
Step 2: Two real, named options
| Easy Digital Downloads + Software Licensing | Freemius | |
|---|---|---|
| What it is | A WordPress-native e-commerce plugin (EDD) plus an official extension purpose-built for license key generation, activation, deactivation, version checks, and update delivery | A hosted SaaS platform acting as merchant of record, handling checkout, subscriptions, taxes and compliance, licensing, and a customer self-service portal |
| Where it runs | On your own WordPress site, you own the store and the data | Hosted by Freemius, your plugin integrates with their SDK and dashboard |
| What it handles for you | License key generation and validation, activation limits per license, renewal reminders and discounts, update delivery through the standard WordPress updater UI, upgrade/renewal reporting | Checkout and subscriptions, global tax and compliance handling, licensing with activations and renewals, a customer portal for licenses and billing, affiliate management, product analytics |
| Good fit for | Developers who want to run their own store on their own site and already use, or are comfortable running, WordPress-based e-commerce | Developers who want to offload payments, tax compliance, and the checkout/billing surface entirely to avoid building or maintaining it themselves |
This course names EDD and Freemius because they’re real, well-known, and specifically built for this problem in the WordPress ecosystem, not because one is objectively better. Evaluate both against your own pricing model, your comfort running your own store versus using a hosted merchant of record, and your actual support and tax situation.
Step 3: The shape of a license check, in plugin code
Whichever service you use (or if you build your own), the plugin-side pattern is similar: store the license key, call a remote endpoint to validate it, cache the result so you’re not checking on every page load, and gate update access on a valid response.
// File: includes/class-license-check.php
class My_Plugin_License {
private $api_url = 'https://my-site.com/wp-json/my-plugin/v1/license';
public function is_valid() {
$cached = get_transient( 'my_plugin_license_status' );
if ( false !== $cached ) {
return 'valid' === $cached;
}
$license_key = get_option( 'my_plugin_license_key' );
if ( empty( $license_key ) ) {
return false;
}
$response = wp_remote_post( $this->api_url, array(
'timeout' => 15,
'body' => array(
'license' => $license_key,
'site_url' => home_url(),
'action' => 'check',
),
) );
if ( is_wp_error( $response ) ) {
// Network failure: don't lock the customer out, re-check soon instead.
set_transient( 'my_plugin_license_status', 'valid', HOUR_IN_SECONDS );
return true;
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
$status = isset( $body['status'] ) ? $body['status'] : 'invalid';
set_transient( 'my_plugin_license_status', $status, 12 * HOUR_IN_SECONDS );
return 'valid' === $status;
}
}
A few decisions in that snippet are deliberate. The transient caches the result so a
license isn’t re-validated on every single page load, which would add latency and hit
your license server unnecessarily. The home_url() value ties the check to a specific
site, matching the “activate against a site URL” requirement from Step 1. And a network
failure fails open (treats the license as valid for the caching window) rather than
locking a paying customer out of their own site because your license server had a bad
minute, a real and common failure mode worth deciding on deliberately rather than by
accident.
If your license server is briefly unreachable and your check fails closed (treats the customer as unlicensed), you disable a paying customer’s plugin because of your own downtime, not theirs. Decide this behavior on purpose. Most real systems, including EDD and Freemius, fail open for a grace period rather than immediately locking out an active customer.
Test it
Point the snippet above at a mock endpoint (or a real one on a staging license server) and confirm all three paths behave as intended:
# Valid license
curl -s -X POST https://my-site.com/wp-json/my-plugin/v1/license \
-d "license=TEST-VALID&site_url=http://example.test&action=check"
# Invalid license
curl -s -X POST https://my-site.com/wp-json/my-plugin/v1/license \
-d "license=TEST-INVALID&site_url=http://example.test&action=check"
Then simulate a network failure (block the request, or point at a nonexistent host) and confirm the plugin still treats the site as licensed for the length of the cached grace window, rather than immediately disabling functionality.
Recap
A license key system, whether you build it or use EDD’s Software Licensing extension or Freemius, needs to activate, deactivate, tie to a site URL, and check on update. EDD keeps the store on your own WordPress site and gives you direct ownership of licensing data; Freemius acts as a hosted merchant of record and takes on payments, tax compliance, and the checkout surface entirely. Whichever you choose, decide deliberately how your plugin behaves when the license check itself fails, since failing closed on a network error punishes the customer for your infrastructure, not theirs.
Resources & further reading
- Easy Digital Downloads, easydigitaldownloads.com
- EDD Software Licensing extension, easydigitaldownloads.com
- Freemius, freemius.com