Engineering

How to Add License Key Validation to Your WordPress Plugin

TOT
Traffic Orchestrator Team
Engineering
April 8, 2026 6 min read 569 words
Share

<h2>License-Protect Your WordPress Plugin</h2>

<p>You've built a WordPress plugin that people want to pay for. Now you need to protect it. This guide shows you how to add license key validation to your plugin so you can sell premium features, gate updates, and track installations.</p>

<h3>The WordPress Licensing Challenge</h3>

<p>WordPress plugins have unique licensing challenges:</p> <ul> <li><strong>No built-in licensing</strong> — WordPress has no native concept of license keys</li> <li><strong>Easy to pirate</strong> — PHP source code is readable; you can't rely on obfuscation</li> <li><strong>Update distribution</strong> — The WordPress updater needs to be hooked to check license status</li> <li><strong>Multi-site support</strong> — One key may need to work across multiple sites</li> </ul>

<h3>Step 1: Create the License Checker Class</h3>

<pre><code>class TO_License_Checker { private $api_url = 'https://api.trafficorchestrator.com/api/v1'; private $option_key = 'my_plugin_license_key';

public function validate() { $key = get_option($this->option_key); if (empty($key)) { return ['valid' => false, 'error' => 'No key entered']; }

$domain = parse_url(home_url(), PHP_URL_HOST); $response = wp_remote_post($this->api_url . '/validate', [ 'body' => json_encode([ 'key' => $key, 'domain' => $domain ]), 'headers' => ['Content-Type' => 'application/json'], 'timeout' => 10 ]);

if (is_wp_error($response)) { // Network error — check cached status return $this->get_cached_status(); }

$body = json_decode(wp_remote_retrieve_body($response), true);

// Cache the result for offline/error scenarios update_option('my_plugin_license_status', $body); update_option('my_plugin_license_checked', time());

return $body; }

private function get_cached_status() { $cached = get_option('my_plugin_license_status'); $checked = get_option('my_plugin_license_checked', 0);

// Accept cached result for up to 72 hours if ($cached && (time() - $checked) < 259200) { $cached['cached'] = true; return $cached; }

return ['valid' => false, 'error' => 'Unable to verify']; } }</code></pre>

<h3>Step 2: Add the Admin Settings Page</h3>

<pre><code>add_action('admin_menu', function() { add_options_page( 'My Plugin License', 'My Plugin License', 'manage_options', 'my-plugin-license', 'render_license_page' ); });

function render_license_page() { if (isset($_POST['license_key'])) { update_option('my_plugin_license_key', sanitize_text_field($_POST['license_key'])); $checker = new TO_License_Checker(); $result = $checker->validate(); }

$key = get_option('my_plugin_license_key', ''); $status = get_option('my_plugin_license_status', []);

echo '<div class="wrap">'; echo '<h1>License Activation</h1>'; echo '<form method="post">'; echo '<table class="form-table"><tr>'; echo '<th>License Key</th>'; echo '<td><input type="text" name="license_key" value="' . esc_attr($key) . '" class="regular-text">'; echo '<p class="description">Enter your license key from Traffic Orchestrator.</p></td>'; echo '</tr></table>'; submit_button('Activate License'); echo '</form></div>'; }</code></pre>

<h3>Step 3: Gate Premium Features</h3>

<pre><code>function is_premium_active() { $status = get_option('my_plugin_license_status', []); return !empty($status['valid']); }

// Use in your plugin code: if (is_premium_active()) { // Show premium feature add_action('admin_init', 'register_premium_settings'); } else { // Show upgrade prompt add_action('admin_notices', function() { echo '<div class="notice notice-info"><p>'; echo 'Upgrade to Pro for advanced features. '; echo '<a href="' . admin_url('options-general.php?page=my-plugin-license') . '">Activate License</a>'; echo '</p></div>'; }); }</code></pre>

<h3>Step 4: Gate Plugin Updates</h3>

<pre><code>add_filter('pre_set_site_transient_update_plugins', function($transient) { if (!is_premium_active()) { return $transient; // No updates without valid license }

// Check your update server for new versions $update_url = 'https://api.trafficorchestrator.com/api/v1/releases/check'; $response = wp_remote_get($update_url . '?product=my-plugin&current=' . MY_PLUGIN_VERSION);

if (!is_wp_error($response)) { $update = json_decode(wp_remote_retrieve_body($response)); if ($update && version_compare($update->version, MY_PLUGIN_VERSION, '>')) { $transient->response['my-plugin/my-plugin.php'] = (object) [ 'slug' => 'my-plugin', 'new_version' => $update->version, 'package' => $update->download_url ]; } }

return $transient; });</code></pre>

<h3>Best Practices for WordPress Licensing</h3>

<ul> <li><strong>Cache validation results</strong> — Don't call the API on every page load. Check once per day and cache.</li> <li><strong>Graceful degradation</strong> — If the API is unreachable, honor the cached result for 72 hours.</li> <li><strong>Clear admin notices</strong> — Tell users exactly what to do when their license expires.</li> <li><strong>Multi-site support</strong> — Use <code>network_admin_menu</code> for network-wide license management.</li> </ul>

<p><a href="/docs/quickstart/wordpress">View the WordPress Quickstart →</a> | <a href="/docs/quickstart/php">PHP Quickstart →</a></p>

Related Articles

TOT
Traffic Orchestrator Team
Engineering

The engineering team behind Traffic Orchestrator, building enterprise-grade software licensing infrastructure used by developers worldwide.

Was this article helpful?
Get licensing insights delivered

Engineering deep-dives, security advisories, and product updates. Unsubscribe anytime.

Share this article
Free Plan Available

Ship licensing in your next release

5 licenses, 500 validations/month, full API access. Set up in under 5 minutes — no credit card required.

2-minute setup No credit card Cancel anytime