WP License Guard
Drop-in license validation for your WordPress plugins and themes, on any license API
Products you sell (Lemon Squeezy, EDD, Generic REST, engzaki.pro) and the SDK on this site diagnostics
Your license key appears in your account (My license keys) as soon as the payment is confirmed, and is emailed to you.
Overview
Selling a WordPress plugin or theme means writing the same licensing code again and again: a key field, activation calls, caching, what to do when the license server is down, notices, updates. WP License Guard gives you that layer once, done carefully, so you can get back to building your product.
A small SDK you bundle in your product
Copy one folder into your plugin or theme and register your product with a single call. The SDK talks to your license API, stores the verdict, and answers wplg_is_valid() and wplg_feature() from cache, so gating never adds an HTTP request to a page view. Your customers get a clean license page with Activate, Check now and Deactivate, plus clear notices when a key is missing, invalid, expired or about to expire.
Built for the real world
- Offline grace period: a timeout, 5xx or maintenance page never switches a paying customer off. Only a definitive answer from your server does.
- Exponential backoff and background re-checks with WP-Cron, timeouts capped at 8 seconds, every error caught.
- Versioned loader: many products on one site can bundle different SDK versions without class collisions; only the newest copy loads.
- Domain normalisation, automatic re-activation after a site move, and network-wide licenses on multisite.
Works with the store you already use
Presets for engzaki.pro, Lemon Squeezy, Easy Digital Downloads Software Licensing, License Manager for WooCommerce, the WooCommerce Software Add-on and Freemius-style license-key APIs. Anything else connects through the generic REST adapter: set the endpoint, method, parameter names and the JSON paths for the verdict, reason, expiry, plan and features. You can also add your own adapter class.
A control panel for you
The plugin lets you register the products you sell, generates ready-to-paste code for plugins, themes and uninstall.php, downloads the SDK, and includes a connection tester that calls your real API and shows the normalised result next to the raw answer with secrets masked. You can even preview the exact license page your customers will see.
Plan-based feature rules, an optional self-updater for plugins and themes, translation-ready strings with Arabic included, and two complete examples in the box. WP License Guard is a client: it works with your existing store and does not issue keys or host a license server.
Features
Drop-in SDK
One folder and one wplg_register() call add licensing to any plugin or theme. Fully prefixed and self-contained.
Seven API presets
engzaki.pro, Lemon Squeezy, EDD Software Licensing, License Manager for WooCommerce, WooCommerce Software Add-on, Freemius-style APIs and a generic REST adapter.
Generic REST mapping
Point it at any JSON license API: URLs with placeholders, method, JSON/form/query, parameter names, JSON paths and a reason map.
Offline grace period
Network errors, timeouts and 5xx answers never disable a valid license. Configurable grace period, 7 days by default.
Cache, cron and backoff
Verdicts cached with a configurable TTL, re-checked in the background with WP-Cron, failures retried with exponential backoff.
Ready-made license page
One argument adds a license page; or print the license box inside your own settings screen. Admin notices included.
Feature gating
wplg_is_valid() and wplg_feature() read cached state only. Unlock features by plan name or by the feature list your API returns.
Versioned loader
Several products on one site can ship different SDK versions. Only the newest loads, so nothing collides.
Self-updater
Optional updates for plugins and themes from engzaki.pro, EDD or your own endpoint, with the View details modal.
Multisite and domain aware
Network-wide licenses for network-activated plugins, domain normalisation and automatic re-activation after a site move.
Code generator and SDK download
Copy-paste integration code for plugins, themes and uninstall.php, generated from the settings you saved.
Connection tester and preview
Call your real API from wp-admin, read the normalised result and the masked raw answer, and preview your customers' license page.
Installation
- 1Upload wp-license-guard-1.0.0.zip in Plugins → Add New → Upload Plugin and activate it.
- 2Open License Guard → License and activate your engzaki.pro license key.
- 3Go to License Guard → Products → Add product and choose your license API preset.
- 4Open License Guard → Integration code, download the SDK and copy its folder into your plugin or theme.
- 5Paste the generated code into your main plugin file (or functions.php) and gate your features with wplg_is_valid() / wplg_feature().
- 6Optional: try your API in the Connection tester and enable “Preview on this site” to see the license page your customers get.
Documentation
Show the full documentationHide the documentation+
Drop-in license validation client for your own WordPress plugins — talks to any license API, caches results, and gracefully degrades offline.
By Zaki International · Product page: https://engzaki.pro/en/products/wp-license-guard · Version 1.0.0 · GPL-2.0-or-later
Contents
- Overview
- Requirements
- Installation
- The plugin, screen by screen
- Using the SDK in your product
- Arguments reference
- License API presets
- How it works
- Hooks reference
- Self-updater
- Multisite
- Uninstall
- Troubleshooting
- FAQ
- Licensing of WP License Guard itself
1. Overview
WP License Guard has two parts:
| Part | Who uses it | What it is |
|---|---|---|
SDK (sdk/) |
Your customers' sites, inside your product | A self-contained, prefixed PHP library you bundle in your plugin or theme. It activates/checks/deactivates keys against your license API, caches the verdict, keeps working offline for a grace period, re-checks in the background, renders the license page and notices, gates features, and can deliver updates. |
| Plugin | You, the developer | A control panel to register the products you sell, configure their license API, generate copy-paste integration code, download the SDK, test API calls and preview the license page. |
Your customers never need the WP License Guard plugin — only the sdk/ folder you ship inside your product.
2. Requirements
- WordPress 6.0 or later (tested up to 6.8)
- PHP 7.4 – 8.3
- Outgoing HTTPS from the site to your license server
- WP-Cron (default) or a real system cron for background re-checks
- For the "Download SDK" button: the PHP Zip extension (otherwise copy the
sdk/folder by hand)
3. Installation
- Plugins → Add New → Upload Plugin → choose
wp-license-guard-1.0.0.zip→ Install → Activate. - License Guard → License: paste your engzaki.pro key (
EZ-XXXX-XXXX-XXXX-XXXX) → Activate license. - License Guard → Products → Add product.
- License Guard → Integration code: download the SDK, paste the code into your product.
4. The plugin, screen by screen
Products
The list of products you sell: name, slug, type (plugin/theme), license API, cache / grace settings and — when Preview on this site is on — the live status of the preview. Actions: Edit, Integration code, Test, Delete (deleting only removes it from this list; code you shipped is unaffected).
Below the list, SDK on this site shows the SDK version that is actually loaded and from which folder, every other copy bundled by other plugins/themes (only the newest is loaded), and every product on this site that uses the SDK with its license status. Handy when debugging a customer's site.
Add / edit product
- Product — Name, Slug (your plugin/theme folder name; also the id for
wplg_is_valid( 'slug' )), Type. - License API — choose a preset; only that preset's settings are shown (see §7). Secrets (password fields) are kept when left empty.
- Behaviour — Cache validation for (hours, default 12), Offline grace period (days, default 7), Request timeout (1–8 s), License page location (Settings, Tools, Appearance, Plugins, own top-level menu, or under your own plugin menu by slug), Feature rules (one per line:
exportorwhite_label: Agency, Unlimited). - Links & updates — Buy / Renew / Customer account URLs shown on the license page and notices; Self-updater; Preview on this site.
Integration code
- Get the SDK — downloads
wp-license-guard-sdk-<version>.zipcontaining awp-license-guard/folder. - Paste the code — pick a product; you get the code for your main plugin file (or
functions.phpfor themes), an optional block for rendering the license box in your own settings page and reacting to status changes, and anuninstall.phpblock. A warning appears when the configuration contains a secret that would ship with your product.
Connection tester
Runs Check, Activate (may consume an activation) or Deactivate against your real API with the saved product settings, optionally as another domain (a *.test/localhost domain usually does not use a slot). Shows the normalised result (status, reason, server message, expiry, plan, features, activations, HTTP status, time) and the raw answer with tokens, secrets, e-mails and the key masked. Nothing is stored on the site except the activation data of the test (1 hour, per user) so you can check and deactivate the same test activation.
Settings
On uninstall: delete the product list, settings and this plugin's own license data when the plugin is deleted. Other products' license data is never touched.
License
WP License Guard's own license page — rendered by the SDK itself (dogfooding).
Preview pages
Each product with Preview on this site gets a "Preview: Name" page under License Guard: the exact license page your customers will see, wired to your real API, stored under a separate id (wplg-preview-<slug>) so it never interferes with a real installation of the product on the same site.
5. Using the SDK in your product
5.1 Plugin
Copy the SDK folder to my-plugin/vendor/wp-license-guard/, then in your main plugin file:
require_once __DIR__ . '/vendor/wp-license-guard/wp-license-guard-sdk.php';
wplg_register( array(
'slug' => 'my-plugin',
'name' => 'My Plugin',
'file' => __FILE__,
'api' => 'lemonsqueezy',
'api_config' => array( 'store_id' => '12345', 'product_id' => '67890' ),
'buy_url' => 'https://example.com/my-plugin/',
'menu' => array( 'parent' => 'options-general.php' ), // Settings → My Plugin License.
'features' => array( 'export' => true, 'white_label' => array( 'Agency' ) ),
) );
// Anywhere later (init or after):
if ( wplg_is_valid( 'my-plugin' ) ) {
// Licensed code.
}
if ( wplg_feature( 'my-plugin', 'white_label' ) ) {
// Agency-only code.
}
wplg_register() can be called immediately: before the SDK boots the call is queued and replayed. It returns the WPLG_SDK_Product when the SDK is already loaded, otherwise null; use wplg( 'my-plugin' ) to get the product later.
5.2 Theme
Copy the SDK to my-theme/inc/wp-license-guard/, then in functions.php:
require_once get_template_directory() . '/inc/wp-license-guard/wp-license-guard-sdk.php';
wplg_register( array(
'slug' => 'my-theme',
'name' => 'My Theme',
'type' => 'theme',
'file' => __FILE__,
'api' => 'edd',
'api_config' => array( 'store_url' => 'https://store.example.com', 'item_id' => '1234' ),
'menu' => array( 'parent' => 'themes.php' ),
'updater' => true,
) );
Themes load after plugins_loaded: the loader notices it and boots immediately (or reuses a newer copy a plugin already loaded).
5.3 Your own settings page
Leave out menu, tell the notices where your page is with page_url, and print the box:
wplg( 'my-plugin' )->render_license_box(); // Box only.
wplg( 'my-plugin' )->render_license_page(); // With .wrap and <h1>.
5.4 Public API
| Function / method | Returns |
|---|---|
wplg_register( array $args ) |
WPLG_SDK_Product or null (queued) |
wplg( $id ) |
WPLG_SDK_Product or null |
wplg_is_valid( $id ) |
bool — valid or in grace (cached, no HTTP) |
wplg_feature( $id, $feature ) |
bool |
$p->activate( $key, $fields = array() ) |
WPLG_SDK_Result |
$p->check( $force = false ) |
WPLG_SDK_Result or null (skipped) |
$p->deactivate() |
WPLG_SDK_Result |
$p->status() |
valid · grace · expired · invalid · pending · none |
$p->get_state() |
stored state array |
$p->grace_days_left(), $p->expires_in_days() |
int / `int |
$p->render_license_page(), $p->render_license_box() |
prints HTML |
$p->page_url() |
license page URL |
$p->delete_data() / WPLG_SDK_Product::purge( $id ) |
removes stored data |
WPLG_SDK_Result has status (valid/invalid/error), reason, message, expires_at, plan, features, activations, max_activations, product, http_code, plus is_valid(), is_invalid(), is_error().
Canonical reasons: not_found, invalid, expired, revoked, activation_limit, wrong_product, site_inactive, bad_request, failed (definitive) and network, timeout, rate_limited, bad_response, auth, config, http_<code>, exception (errors — never switch a license off).
6. Arguments reference
| Argument | Default | Notes |
|---|---|---|
slug |
— (required) | Product slug; sent to APIs that take a product (engzaki.pro, generic REST). |
id |
slug |
Storage/hook id: option wplg_sdk_<id>, hooks wplg_sdk_cron_<id>… |
name |
slug |
Display name. |
type |
plugin |
plugin or theme. |
file |
'' |
Your main plugin file (__FILE__) or any theme file. Needed for the updater, version detection, network detection and deactivation cleanup. |
theme |
from file |
Theme stylesheet (type theme). |
version |
header | Read from the plugin header / style.css when empty. |
api |
engzaki |
engzaki, lemonsqueezy, edd, lmfwc, wc-software, freemius, rest, a custom id, or a WPLG_SDK_Adapter instance. |
api_config |
array() |
Adapter settings (§7). |
cache_ttl |
12 * HOUR_IN_SECONDS |
Minimum 5 minutes. |
grace_period |
7 * DAY_IN_SECONDS |
Counted from the last successful check. |
timeout |
8 |
Seconds, capped at 8. |
retry_base / retry_max |
300 / cache_ttl |
Backoff: base × 2^(failures−1), capped, +≤10 % jitter. |
scope |
auto |
auto, site, network (multisite). |
capability |
manage_options |
manage_network_options for network scope. |
reactivate_on_domain_change |
true |
Re-activate in the background when the site URL changes. |
menu |
false |
true or array( 'parent', 'slug', 'page_title', 'menu_title', 'position', 'icon', 'priority' ). parent '' = top-level. Titles may be closures (translated late). |
notices |
true |
Admin notices on Dashboard, Plugins, Themes, Updates (+ notice_screens). |
notice_screens |
array() |
Extra screen ids. |
buy_url, renew_url, account_url |
'' |
Links on the page and in notices. |
page_url |
'' |
License page URL when you render the box yourself. |
strings |
array() |
Overrides: page_title, key_label, key_placeholder, activate, deactivate, buy, notice_none. |
features |
array() |
`feature => true |
updater |
false |
true or array( 'homepage', 'icons', 'banners', 'description', 'author' ). |
7. License API presets
All presets were written against the vendors' public documentation / source. Test with your real store before shipping (the connection tester makes that easy).
engzaki.pro (engzaki)
| Setting | Default |
|---|---|
api_base |
https://engzaki.pro |
product |
product slug |
update_slug |
product slug |
POST /api/license/validate {key, domain, product} (activate = check; the server binds the domain on first validation, local/dev hosts don't use a slot), POST /api/license/deactivate, updates from POST /api/plugins/<slug>/update. Keys are upper-cased.
Lemon Squeezy (lemonsqueezy)
Settings: store_id, product_id (both recommended — keys of other stores/products are rejected and their instance released), variant_ids (optional allow-list). Uses POST /v1/licenses/activate (instance named after the domain), /validate with the stored instance_id, /deactivate. The variant name becomes the plan for feature rules. No API key needed.
Easy Digital Downloads – Software Licensing (edd)
Settings: store_url (required), item_id (preferred) or item_name. Uses edd_action=activate_license | check_license | deactivate_license | get_version with license, item_id, url and environment. Lifetime licenses have no expiry. EDD's serialized sections/banners/icons are decoded without ever instantiating objects.
License Manager for WooCommerce (lmfwc)
Settings: store_url, consumer_key, consumer_secret (required), auth (basic over HTTPS or query), product_ids (recommended allow-list). Uses REST API v2: GET /wp-json/lmfwc/v2/licenses/activate/{key}?label=<domain> (stores the activation token), GET /licenses/{key} (status, expiry and that our token is still active), GET /licenses/deactivate/{key}?token=…. Authentication failures are treated as errors (grace period), not as invalid keys. The consumer key/secret ship inside your product: create a dedicated key with minimal permissions.
WooCommerce Software Add-on (wc-software)
Settings: store_url, product_id (the Software tab's Product ID). The customer also enters the license email (the API requires it; the license page shows the field automatically). Uses ?wc-api=software-api&request=activation | check | deactivation with a stable per-site instance. Error codes 100–105 are mapped (101 not found, 102 deactivated, 103 activation limit, 104 invalid instance).
Freemius-style license-key API (freemius)
Settings: api_base (default https://api.freemius.com), product_id. Uses the public license-key activation endpoints Freemius documents for non-WordPress clients: POST /v1/products/{id}/licenses/activate.json {uid, license_key, title}, GET /installs/{install_id}/license.json?uid&license_key (expiration, is_cancelled), POST /licenses/deactivate.json. This is not the Freemius WordPress SDK; the same adapter works with any server that copies this shape.
Generic REST (rest)
| Setting | Meaning |
|---|---|
activate_url (required), check_url, deactivate_url, update_url |
URLs; placeholders {key}, {domain}, {product}, {instance}, {uid} (URL-encoded). |
method, check_method |
POST / GET. |
format |
json, form or query. |
field_key, field_domain, field_product, field_instance |
Parameter names (empty = not sent). |
product_value |
Value sent as product (default: slug). |
extra_params, headers |
JSON objects. |
path_valid + valid_values |
Dot path of the verdict and the values meaning valid (e.g. data.status + active,valid; empty list = any truthy value). |
path_reason, path_message, path_expires, path_instance, path_plan, path_features, path_activations, path_max_activations |
Dot paths into the answer. |
reason_map |
JSON object mapping your codes to canonical reasons. |
uppercase_keys |
Upper-case keys before sending. |
Interpretation: no response / 5xx / 429 / non-JSON → error (grace). A verdict at path_valid → valid or invalid. No verdict but a 4xx → invalid (401/403 → auth error). A non-valid status value such as expired becomes the reason.
Your own adapter
class My_Adapter extends WPLG_SDK_Adapter {
public static function id() { return 'mine'; }
public function activate( array $ctx ) { /* $ctx: key, domain, url, product, version, activation, fields, uid, timeout */
$res = $this->call( 'POST', 'https://api.example.com/activate', array( 'k' => $ctx['key'], 'd' => $ctx['domain'] ), 'json', $ctx );
if ( $err = WPLG_SDK_Http::transport_error( $res ) ) { return $err; }
return ! empty( $res['json']['ok'] )
? WPLG_SDK_Result::valid( array( 'expires_at' => $res['json']['until'] ) )
: WPLG_SDK_Result::invalid( 'not_found' );
}
public function check( array $ctx ) { return $this->activate( $ctx ); }
}
add_filter( 'wplg_sdk_adapters', function ( $map ) { $map['mine'] = 'My_Adapter'; return $map; } );
(Define the class after the SDK has loaded, e.g. on wplg_sdk_loaded, or pass an instance as api.)
8. How it works
Versioned loader. wp-license-guard-sdk.php registers its version and folder in a global registry. At plugins_loaded priority 0 (or immediately when included later) the newest registered copy's classes are loaded — once. Shared helper functions (wplg_register(), wplg(), wplg_is_valid(), wplg_feature()) are defined by whichever copy loads first and only forward calls, so their behaviour never depends on the copy.
State. Each product stores one small autoloaded option wplg_sdk_<id> (site option for network licenses): key, extra fields, status (none/valid/invalid/unreachable), reason, expiry, plan, features, activation data (instance id/token), domain, a stable 32-char installation uid, timestamps, failure count and next retry time.
Display status. valid; grace (unreachable, last success within the grace period); expired (expiry date passed or server said expired); invalid; pending (key entered but never confirmed because the server was unreachable); none. is_valid() is true for valid and grace.
Checks.
- Activation and Check now are synchronous (user-initiated, ≤ 8 s).
- Everything else is background: a twice-daily (hourly when the TTL is under 12 h) WP-Cron event re-checks when the cache transient has expired; an admin page view that finds a stale cache or a changed domain schedules a one-off event 5 s later. A 60 s lock prevents parallel checks.
- Definitive answers (valid/invalid) are cached for the TTL. Invalid licenses keep being re-checked, so a renewed or re-enabled license comes back on its own.
- Errors increment the failure counter and set the next retry with exponential backoff; a valid license becomes
unreachableand stays usable through the grace period. - When the adapter needs activation data (Lemon Squeezy instance, LMFWC token, Freemius install) and none is stored, the background check activates instead.
- When the site's domain changes (migration, staging clone) the next check re-activates on the new domain (filter
wplg_sdk_reactivate_on_domain_change).
Never fatal. Product construction errors make wplg_register() return null; every public method catches Throwable; HTTP failures are values, not exceptions; the admin renderer and notices are wrapped.
9. Hooks reference
| Hook | Type | Arguments |
|---|---|---|
wplg_sdk_loaded |
action | $version |
wplg_sdk_registered |
action | $product |
wplg_sdk_product_args |
filter | $args |
wplg_sdk_adapters |
filter | $map (id ⇒ class) |
wplg_sdk_is_valid, wplg_sdk_<id>_is_valid |
filter | $valid, $product |
wplg_sdk_feature, wplg_sdk_<id>_feature |
filter | $on, $feature, $product |
wplg_sdk_status_changed |
action | $new, $old, $product |
wplg_sdk_<id>_status_changed |
action | $new, $old, $product |
wplg_sdk_result |
action | $result, $state, $product (after every API answer) |
wplg_sdk_activated, wplg_sdk_deactivated |
action | $result, $product |
wplg_sdk_http_args |
filter | $request_args, $url (timeout re-capped at 8 s) |
wplg_sdk_domain |
filter | $domain, $product |
wplg_sdk_network_scope |
filter | $network, $product |
wplg_sdk_capability |
filter | $cap, $product |
wplg_sdk_reactivate_on_domain_change |
filter | $bool, $product |
wplg_sdk_notice_html |
filter | $html, $status, $product ('' hides) |
wplg_sdk_license_box_after |
action | $product |
wplg_sdk_update_info |
filter | $info, $product |
Plugin-only: wplg_licensed (filter, force the tools on), wplg_capability (filter), wplg_loaded (action).
10. Self-updater
Set 'updater' => true (or an array with homepage, icons, banners, description, author). Supported by engzaki (POST /api/plugins/<slug>/update), edd (get_version) and rest (update_url, same contract as engzaki.pro: { version, package, requires, tested, requires_php, homepage, last_updated, sections, icons, banners }). The answer is cached 12 h (failures 6 h). An empty package means "update available — renew/activate to download". Plugins get the update row and "View details" modal; themes get the Themes update screen.
11. Multisite
scope => auto(default): a plugin that is network-activated has one network-wide license (site option; page under Network Admin → Settings; capabilitymanage_network_options; checked by the main site's cron). Otherwise each site has its own license.- The domain of a network license is the network's main domain.
- Force with
scope => 'network'or'site'.
12. Uninstall
- WP License Guard's
uninstall.phpalways clears its cron events. With Settings → On uninstall enabled it deleteswplg_products,wplg_settings, its own license (wplg_sdk_wp-license-guard), preview data and tester scratch data. Data of other products that bundle the SDK is never touched. - In your own product, call
WPLG_SDK_Product::purge( 'my-plugin' )or use the generateduninstall.phpsnippet (works without the SDK loaded).
13. Troubleshooting
| Symptom | What to check |
|---|---|
| "Could not reach the license server" | Outgoing HTTPS blocked (firewall, WP_HTTP_BLOCK_EXTERNAL), DNS, or the server is down. The license stays valid during the grace period; Check now retries immediately. |
| "Pending" after activating | The first activation could not reach the server. It will retry automatically with backoff, or press Check now. |
| Status never refreshes | WP-Cron disabled (DISABLE_WP_CRON) without a system cron. Add a real cron for wp-cron.php, or use Check now. |
| "This license key belongs to a different product" | Wrong product / store_id / product_id / item_id / product_ids in api_config, or the customer used another product's key. |
| "Activation limit" | Deactivate the key on another site (from that site or your store's dashboard). Local *.test/localhost sites usually don't count on engzaki.pro. |
| LMFWC: "rejected the product's API credentials" | Consumer key/secret wrong, lacking permissions, or the route is disabled in LMFWC settings. |
| Old SDK behaviour on a site | License Guard → Products → SDK on this site shows which copy is loaded and from where. |
| No update offered | file/version missing, update endpoint not returning version, or the cached answer (12 h) — updates are re-checked when WordPress refreshes its update transients. |
| Download SDK fails | The PHP Zip extension is missing: copy wp-content/plugins/wp-license-guard/sdk/ by hand. |
14. FAQ
Does it stop piracy? No client-side licensing in GPL software can. It makes licensing correct, pleasant and robust for paying customers and discourages casual unlicensed use.
Does it phone home? Only to the license/update API you configure, when activating, checking (about twice a day) and checking for updates. No tracking, no CDN assets.
What data is sent? The key, the normalised domain, the product id/slug, and adapter-specific values (e.g. Lemon Squeezy instance id, EDD site URL and environment type, WooCommerce Software Add-on email). The updater also sends WordPress/PHP versions and locale (engzaki.pro/REST contract).
Can I keep my settings page and only use the license box? Yes — §5.3.
Can I change texts? Use the strings argument; all SDK strings are translatable (text domain wp-license-guard, Arabic included).
15. Licensing of WP License Guard itself
WP License Guard is licensed through engzaki.pro using its own SDK (adapter engzaki, 12 h cache, 7-day grace, self-updates from https://engzaki.pro/api/plugins/wp-license-guard/update). Without an active license, Products and Settings remain usable; the code generator, SDK download, connection tester and previews are locked with a notice and a link to https://engzaki.pro/en/products/wp-license-guard. Nothing on the site breaks.
Changelog
= 1.0.0 = * First release: versioned SDK loader; adapters for engzaki.pro, Lemon Squeezy, EDD Software Licensing, License Manager for WooCommerce, WooCommerce Software Add-on, Freemius-style APIs and a generic REST adapter; caching, grace period, exponential backoff, WP-Cron re-checks; license page renderer, notices, gating helpers; self-updater for plugins and themes; product registry, code generator, SDK download, connection tester, on-site preview; Arabic translation.
FAQ
+Do my customers need to install WP License Guard?
No. They only get the SDK folder you bundle inside your product. The plugin is your developer tool.
+What happens when the license server is down?
A previously valid license keeps working for the grace period (7 days by default) while the SDK retries in the background with exponential backoff. Only a definitive answer from the server turns a license off.
+Will it slow down my customers' sites?
Gating reads one cached option and never makes an HTTP request. Re-validation runs from WP-Cron, and every request has a timeout of 8 seconds or less.
+Which license servers does it support?
engzaki.pro, Lemon Squeezy, Easy Digital Downloads Software Licensing, License Manager for WooCommerce, the WooCommerce Software Add-on, Freemius-style license-key APIs, and any JSON API through the generic REST adapter. You can also register your own adapter class.
+What if two plugins on one site bundle different SDK versions?
Each copy registers its version and only the newest is loaded, the same approach Action Scheduler uses, so there are no class collisions.
+Does it work with themes and multisite?
Yes. Register a theme with 'type' => 'theme'. On multisite, a network-activated plugin gets one network-wide license managed in Network Admin.
+Does it issue license keys?
No. WP License Guard is a client. Keys, activations and payments stay in the store you already use.
+Can it stop piracy?
No client-side licensing in GPL software can. It makes licensing reliable for paying customers and discourages casual unlicensed use.
Support
Every purchase includes support from the developer. If something does not work as described, send us your license key and site address and we will help you get it running.