Plugin Integration
Add licensing andautomatic updates to your WordPress plugin in two steps. One file, six lines — that's it.
Step 1 — Add the client file
Copy this file into your plugin folder as dw-license-client.php. Nothing to edit inside it.
<?php
/**
* Devs Wizard license client — drop-in licensing + auto-updates for one plugin.
* Just copy this whole file into your plugin. No edits needed here.
* Docs: https://license.devswizard.com/docs
*/
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DW_License_Client {
private $server, $slug, $version, $plugin_file, $name, $opt, $hook, $settings_page;
public function __construct( $args ) {
$this->server = untrailingslashit( $args['server'] );
$this->slug = $args['slug'];
$this->version = $args['version'];
$this->plugin_file = $args['plugin_file'];
$this->name = isset( $args['name'] ) ? $args['name'] : $args['slug'];
$this->settings_page = ! empty( $args['settings_page'] );
$this->opt = 'dw_license_' . $this->slug;
$this->hook = 'dw_verify_' . $this->slug;
// Auto-updates.
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_for_update' ) );
add_filter( 'plugins_api', array( $this, 'plugins_api' ), 20, 3 );
// Daily background verification.
add_action( $this->hook, array( $this, 'verify' ) );
if ( ! wp_next_scheduled( $this->hook ) ) {
wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', $this->hook );
}
// Optional built-in Settings screen.
if ( $this->settings_page ) {
add_action( 'admin_menu', array( $this, 'add_settings_page' ) );
}
}
/* ---------- stored state ---------- */
public function get_key() { $d = get_option( $this->opt, array() ); return isset( $d['key'] ) ? $d['key'] : ''; }
public function is_valid() { $d = get_option( $this->opt, array() ); return ! empty( $d['valid'] ); }
private function store( $key, $valid, $status = '', $expires = null ) {
update_option( $this->opt, array(
'key' => $key, 'valid' => (bool) $valid, 'status' => $status,
'expires_at' => $expires, 'checked_at' => time(),
) );
}
private function pick( $a, $k ) { return isset( $a[ $k ] ) ? $a[ $k ] : null; }
/* ---------- HTTP ---------- */
private function post( $endpoint, $body ) {
$res = wp_remote_post( $this->server . $endpoint, array(
'timeout' => 15,
'headers' => array( 'Content-Type' => 'application/json' ),
'body' => wp_json_encode( $body ),
) );
if ( is_wp_error( $res ) ) { return array( 'ok' => false, 'data' => array( 'message' => $res->get_error_message() ) ); }
$code = wp_remote_retrieve_response_code( $res );
$data = json_decode( wp_remote_retrieve_body( $res ), true );
return array( 'ok' => ( $code >= 200 && $code < 300 ), 'data' => is_array( $data ) ? $data : array() );
}
/* ---------- license lifecycle ---------- */
public function activate( $key ) {
$r = $this->post( '/api/license/activate', array( 'license_key' => $key, 'domain' => home_url(), 'plugin_slug' => $this->slug, 'plugin_version' => $this->version ) );
$ok = $r['ok'] && ! empty( $r['data']['valid'] );
$this->store( $key, $ok, $this->pick( $r['data'], 'status' ), $this->pick( $r['data'], 'expires_at' ) );
return $r['data'];
}
public function verify() {
$key = $this->get_key();
if ( empty( $key ) ) { return; }
$r = $this->post( '/api/license/verify', array( 'license_key' => $key, 'domain' => home_url(), 'plugin_slug' => $this->slug ) );
$ok = $r['ok'] && ! empty( $r['data']['valid'] );
$this->store( $key, $ok, $this->pick( $r['data'], 'status' ), $this->pick( $r['data'], 'expires_at' ) );
}
public function deactivate() {
$key = $this->get_key();
if ( empty( $key ) ) { return array(); }
$r = $this->post( '/api/license/deactivate', array( 'license_key' => $key, 'domain' => home_url(), 'plugin_slug' => $this->slug ) );
delete_option( $this->opt );
return $r['data'];
}
/* ---------- auto-updates ---------- */
private function remote_info() {
$res = wp_remote_get( add_query_arg( array(
'plugin_slug' => $this->slug, 'version' => $this->version, 'license_key' => $this->get_key(),
), $this->server . '/api/update/check' ), array( 'timeout' => 15 ) );
if ( is_wp_error( $res ) ) { return null; }
return json_decode( wp_remote_retrieve_body( $res ), true );
}
public function check_for_update( $transient ) {
if ( empty( $transient->checked ) ) { return $transient; }
$info = $this->remote_info();
if ( ! empty( $info['update_available'] ) ) {
$transient->response[ $this->plugin_file ] = (object) array(
'slug' => $this->slug, 'plugin' => $this->plugin_file,
'new_version' => $info['new_version'], 'package' => $info['download_url'], 'url' => $this->server,
);
}
return $transient;
}
public function plugins_api( $result, $action, $args ) {
if ( 'plugin_information' !== $action || empty( $args->slug ) || $args->slug !== $this->slug ) { return $result; }
$info = $this->remote_info();
if ( ! is_array( $info ) ) { return $result; }
return (object) array(
'name' => $this->name, 'slug' => $this->slug,
'version' => $this->pick( $info, 'new_version' ),
'download_link' => $this->pick( $info, 'download_url' ),
'sections' => array( 'changelog' => (string) $this->pick( $info, 'changelog' ) ),
);
}
/* ---------- built-in Settings screen ---------- */
public function add_settings_page() {
add_options_page( $this->name . ' License', $this->name . ' License', 'manage_options', 'dw-license-' . $this->slug, array( $this, 'render_settings_page' ) );
}
public function render_settings_page() {
if ( isset( $_POST['dw_key'] ) && check_admin_referer( 'dw_license_' . $this->slug ) ) {
$resp = $this->activate( sanitize_text_field( wp_unslash( $_POST['dw_key'] ) ) );
$class = ! empty( $resp['valid'] ) ? 'success' : 'error';
echo '<div class="notice notice-' . esc_attr( $class ) . '"><p>' . esc_html( isset( $resp['message'] ) ? $resp['message'] : '' ) . '</p></div>';
}
if ( isset( $_POST['dw_deactivate'] ) && check_admin_referer( 'dw_license_' . $this->slug ) ) {
$this->deactivate();
echo '<div class="notice notice-success"><p>License deactivated.</p></div>';
}
$key = $this->get_key(); $valid = $this->is_valid();
?>
<div class="wrap">
<h1><?php echo esc_html( $this->name ); ?> License</h1>
<p>Status: <strong><?php echo $valid ? 'Active' : 'Inactive'; ?></strong></p>
<form method="post">
<?php wp_nonce_field( 'dw_license_' . $this->slug ); ?>
<input type="text" name="dw_key" class="regular-text" value="<?php echo esc_attr( $key ); ?>" placeholder="DW-XXXX-XXXX-XXXX-XXXX" />
<p>
<?php submit_button( 'Activate', 'primary', 'submit', false ); ?>
<?php submit_button( 'Deactivate', 'secondary', 'dw_deactivate', false ); ?>
</p>
</form>
</div>
<?php
}
}
Step 2 — Start it from your main plugin file
Require the file and create the client with your plugin's details. The slug must match the one you registered in the dashboard.
<?php
/**
* Plugin Name: DW AI Blog Automation
* Version: 1.3.0
*/
if ( ! defined( 'ABSPATH' ) ) { exit; }
require_once plugin_dir_path( __FILE__ ) . 'dw-license-client.php';
new DW_License_Client( array(
'server' => 'https://license.devswizard.com',
'name' => 'DW AI Blog Automation',
'slug' => 'dw-ai-blog-automation', // must match the slug in your dashboard
'version' => '1.3.0', // keep in sync with the header above
'plugin_file' => plugin_basename( __FILE__ ),
'settings_page' => true, // adds Settings → "… License"
) );Done. Your plugin now has:
- A Settings → “… License” page where customers paste their key and click Activate.
- Automatic daily verification (revocations take effect within a day).
- Normal WordPress update prompts whenever you publish a newer GitHub release — the download is license-checked automatically.
Optional — lock premium features
To run code only when the license is valid, keep a single shared instance and check is_valid():
<?php
// Reuse one instance so you can check the license anywhere.
function my_plugin_license() {
static $client = null;
if ( null === $client ) {
$client = new DW_License_Client( array(
'server' => 'https://license.devswizard.com',
'name' => 'DW AI Blog Automation',
'slug' => 'dw-ai-blog-automation',
'version' => '1.3.0',
'plugin_file' => plugin_basename( __FILE__ ),
'settings_page' => true,
) );
}
return $client;
}
my_plugin_license(); // boot it
// Gate premium features behind a valid license:
if ( my_plugin_license()->is_valid() ) {
require_once plugin_dir_path( __FILE__ ) . 'includes/premium.php';
}Optional — a nicer settings page
The built-in screen (settings_page => true) is plain. If you'd prefer a friendlier page to ship to your users — with a status pill and a masked key — set settings_page => false and drop this ready-made version in instead. It uses the same shared instance from my_plugin_license().
<?php
/**
* A nicer, ready-made license settings page you can ship to your users.
* To use it: set 'settings_page' => false in the client args, then drop this in.
* It relies on the shared instance from my_plugin_license().
*/
add_action( 'admin_menu', function () {
add_options_page( 'License', 'DW License', 'manage_options', 'dw-license', 'dw_render_license_page' );
} );
function dw_render_license_page() {
$client = my_plugin_license();
if ( isset( $_POST['dw_key'] ) && check_admin_referer( 'dw_license' ) ) {
$resp = $client->activate( sanitize_text_field( wp_unslash( $_POST['dw_key'] ) ) );
$class = ! empty( $resp['valid'] ) ? 'notice-success' : 'notice-error';
echo '<div class="notice ' . esc_attr( $class ) . '"><p>' . esc_html( isset( $resp['message'] ) ? $resp['message'] : '' ) . '</p></div>';
}
if ( isset( $_POST['dw_deactivate'] ) && check_admin_referer( 'dw_license' ) ) {
$client->deactivate();
echo '<div class="notice notice-success"><p>License deactivated.</p></div>';
}
$key = $client->get_key();
$active = $client->is_valid();
$masked = $key ? substr( $key, 0, 7 ) . str_repeat( 'x', 8 ) . substr( $key, -4 ) : '';
?>
<div class="wrap">
<h1>License</h1>
<div style="max-width:520px;margin-top:16px;border:1px solid #dcdcde;border-radius:10px;background:#fff;padding:24px;">
<div style="display:flex;align-items:center;justify-content:space-between;">
<h2 style="margin:0;font-size:16px;">Plugin License</h2>
<span style="padding:2px 10px;border-radius:999px;font-size:12px;font-weight:600;background:<?php echo $active ? '#e6f6ef' : '#fdecea'; ?>;color:<?php echo $active ? '#07845b' : '#b3261e'; ?>;">
<?php echo $active ? 'Active' : 'Inactive'; ?>
</span>
</div>
<?php if ( $active ) : ?>
<p style="margin:16px 0 6px;color:#50575e;">Your license is active on this site.</p>
<code style="display:inline-block;margin-bottom:16px;"><?php echo esc_html( $masked ); ?></code>
<form method="post">
<?php wp_nonce_field( 'dw_license' ); ?>
<button type="submit" name="dw_deactivate" class="button">Deactivate</button>
</form>
<?php else : ?>
<p style="margin:16px 0 8px;color:#50575e;">Enter your license key to unlock updates and premium features.</p>
<form method="post">
<?php wp_nonce_field( 'dw_license' ); ?>
<input type="text" name="dw_key" class="regular-text" style="width:100%;" placeholder="DW-XXXX-XXXX-XXXX-XXXX" value="<?php echo esc_attr( $key ); ?>" />
<p style="margin-top:12px;"><button type="submit" class="button button-primary">Activate License</button></p>
</form>
<?php endif; ?>
</div>
</div>
<?php
}Optional — clean up on deactivation
Remove the scheduled verification when the plugin is deactivated in WordPress:
<?php
register_deactivation_hook( __FILE__, function () {
wp_clear_scheduled_hook( 'dw_verify_dw-ai-blog-automation' );
} );Test it
- Generate a license in the dashboard and paste it on your test site's license screen.
- The status should flip to Active — check the dashboard Logs page for the activation entry.
- Publish a higher GitHub release, then open Dashboard → Updates in WP — your plugin should offer the new version.