Telemetry
Introduction
bitapps/wp-telemetry adds two optional facilities to a WordPress plugin:
- an administrator notice for telemetry consent and a weekly usage report after opt-in;
- a feedback dialog when an administrator deactivates the plugin.
The package does not choose the receiving service. You provide the server base URL, terms URL and privacy-policy URL, and you remain responsible for disclosing the data sent to that service.
Installation
The starter already includes the package. Install it directly in another plugin with Composer:
composer require bitapps/wp-telemetry
Because the boilerplate prefixes dependencies, import the generated Deps namespace:
use YourPlugin\Deps\BitApps\WPTelemetry\Telemetry\Telemetry;
use YourPlugin\Deps\BitApps\WPTelemetry\Telemetry\TelemetryConfig;
Configuration and initialization
Initialize telemetry once while the plugin is bootstrapping. A private method on Plugin keeps the setup alongside the other services:
use YourPlugin\Deps\BitApps\WPTelemetry\Telemetry\Telemetry;
use YourPlugin\Deps\BitApps\WPTelemetry\Telemetry\TelemetryConfig;
private function registerTelemetry(): void
{
TelemetryConfig::setTitle(Config::TITLE);
TelemetryConfig::setSlug(Config::SLUG);
TelemetryConfig::setPrefix(Config::VAR_PREFIX);
TelemetryConfig::setVersion(Config::VERSION);
TelemetryConfig::setServerBaseUrl('https://telemetry.example.com/');
TelemetryConfig::setTermsUrl('https://example.com/terms/');
TelemetryConfig::setPolicyUrl('https://example.com/privacy/');
Telemetry::report()->init();
Telemetry::feedback()->init();
}
Call it from the constructor after registerInstaller():
public function __construct()
{
$this->registerInstaller();
$this->registerTelemetry();
Hooks::addAction('plugins_loaded', [$this, 'loaded']);
}
| Setting | Meaning |
|---|---|
title | Human-readable plugin name shown in the consent and feedback UI |
slug | WordPress plugin slug and translation domain |
prefix | Unique prefix for hooks and options; Config::VAR_PREFIX already ends in _ |
version | Current plugin version included in every request |
serverBaseUrl | API base URL; the package appends plugin-track-create or deactivate-reason |
termsUrl | Optional link displayed in the consent notice |
policyUrl | Optional privacy-policy link displayed in the consent notice |
Telemetry::report()->init() registers the consent notice, opt-in and opt-out handlers, weekly cron schedule, and lifecycle listeners. Telemetry::feedback()->init() registers the deactivation dialog and its AJAX endpoint. Omit either call if the plugin does not use that facility.
Consent and scheduling
Until tracking is allowed, administrators with the manage_options capability see an opt-in notice. Its actions are nonce-protected and behave as follows:
| Action | Result |
|---|---|
| Allow | Stores {prefix}allow_tracking, sends a report immediately and schedules {prefix}send_tracking_event weekly |
| Skip | Stores tracking as disabled, dismisses the notice, clears the scheduled event and sends a minimal skipped report |
| Plugin activation | Schedules and sends only when tracking was already allowed |
| Plugin deactivation | Clears the scheduled event and resets the notice dismissal |
The boilerplate's InstallerProvider already emits Config::withPrefix('activate') and Config::withPrefix('deactivate'), which are the lifecycle actions the package listens to. No additional activation or deactivation hook is needed when using the starter.
You can also connect your own settings UI to the same state:
$report = Telemetry::report();
$report->trackingOptIn();
$report->trackingOptOut();
$isAllowed = (bool) $report->isTrackingAllowed();
Only call these methods after verifying a nonce and an appropriate capability in your settings handler. The methods change options and scheduling but do not authorize the current request themselves.
trackingOptOut() sends the configured server a minimal skipped report containing the site URL, plugin prefix, plugin version and skip state. If your privacy requirements prohibit every request before consent, the built-in Skip flow does not satisfy that requirement as currently implemented.
Data sent in a usage report
After opt-in, the package sends the following data to {serverBaseUrl}/plugin-track-create at most once per week:
| Category | Fields |
|---|---|
| Site | Home URL and site name |
| Administrator | Admin email and the first/last name of the first administrator account |
| Server | Server software, PHP and MySQL versions, upload limit, timezone, and SOAP, socket and cURL availability |
| WordPress | Version, locale, memory limit, debug and multisite status, and active theme name, slug, version, URI and author |
| Users and plugins | User counts by role, plus active and inactive plugin counts |
| Network | Public IP fetched through https://icanhazip.com/ and whether the host appears to be local |
| Package | Plugin prefix, plugin version and the wp-telemetry library version |
Calling addPluginData() also includes the names and versions of active plugins, excluding the current plugin:
Telemetry::report()
->addPluginData()
->init();
Update the consent copy and privacy policy when enabling this extra field. The filter hooks below can remove fields before the main report is sent, but they do not alter the minimal skipped report or deactivation feedback.
Customize report data
Add plugin-specific metrics under additional_data:
add_filter(Config::withPrefix('telemetry_additional_data'), function (array $data): array {
$data['configured_integrations'] = 3;
return $data;
});
Use the final payload filter to redact or change built-in fields:
add_filter(Config::withPrefix('telemetry_data'), function (array $data): array {
unset($data['admin_email'], $data['first_name'], $data['last_name']);
return $data;
});
Register these filters before the report can run. Keep additional values aggregate and non-sensitive, and document each one in the opt-in copy and privacy policy.
Deactivation feedback
The feedback client intercepts the Deactivate link on the Plugins and Network Plugins screens. It asks for a reason, then posts the home URL, reason key and text, plugin prefix, and plugin version to {serverBaseUrl}/deactivate-reason. The AJAX handler requires a valid nonce and the activate_plugins capability.
Feedback submission is separate from usage-report consent: it can be sent even when weekly tracking is disabled. Administrators can choose Skip & Deactivate, which deactivates without submitting feedback.
Add a reason with the prefixed filter:
add_filter(Config::withPrefix('deactivate_reasons'), function (array $reasons): array {
$reasons['too_complex'] = [
'title' => 'The plugin is too difficult to configure',
'placeholder' => 'What could we simplify?',
];
return $reasons;
});
Reason titles and placeholders are rendered in the administrator's browser, so use translated, escaped strings when values are dynamic.
Extension points
With Config::VAR_PREFIX set to YOUR_PLUGIN_, the package registers these hooks:
| Hook suffix | Type | Purpose |
|---|---|---|
telemetry_additional_data | Filter | Add values under the report's additional_data key |
telemetry_data | Filter | Modify the complete usage report before sending |
deactivate_reasons | Filter | Modify feedback-dialog reasons |
tracking_opt_in | Action | Run code after tracking is enabled |
tracking_opt_out | Action | Run code after tracking is disabled |
send_tracking_event | Cron action | Send the scheduled usage report |
Always build the full hook tag with Config::withPrefix() so it stays aligned with TelemetryConfig::setPrefix().