Skip to content
Gaurav TiwariDocs

Add Licensing and Updates to a WordPress Plugin or Theme

Version 0.2.0 Updated November 15, 2026

class-vendwick-updater.php is one file you bundle with a plugin or theme you sell through a Vendwick store. It adds license activation and automatic updates through WordPress’s normal update screens.

  • PHP 7.4+, WordPress 5.5+, no dependencies, GPL-2.0-or-later.
  • Several products can bundle different copies. Each copy registers itself; the newest copy’s class loads once and serves them all.
  • It sends only the license key, the item id or slug, the site URL and the installed version.

Plugin in five minutes

  1. Copy the file to my-plugin/includes/class-vendwick-updater.php.
  2. Add this to your main plugin file:
require_once __DIR__ . '/includes/class-vendwick-updater.php';

vendwick_updater( array(
	'store_url'     => 'https://store.example.com',
	'item_id'       => 42,                 // product id on the store (or 'item_slug' => 'my-plugin')
	'file'          => __FILE__,
	'version'       => '1.4.0',            // keep in step with the Version: header
	'name'          => 'My Plugin',
	'text_domain'   => 'my-plugin',
	'settings_page' => array( 'parent' => 'options-general.php' ), // optional ready-made page
) );
  1. Publish a release on the store. Sites with an active license see the update on Dashboard → Updates and Plugins, with "View details".

The call queues the config. The updater boots on after_setup_theme, after every plugin has had a chance to register a newer copy. Called later than that, it boots at once and returns the instance.

Theme in five minutes

Copy the file to my-theme/inc/class-vendwick-updater.php, then in functions.php:

require_once get_template_directory() . '/inc/class-vendwick-updater.php';

vendwick_updater( array(
	'store_url'     => 'https://store.example.com',
	'item_slug'     => 'my-theme',
	'theme'         => get_template(),     // the theme's stylesheet (folder name)
	'version'       => wp_get_theme( get_template() )->get( 'Version' ),
	'name'          => 'My Theme',
	'text_domain'   => 'my-theme',
	'settings_page' => array( 'parent' => 'themes.php' ),
) );

Your own settings screen

Leave out settings_page and print the form wherever you like:

$updater = Vendwick_Updater::get( plugin_basename( __FILE__ ) ); // or the theme stylesheet
if ( $updater ) {
	$updater->render_license_field();
}

The form posts to admin-post.php. Every action checks the capability (default manage_options) and a nonce before doing anything, and the result shows as a notice on the next page. Output is escaped and the stored key is shown masked.

Other helpers: activate( $key ), deactivate(), check(), get_status(), is_valid(), notice() (returns level and message, or null).

Configuration

KeyDefaultNotes
store_urlrequiredStore home URL.
item_id / item_slugone requiredThe id wins when both are set.
file or themeone requiredPlugin main file, or theme stylesheet.
versionrequiredInstalled version.
option{slug}_licenseWhere the key and status live. Network-wide (site_option) on multisite.
betafalseAsk the store for the beta channel.
cache_ttl12 hoursHow long release info and the license status are trusted.
grace7 daysHow long a valid license stays valid while the store is unreachable.
require_sha256trueRefuse a package the store sent no checksum for.
capabilitymanage_optionsWho can manage the license.
api_baseVendwick REST pathOverride for stores that serve the license API somewhere else. The action name is appended.
noticestrueLicense notice on the Plugins, Themes and Updates screens.

How it behaves

  • API: POST {store}/_emdash/api/plugins/vendwick/license/{check|activate|deactivate|version}. A 404 or non-JSON answer falls back to the store’s EDD-style endpoint {store}/_vendwick/license?edd_action=…. That path is a store route, so it works even where a static home page answers / before the store does.
  • HTTPS only: store_url must be https://. Plain http:// is accepted only for a local store (localhost, 127.0.0.1, ::1, or a host ending in .test or .localhost); anything else throws InvalidArgumentException. To allow another host, for example a staging store on your LAN, return true from the vendwick_updater_allow_insecure_store filter (bool $allowed, string $store_url, array $config).
  • Reliability: one retry on a connection error, HTTP 429 or 5xx. A network error never changes the stored status. A valid license stays valid for cache_ttl + grace without a successful check; after that, updates pause (status unverified) but the key is kept, and the next successful check resumes them. An expiry date that passes is honored locally.
  • Daily recheck: WP-Cron, scheduled on admin_init and removed on plugin deactivation or theme switch. If WP-Cron doesn’t run, the updater rechecks before offering an update (at most once an hour).
  • Updates: offered only for a valid license, and only when the store’s version is newer (never a downgrade). "View details" shows the store’s sections (sanitized with wp_kses_post), banners, icons, requires, tested and requires_php.
  • Downloads: package URLs live 15 minutes, so upgrader_pre_download asks the store for a fresh one right before the download, then checks the ZIP’s SHA-256 against the store’s value. A mismatch deletes the file and stops the update.
  • Deactivation: the local key is removed only after the store confirms, so a failed call can be retried.

Moving from FluentCart or EDD

The same folder has vendwick-export.php (Vendwick 0.2.0), a read-only exporter that writes a FluentCart or Easy Digital Downloads store to one file for Vendwick’s store importer. It runs from WP-CLI (wp vendwick-export fluentcart --output=fluentcart.ndjson.gz) or as Tools, Vendwick export in the WordPress admin. Imported products keep answering their old item_id and slug, so plugins and themes already installed keep working once this updater (or their EDD updater) points at the new store. See docs/importing.md in the Vendwick package.

Share

Was this helpful?

Still stuck? Open a support ticket