Plugin Integration

Add licensing andautomatic updates to your WordPress plugin in two steps. One file, six lines — that's it.

The client is self-contained: it renders its own license screen, verifies daily in the background, and wires up updates. You don't touch any WordPress hooks yourself.

Step 1 — Add the client file

Copy this file into your plugin folder as dw-license-client.php. Nothing to edit inside it.

dw-license-client.php
<?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.

dw-ai-blog-automation.php
<?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():

dw-ai-blog-automation.php
<?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().

includes/license-settings.php
<?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
<?php
register_deactivation_hook( __FILE__, function () {
	wp_clear_scheduled_hook( 'dw_verify_dw-ai-blog-automation' );
} );

Test it

  1. Generate a license in the dashboard and paste it on your test site's license screen.
  2. The status should flip to Active — check the dashboard Logs page for the activation entry.
  3. Publish a higher GitHub release, then open Dashboard → Updates in WP — your plugin should offer the new version.