Add Licensing and Updates to a WordPress Plugin or Theme
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
- Copy the file to
my-plugin/includes/class-vendwick-updater.php. - 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
) );
- 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
| Key | Default | Notes |
|---|---|---|
store_url | required | Store home URL. |
item_id / item_slug | one required | The id wins when both are set. |
file or theme | one required | Plugin main file, or theme stylesheet. |
version | required | Installed version. |
option | {slug}_license | Where the key and status live. Network-wide (site_option) on multisite. |
beta | false | Ask the store for the beta channel. |
cache_ttl | 12 hours | How long release info and the license status are trusted. |
grace | 7 days | How long a valid license stays valid while the store is unreachable. |
require_sha256 | true | Refuse a package the store sent no checksum for. |
capability | manage_options | Who can manage the license. |
api_base | Vendwick REST path | Override for stores that serve the license API somewhere else. The action name is appended. |
notices | true | License 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_urlmust behttps://. Plainhttp://is accepted only for a local store (localhost,127.0.0.1,::1, or a host ending in.testor.localhost); anything else throwsInvalidArgumentException. To allow another host, for example a staging store on your LAN, return true from thevendwick_updater_allow_insecure_storefilter (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 + gracewithout a successful check; after that, updates pause (statusunverified) 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_initand 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,testedandrequires_php. - Downloads: package URLs live 15 minutes, so
upgrader_pre_downloadasks 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.