Skip to content

ADR 0006 — Installation identity

Status: accepted Date: 2026-08-21

Context

The platform needs to count installations against a plan limit and to recognise the same site across a domain change. The plugin needs a stable identifier that survives normal WordPress operations.

The candidates all have problems. site_url changes on an SSL migration, a www redirect or a domain move. A hash of the database prefix changes on migration. home_url plus the admin email changes when either does.

The site is also copyable: staging refreshes, backups and clones are routine, and none of them are abuse.

Decision

A UUID v4, generated once, plus a server-issued credential.

  • installation_uuid from Support\Uuid, stored in ad_form_license_identity, seeded in Activator::activate_site().
  • Never regenerated on a normal request. Generated lazily only if the option is missing, which is the repair path for installs predating licensing.
  • A malformed stored value is replaced rather than trusted.
  • Scope is per site, including per blog on multisite. See ADR 0007.
  • After activation the platform issues an installation_token. The licence key is never transmitted again.
  • site_url and home_url are reported verbatim on every request. There is no local URL comparison anywhere. Reconciliation is the API's job.
  • forget_credentials() drops the token, installation id, licence id and masked key but keeps the UUID, so a reconnect maps to the same installation rather than consuming another slot.

The UUID is an identifier, not a secret: safe in logs and support tickets. The token is confidential and never appears in HTML, JavaScript, REST responses or logs, not even partially.

Consequences

Good:

  • Stable across SSL migration, www changes, domain moves and prefix changes.
  • Reconnecting does not consume an activation.
  • No PII in the identifier.
  • The plugin cannot mistakenly deactivate a working production site by comparing strings, which is the classic way licensing breaks a customer's site.

Costs:

  • A cloned site carries the same UUID, so two installations report the same identity. Intentional: the plugin reports the collision alongside the differing URLs and the API decides. A local clone check would misfire on legitimate restores.
  • Wiping the option looks like a new installation. Bounded by the platform's activation limit, and visible server-side as repeated activations of one licence.

Lifecycle

EventBehaviour
First activationGenerate and store
Normal requestRead only
Backup restoreUUID returns with the backup, which is correct
Domain migrationSame UUID, new URL, both reported
Clone or staging refreshSame UUID, different URL, both reported. API decides
Plugin deactivatedUntouched
Plugin deleted, data deletion enabledRemoved with the other options

Rejected

Derive identity from site_url. Breaks on every URL change, which is the most common support case in licensing systems.

Hash the database prefix or table names. Breaks on migration.

Use the admin email. PII, and it changes.

Detect clones locally and self-deactivate. The plugin cannot distinguish a clone from a restore, and getting it wrong disables a paying customer's production site. Reporting and letting the API decide is strictly better.

Regenerate the UUID when the URL changes. Turns every migration into a new activation.

Digitale Lösungen. Persönlich. Zukunftssicher.