Skip to main content

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:

backend/app/Plugin.php
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']);
}
SettingMeaning
titleHuman-readable plugin name shown in the consent and feedback UI
slugWordPress plugin slug and translation domain
prefixUnique prefix for hooks and options; Config::VAR_PREFIX already ends in _
versionCurrent plugin version included in every request
serverBaseUrlAPI base URL; the package appends plugin-track-create or deactivate-reason
termsUrlOptional link displayed in the consent notice
policyUrlOptional 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.

Until tracking is allowed, administrators with the manage_options capability see an opt-in notice. Its actions are nonce-protected and behave as follows:

ActionResult
AllowStores {prefix}allow_tracking, sends a report immediately and schedules {prefix}send_tracking_event weekly
SkipStores tracking as disabled, dismisses the notice, clears the scheduled event and sends a minimal skipped report
Plugin activationSchedules and sends only when tracking was already allowed
Plugin deactivationClears 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.

Consent behavior

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:

CategoryFields
SiteHome URL and site name
AdministratorAdmin email and the first/last name of the first administrator account
ServerServer software, PHP and MySQL versions, upload limit, timezone, and SOAP, socket and cURL availability
WordPressVersion, locale, memory limit, debug and multisite status, and active theme name, slug, version, URI and author
Users and pluginsUser counts by role, plus active and inactive plugin counts
NetworkPublic IP fetched through https://icanhazip.com/ and whether the host appears to be local
PackagePlugin 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 suffixTypePurpose
telemetry_additional_dataFilterAdd values under the report's additional_data key
telemetry_dataFilterModify the complete usage report before sending
deactivate_reasonsFilterModify feedback-dialog reasons
tracking_opt_inActionRun code after tracking is enabled
tracking_opt_outActionRun code after tracking is disabled
send_tracking_eventCron actionSend the scheduled usage report

Always build the full hook tag with Config::withPrefix() so it stays aligned with TelemetryConfig::setPrefix().