PBJ License Manager runs on the site you sell from and acts as a license server for your own plugins and themes. It stores products and license keys, tracks which customer sites activated each key, serves update manifests to the WordPress update system, and streams plugin zips through signed, expiring download links. A key never disables a customer's plugin — it only controls whether they receive updates.
Update URI system)pbj-license-manager.zip and click Install Now.wp_pbj_licenses table and generates its two secrets (issue API key, download signing secret).Settings carry over on upgrades.
Work through the tabs in this order:
Products tab. Add one product per plugin you sell.
pbj-seo. This is the identity used in keys, REST URLs, and the client.2.1.0. You will bump this on every release./home/you/private/pbj-seo.zip. Keep it OUTSIDE the public web root; the plugin streams it through signed links. Preferred over Package URL.Settings tab.
{product} and {key} are replaced at send time.Form Title => product-slug. Matching is by form title, case-insensitive (form IDs are never used because they change across Studio→Live syncs).Secrets (bottom of Settings tab).
X-PBJ-LM-Key header when an external system POSTs to the issue endpoint.Issuing a key manually. Keys tab → Issue a key → pick product, enter buyer email and order reference, keep "Email key to buyer" checked → Issue key. The key appears in the notice and the list.
Automatic keys from PBJ Form Builder. Nothing to do once the map is set: every PAID submission on a mapped form mints a key and emails it to the submission's email field. Free submissions never mint keys.
Issuing from code. From any plugin on the same site:
do_action( 'pbj_license_issue', 'pbj-seo', 'buyer@example.com', 'order-123' );
Issuing from an external system. POST to /wp-json/pbj-license/v1/issue with header X-PBJ-LM-Key: <your API key> and body fields product, email, order_ref. The response contains the key.
Revoking / reinstating. Keys tab → search the key or buyer email → Revoke (refunds, chargebacks) or Reinstate.
Exporting. Keys tab → Export CSV.
Managing package files (Packages tab, 1.1.0+). Upload each release zip with its version number; files are stored in wp-content/uploads/pbj-license-packages/<product>/ behind a deny-all .htaccess and served only through signed expiring links. Leaving "Set as current release" ticked updates the product's package file AND latest version in one step. Old versions stay listed per product with size/date; "Make current" rolls back or forward, and Delete removes anything that isn't the current package.
Shipping a release. Packages tab → upload the new zip with "Set as current release" ticked. Done — customer sites see the update within ~6 hours (their manifest cache), or immediately after Dashboard → Updates → Check Again.
Download link in key emails (1.1.0+). The {download_url} placeholder in the key email is a signed link (default 72 hours, Settings → Email download link lifetime) to the current release of the major version the buyer purchased. Feature/minor updates are never emailed — they flow through the WordPress update system. Use "Resend email" on any key row to send a fresh copy with a fresh link.
Shipping a paid major upgrade. Do the above AND bump Current major version. Existing keys keep working installs but get manifests with reason new_major and no package; sell them a new key (a discount coupon to existing buyers works well).
flush_cache() runs when the key is re-saved), and confirm the update still shows but the download link is gone.Upload the new pbj-license-manager.zip via Plugins → Add New → Upload Plugin and choose "Replace current with uploaded". Settings, products, and all license data survive. Once the plugin's own update channel is live at pbj.tech, updates arrive through the normal WordPress update UI instead.
paid_cents > 0) in Form Builder. Confirm the product slug on the right side of => exists in Products.wp_mail failed on your host. The key is in the Keys tab — resend manually. Installing an SMTP plugin fixes delivery permanently.site_limit. The key's sites are used up. Keys tab shows the hostnames; ask the customer to deactivate an old site (their plugin's license field) or raise Sites per key on the product.wp-content/plugins/pbj-license-manager/pbj-license-manager.php{prefix}pbj_licensespbj_license_manager_settingspbj_license_manager_productspbj_license_manager_db_versionpbj_license_manager_ (server side), pbj_lc_ (client side, on customer sites)/wp-json/pbj-license/v1/ (activate, deactivate, manifest/<product>, download/<product>, issue)wp-content/uploads/pbj-license-packages/<product>/ (deny-all .htaccess; managed from the Packages tab)wp-content/plugins/pbj-license-manager/sdk/class-pbj-license-client.phpjake@pbj.tech in includes/class-pbj-license-manager-feedback.php